我正在try 使用TypeScrip中的类型函数来实现深度优先搜索(DFS)算法.

以下是我的代码: 首先,我定义了最终的DFSRecursive类型函数所需的所有不同类型的函数,在那里我实现了递归DFS算法.

const MyGraph: Graph = {
  A: ["B", "C", "D"],
  B: ["F", "E"],
  C: ["E", "F"],
  D: ["E", "F", "G"],
  E: [],
  F: [],
  G: [],
};

type Graph = {
  A: ["B", "C", "D"],
  B: ["F", "E"],
  C: ["E", "F"],
  D: ["E", "F", "G"],
  E: [],
  F: [],
  G: [],
}

export type Concat<T extends string[], U extends string[]> = [...T, ...U];
export type Pop<T extends string[]> = T extends [...infer Rest extends string[], infer Last extends string]
  ? T extends [infer Last]
  ? []
  : Rest
  : never;
export type Append<T extends string[], U extends string> = [...T, U];
export type IsEmpty<T extends string[]> = T extends [] ? true : false;
export type Difference<T extends string[], U extends string[], Result extends string[] = []> =
  T extends [infer First extends string, ...infer Rest extends string[]]
  ? false extends Contains<U, First> 
  ? Difference<Rest, U, Append<Result, First>>
  : Difference<Rest, U, Result>
  : T extends string[]
  ? string[]
  : Result;
export type Peek<T extends string[]> = T extends [...infer Rest extends string[], infer Last extends string]
  ? Last
  : T extends [infer Last extends string]
  ? Last
  : T extends string[]
  ? never
  : never;
export type Contains<T extends string[], Element extends string> = T extends [infer Head, ...infer Rest extends string[]]
  ? Head extends Element
  ? true
  : Contains<Rest, Element>
  : false;

export type DFSRecursive<Curr extends string, GraphType extends Graph, Stack extends string[], Visited extends string[] = []> =
  true extends IsEmpty<Stack>
  ? Visited
  : true extends Contains<Visited, Peek<Stack>>
  ? DFSRecursive<Peek<Stack>, GraphType, Pop<Stack>, Visited> //Append<Visited, Peek<Stack>>>
  : GraphType[keyof Graph & Peek<Stack>] extends infer GT extends string[]
  ? Difference<GT, Visited> extends infer D extends string[]
  ? Peek<Stack> extends infer PS extends string
  ? Append<Visited, PS> extends infer A extends string[]
  ? Concat<Pop<Stack>, D> extends infer C extends string[]
  ? DFSRecursive<Peek<Stack>, GraphType, C, A>
  : never
  : never
  : never
  : never
  : never;

type X = DFSRecursive<"A", Graph, ["A"]>

工作了几个小时后,我意识到有一个错误:

Type instantiation is excessively deep and possibly infinite.

我怎么可能解决这个问题呢?

Playground

推荐答案

像这样聪明的元编程的问题是,编写代码通常比阅读代码并理解所发生的事情更容易.

我不能准确地指出你的错误.我认为Difference()中可能有一个错误,它的行为似乎不像预期的那样,但可能有多个问题,因为您的代码相当复杂,而且您似乎没有测试过DFS实现所依赖的任何助手函数.

然而,我可以用一种更简单的方式重写算法,这样就能奏效:

export type Contains<A extends string[], E extends string> =
  A extends [infer Head extends string, ...infer Rest extends string[]]
  ? Head extends E ? true : Contains<Rest, E> : false;

export type Minus<A extends string[], B extends string[]> =
   A extends [infer First extends string, ...infer Rest extends string[]]
   ? (true extends Contains<B, First> ? Minus<Rest, B> : [First, ...Minus<Rest, B>])
   : [];

export type DFSRecursiveImpl<
    GraphType extends Record<string, string[]>,
    Stack extends string[],
    Visited extends string[]> =
  Stack extends [...infer Prefix extends string[], infer V extends string]
  ? Minus<GraphType[V], Visited> extends [infer W extends string, ...string[]]
  ? DFSRecursiveImpl<GraphType, [...Stack, W], [...Visited, W]>
  : DFSRecursiveImpl<GraphType, Prefix, Visited>
  : Visited;

export type DFSRecursive<GraphType extends Record<string, string[]>, Start extends string> =
  DFSRecursiveImpl<GraphType, [Start], [Start]>;

TypeScript playground link

上面的代码是通用的,因为它接受将字符串映射到字符串数组的任何类型作为图形定义,但它不判断顶点字符串是否作为属性键实际存在.

或多或少是偶然的,如果一些关键点没有定义,它仍然可以工作,并简单地处理缺少的顶点(例如,{"A":["B"]}被视为{"A":["B"], "B":[]}),这是非常好的默认行为.

可以创建要求顶点与图形类型中的关键点相对应的更安全版本,例如,如下所示:

export type DFSRecursiveSafe<
    Vertex extends string,
    GraphType extends Record<Vertex, Vertex[]>,
    Start extends Vertex>
  = DFSRecursive<GraphType, Start>;

type Accepted = DFSRecursiveSafe<keyof Graph, Graph, "A">;  // A B F E C D G

这保证了图形类型只包含已知顶点之间的边(有关更多示例,请参见操场链接).

最后注意:在代码中,我在所有地方都使用了string,但同样有可能使用PropertyKey,其优点是您可以使用数字或符号作为顶点ID.


我还看到了一条 comments ,问我是如何得出这个解决方案的.与最初的问题一样,我从一个最小的Python函数开始:

graph = {
  'A': ["B", "C", "D"],
  'B': ["F", "E"],
  'C': ["E", "F"],
  'D': ["E", "F", "G"],
  'E': [],
  'F': [],
  'G': [],
}

def DfsRecursive(stack, visited):
  if not stack:
    return visited
  v = stack[-1]
  for w in graph[v]:
    if w not in visited:
      return DfsRecursive(stack + [w], visited + [w])
  return DfsRecursive(stack[:-1], visited)

print(DfsRecursive(['A'], ['A']))

应该不难看出这是如何映射到类型脚本定义的(for-和if-语句被组合到减号<;>;实例中).

Typescript相关问答推荐

typescribe不能使用值来索引对象类型,该对象类型满足具有该值的另一个类型'

如何在排版中正确键入中间件链和控制器链

未在DIST中正确编译TypeScrip src模块导入

在TypeScrip的数组中判断模式类型

从子类构造函数推断父类泛型类型

如何实现异构数组内函数的类型缩小

部分更新类型的类型相等

TypeError:正文不可用-NextJS服务器操作POST

按函数名的类型安全类型脚本方法包装帮助器

常量类型参数回退类型

vue-tsc失败,错误引用项目可能无法禁用vue项目上的emit

有没有一种方法可以确保类型的成员实际存在于TypeScript中的对象中?

刷新时保持当前名称的Angular

如何使函数参数数组不相交?

在类型脚本中创建显式不安全的私有类成员访问函数

我可以将一个类型数组映射到TypeScrip中的另一个类型数组吗?

导航链接不触发自定义挂钩

Typescript泛型-键入对象时保持推理

如何使用动态生成的键和值定义对象的形状?

req.files = 未定义(Multer、Express、Typescript)