如何利用 TypeScript 可变元组类型来实现笛卡尔积函数?

问题描述 投票:0回答:3

TypeScript 4.0 开始支持 Variadic Tuple Types 的概念,这是一个很好的类型构造,可以用于例如串联函数。文档中的示例:

type Arr = readonly any[];

function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
  return [...arr1, ...arr2];
}

我感兴趣的是这种类型构造是否可用于键入 Cartesian Product 函数。然后,该函数应从参数推断(混合)类型以生成其返回类型。因此,如果我输入

[number[], string[]]
,我希望输出的类型为
[number, string][]
。笛卡尔积的多个实现可以在this thread中找到,但没有一个是严格类型化的。这是一个例子:

const cartesian =
  (...a) => a.reduce((a, b) => a.flatMap(d => b.map(e => [d, e].flat())));

我当前使用的实现不使用可变元组类型,并且需要显式类型转换:

const cartesian = <T extends any[]>(...arr: any[][]): T[] =>
  arr.reduce<T[]>(
    (a, b) => a.flatMap<T>(c => b.map<T>(d => [...c, d] as T)),
    [[]] as T
  );

const product = cartesian<[number, string]>([1, 2, 3], ['a', 'b', 'c']);

我正在寻找一种无需显式类型转换的解决方案,我认为可变元组类型可能是这里合适的类型构造。

问题

如何使用可变元组类型来推断笛卡尔积函数的类型?

typescript cartesian-product typescript4.0 variadic-tuple-types
3个回答
8
投票

我不认为可变元组类型实际上改善了我们输入的方式。自从 3.1 中添加了对 mapping tuples 的支持以来,输入此内容实际上是可能的(以前可能是可能的,但没有那么干净)。

在实际实现中您仍然需要类型断言,但调用站点将推断参数类型并生成正确的返回类型:

type MapCartesian<T extends any[][]> = {
  [P in keyof T]: T[P] extends Array<infer U>? U: never
}
const cartesian = <T extends any[][]>(...arr: T): MapCartesian<T>[] =>
  arr.reduce(
    (a, b) => a.flatMap(c => b.map(d => [...c, d] )),
    [[]] 
  ) as MapCartesian<T>[];

const product = cartesian([1, 2, 3], ['a', 'b', 'c']);

游乐场链接


1
投票

如果是笛卡尔积,我们应该使用 Set 而不是数组。两者都用于输入和输出。

function cartesian<X, Y>(setX: Set<X>, setY: Set<Y>): Set<[X, Y]> {
    const result = new Set<[X, Y]>();
    setX.forEach(x => setY.forEach(y => result.add([x, y])));
    return result;
}

const product = cartesian(new Set([1, 2, 3]), new Set(['a', 'b', 'c']));

编辑(在尼基评论后):

我尝试概括任意数量的集合的函数签名,但是无法从数组切换到集合:

declare function cartesian<A extends Array<Array<any>>>(
    ...args: A): { [K in keyof A]: A[K] extends Array<any> ? A[K][number] : never }[];
const product = cartesian([1, 2, 3], ['a', 'b', 'c'], [true, false]); 

但是后来我仔细阅读了@Titian Cernicova-Dragomir 的答案,有很好的类型推断,所以我使用了他的集合方法:

declare function cartesian<A extends Array<Set<any>>>(
    ...args: A): Set<{ [K in keyof A]: A[K] extends Set<infer T> ? T : never }>;
const product = cartesian(new Set([1, 2, 3]), new Set(['a', 'b', 'c']), 
    new Set([true, false])); 

0
投票

正如 Titian 所说,可变参数元组类型在这里并不是真正必要的。您还可以通过使用通用

T
表示每个输入数组的内部类型来实现比 Titian 更紧凑的解决方案。另外,您应该使用
unknown
作为
any
的更安全替代品。

const cartesian = <T extends unknown[]>(...a: { [K in keyof T]: T[K][] }) =>
    a.reduce<T[]>(
        (b, c) => b.flatMap((d) => c.map((e) => [...d, e] as T)),
        [[]] as unknown as T[]
    );
© www.soinside.com 2019 - 2024. All rights reserved.