【问题标题】:How to utilise TypeScript Variadic Tuple Types for a Cartesian Product function?如何将 TypeScript 可变元组类型用于笛卡尔积函数?
【发布时间】:2021-03-09 12:46:44
【问题描述】:

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']);

我正在寻找一个没有显式类型转换的解决方案,我认为 Variadic Tuple Types 可能是这里合适的类型构造。

问题

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

【问题讨论】:

    标签: typescript cartesian-product typescript4.0 variadic-tuple-types


    【解决方案1】:

    我不认为 Variadic Tuple Types 实际上改进了我们输入它的方式。自从 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']);
    
    

    Playground Link

    【讨论】:

    • 非常好的解决方案!我遇到了适用于数组的function cartesian&lt;A extends Array&lt;Array&lt;any&gt;&gt;&gt;(...args: A): { [K in keyof A]: A[K] extends Array&lt;any&gt; ? A[K][number] : never }[];,但您的解决方案肯定更好,因为它允许输入“集合”是任何类型,不一定是数组。
    【解决方案2】:

    如果它是笛卡尔积,我们应该使用 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])); 
    

    【讨论】:

    • 感谢您的回答,但不幸的是,这不是问题所在。
    • @Nicky 好的,但是顺便说一下,对于两个“集合”的笛卡尔积,您需要这个吗?还是更多?
    • 我需要无限数量的集合,所以它应该处理 N 个不同的输入(而不是两个)
    • 对不起,我误解了原来的问题。但是@Titian Cernicova-Dragomir 提供的解决方案似乎完全没问题,不是吗?
    猜你喜欢
    • 2018-04-17
    • 2021-02-07
    • 2021-10-22
    • 1970-01-01
    • 2013-07-19
    • 1970-01-01
    • 2016-02-15
    • 2016-04-21
    • 1970-01-01
    相关资源
    最近更新 更多