TS试炼-7

摘要:

包含:AllCombinations(全组合)、Greater Than(大于)、Zip(元组相同下标合并)、IsTuple(判断是否为元组)、Chunk(切片)、Fill(填充)、Trim Right(去除右侧空格)、Without(去除数组指定元素)、Trunc(取整)、IndexOf(获取索引)

1、AllCombinations(全组合)

Github去练习

ts 复制代码
type AllCombinations_ABC = AllCombinations<'ABC'>;
// should be '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'

第一种方案

ts 复制代码
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}` ? First | StringToUnion<Rest> : never;
type AllCombinations<S extends string, T extends string = StringToUnion<S>> = [T] extends [never]
  ? ''
  : '' | { [K in T]: `${K}${AllCombinations<never, Exclude<T, K>>}` }[T];

解释:

ts 复制代码
type A = '' | { A: 'A' }['A']; // '' | 'A'
type B = '' | { B: 'B' }['B']; // '' | 'B'
type C = '' | { C: 'C' }['C']; // '' | 'C'
type AB = '' | { A: `A${B}`, B: `B${A}` }['A' | 'B']; // '' | 'A' | 'AB' | 'B' | 'BA'
type BC = '' | { B: `B${C}`, C: `C${B}` }['B' | 'C']; // '' | 'B' | 'BC' | 'C' | 'CB'
type AC = '' | { A: `A${C}`, C: `C${A}` }['A' | 'C']; // '' | 'A' | 'AC' | 'C' | 'CA'
type ABC = '' | {
  A: `A${BC}`;
  B: `B${AC}`;
  C: `C${AB}`;
}['A' | 'B' | 'C'];
// '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
// 相当于
type ABC = '' | {
  A: 'A' | 'AB' | 'ABC' | 'AC' | 'ACB';
  B: 'B' | 'BA' | 'BAC' | 'BC' | 'BCA';
  C: 'C' | 'CA' | 'CAB' | 'CB' | 'CBA';
}['A' | 'B' | 'C'];

第二种方案

ts 复制代码
// StringToUnion同上
// 通过判断联合类型为never结束
type AllCombinations<
  S extends string,
  T extends string = StringToUnion<S>,
  U extends string = T,
> = [T] extends [never]
  ? ''
  : T extends T
    ? '' | `${T}${AllCombinations<never, Exclude<U, T>>}`
    : never;
    
// 通过判断字符串来结束
type AllCombinations<
  S extends string,
  T extends string = StringToUnion<S>,
  U extends string = T,
> = S extends `${infer F}${infer R}`
  ? U extends U
    ? '' | `${U}${AllCombinations<R, Exclude<T, U>>}`
    : never
  : '';

// 精简一下
type AllCombinations<
  S extends string,
  T extends string = StringToUnion<S> | '',
  U extends string = T,
> = T extends T ? '' | `${T}${AllCombinations<never, Exclude<U, T>>}` : never;

// 同上
type AllCombinations<
  S extends string,
  T extends string = StringToUnion<S> | '',
  U extends string = T,
> = T extends T ? `${T}${AllCombinations<never, Exclude<U, T>> | ''}` : never;

解释:
原理相同,只是第一种采用属性,第二种采用联合类型遍历。
上两种方案,都采用前面联合一个空字符串来实现全组合,如果只要全排列,就不需要联合空字符串了(精简版本不行)

扩展:

字符串全排列(只需要去除空字符串的联合即可)

ts 复制代码
// 'ABC' -> "ABC" | "BCA" | "BAC" | "CBA" | "CAB" | "ACB"
// 通过属性
type StringPermutation<S extends string, T extends string = StringToUnion<S>> = [T] extends [never]
  ? ''
  : { [K in T]: `${K}${StringPermutation<never, Exclude<T, K>>}` }[T];

// 通过联合遍历
type StringPermutation<
  S extends string,
  T extends string = StringToUnion<S>,
  U extends string = T,
> = [T] extends [never]
  ? ''
  : T extends T
    ? `${T}${StringPermutation<never, Exclude<U, T>>}`
    : never;

联合类型全排列

ts 复制代码
// 'A' | 'B' | 'C' -> ["A", "B", "C"] | ["B", "C", "A"] | ["B", "A", "C"] | ["C", "B", "A"] | ["C", "A", "B"] | ["A", "C", "B"]
type UnionPermutation<T, U = T> = [T] extends [never] ? [] : T extends U ? [T, ...UnionPermutation<Exclude<U, T>>] : [];

详情

元组全排列

ts 复制代码
// ['A', 'B', 'C'] -> ["A", "B", "C"] | ["B", "C", "A"] | ["B", "A", "C"] | ["C", "B", "A"] | ["C", "A", "B"] | ["A", "C", "B"]
// 使用上面联合类型的全排列
type TuplePermutation<S extends unknown[]> = UnionPermutation<S[number]>;
// 或者
type TuplePermutation<S extends unknown[], T = S[number], U = T> = [T] extends [never]
  ? []
  : T extends T
    ? [T, ...TuplePermutation<[], Exclude<U, T>>]
    : never;

总结

只要涉及到排列或者组合的,首先考虑转换为联合类型,使用联合类型的遍历,实现相关功能。

2、Greater Than(大于)

Github去练习

ts 复制代码
GreaterThan<2, 1> //should be true
GreaterThan<1, 1> //should be false
GreaterThan<10, 100> //should be false
GreaterThan<111, 11> //should be true

实现

ts 复制代码
// 数字转指定长度的元组
type NumberToTuple<T extends number | string, R extends 0[] = []> = `${R['length']}` extends `${T}` ? R : NumberToTuple<T, [...R, 0]>;
// 长度转指定长度的元组
type LengthToTuple<T extends number | string, R extends 0[] = []> = `${T}` extends `${infer _}${infer rest}` ? LengthToTuple<rest, [...R, 0]> : R;
// 比较两个元组的大小
type CompareTuple<T extends 0[], U extends 0[]> = T extends [...U, ...infer _] ? U extends T ? 0 : true : false;
// 比较两个数字的大小(因元组大小限制,此方法最多支持1000以内的比较)
type CompareNumber<T extends number | string, U extends number | string> = CompareTuple<NumberToTuple<T>, NumberToTuple<U>>;
// 判断是否大于
type GreaterThan<
  T extends number | string,
  U extends number | string,
  L = CompareTuple<LengthToTuple<T>, LengthToTuple<U>>, // 比较长度
> = L extends boolean
  ? L // 长度不同时,直接返回长度比较结果
  : [`${T}`, `${U}`] extends [`${infer TF}${infer TR}`, `${infer UF}${infer UR}`]
    ? CompareNumber<TF, UF> extends true // 相同位数比较
      ? true // 相同位数大于:大于
      : GreaterThan<TR, UR> // 相同位数不大于时,比较剩余位数
    : false; // 全部位数比较完都不大于,返回结果:不大于

解析:
思路:通过往元组添加指定位数项,判断继承关系,从而判断大小。
1、先比较位数,位数大则大,位数小则小,位数相同在对每一位进行比较
2、某一位大则返回大,不大则比较剩余位数
3、全部位数比较完,都没有大于的,则返回不大于。

3、Zip(元组相同下标合并)

Github去练习

ts 复制代码
type exp = Zip<[1, 2], [true, false]> // expected to be [[1, true], [2, false]]
type exp = Zip<[1, 2, 3], ['1', '2']> // expected to be [[1, '1'], [2, '2']]
type exp = Zip<[], [true, false]> // expected to be []

实现

ts 复制代码
type Zip<T extends unknown[], U extends unknown[]> = T extends [infer TF, ...infer TR] ? U extends [infer UF, ...infer UR] ? [[TF, UF], ...Zip<TR, UR>] : [] : []
// 也可以合并在一起判断
type Zip<T extends unknown[], U extends unknown[]> = [T, U] extends [[infer TF, ...infer TR], [infer UF, ...infer UR]] ? [[TF, UF], ...Zip<TR, UR>] : []

4、IsTuple(判断是否为元组)

Github去练习

ts 复制代码
type case1 = IsTuple<[number]> // true
type case2 = IsTuple<readonly [number]> // true
type case3 = IsTuple<number[]> // false

实现

ts 复制代码
type IsTuple<T> = [T] extends [never] ? false : T extends readonly any[] ? number extends T['length'] ? false : true : false

解释:
元组的length是固定值,而any[]的length是number

5、Chunk(切片)

Github去练习

ts 复制代码
type exp1 = Chunk<[1, 2, 3], 2> // expected to be [[1, 2], [3]]
type exp2 = Chunk<[1, 2, 3], 4> // expected to be [[1, 2, 3]]
type exp3 = Chunk<[1, 2, 3], 1> // expected to be [[1], [2], [3]]

实现

ts 复制代码
type Chunk<
  T extends unknown[],
  S extends number,
  C extends unknown[] = [], // 存储当前
  V extends unknown[] = [], // 存储结果
> = T extends [infer F, ...infer R]
  ? C['length'] extends S // 当前长度满足
    ? Chunk<R, S, [F], [...V, C]> // 将当前赋值给结果,重置当前
    : Chunk<R, S, [...C, F], V> // 当前继续添加元素
  : C extends []  // 结束时,判断当前是否有值
    ? V // 无值,直接返回结果
    : [...V, C]; // 有值,添加到结果

简化一下:省一个参数

ts 复制代码
type Chunk<
  T extends unknown[],
  S extends number = 1,
  V extends unknown[] = [], // 存储当前
> = T extends [infer F, ...infer R]
  ? V['length'] extends S // 当前长度满足
    ? [V, ...Chunk<R, S, [F]>] // 将当前赋值给结果,并执行剩余
    : Chunk<R, S, [...V, F]> // 当前继续添加元素
  : V['length'] extends 0  // 结束时,判断当前是否有值
    ? V // 无值,直接返回[]
    : [V]; // 有值,返回

6、Fill(填充)

Github去练习

ts 复制代码
type exp = Fill<[1, 2, 3], 0> // expected to be [0, 0, 0]
type exp = Fill<[1, 2, 3], 0, 0, 0> // [1, 2, 3]
type exp = Fill<[1, 2, 3], true, 0, 1> // [true, 2, 3]
type exp = Fill<[1, 2, 3], true, 1, 3> // [1, true, true]
type exp = Fill<[1, 2, 3], true, 1, 2> // [1, true, 3]

第一种方式

ts 复制代码
type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  R extends unknown[] = [],
> = T extends [infer First, ...infer Rest]
  ? R['length'] extends End // 如果到结束位置
    ? [...R, ...T] // 直接将前面已经替换的和剩余的合并返回
    : R['length'] extends Start // 如果到了开始替换的位置
      ? Fill<Rest, N, [...R, N]['length'], End, [...R, N]> // 将开始位置加一,并存储替换的数据
      : Fill<Rest, N, Start, End, [...R, First]> // 没到位置,就不加一,且保存不替换的数据
  : R; 

解释:
通过增加开始位置,判断继承关系来替换指定的值

第二种方式

ts 复制代码
type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  C extends 0[] = [],
> = T extends [infer F, ...infer R]
  ? [
      GreaterThan<Start, C['length']> extends true // 开始位置大于当前索引(当前索引未到开始位置)
        ? F // 不替换,使用原值
        : GreaterThan<End, C['length']> extends true // 结束位置大于索引(还没结束)
          ? N // 需要替换
          : F, // 结束了,不再替换
      ...Fill<R, N, Start, End, [...C, 0]>, // 剩余参数相同处理
    ]
  : [];

解释:
不修改开始位置,采用大小判断,当前索引大于等于开始,小于结束才替换。
注意:此方法性能较差,用到了前面的GreaterThan工具

7、Trim Right(去除右侧空格)

Github去练习

ts 复制代码
type Trimed = TrimRight<'  Hello World  '> // 应推导出 '  Hello World'

实现

ts 复制代码
type TrimRight<S extends string> = S extends `${infer R}${' ' | '\t' | '\n'}` ? TrimRight<R> : S

8、Without(去除数组指定元素)

Github去练习

ts 复制代码
type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []

实现

ts 复制代码
type ToUnion<T> = T extends any[] ? T[number] : T
type Without<T extends unknown[], U> = T extends [infer F, ...infer R] ? F extends ToUnion<U> ? Without<R, U> : [F, ...Without<R, U>]  : []

解释:
现将第二个类型转为联合类型,然后直接判断继承关系即可。

9、Trunc(取整)

Github去练习

ts 复制代码
type A = Trunc<12.34> // 12

第一种方案(思路)

ts 复制代码
type Trunc<T extends string | number> = `${T}` extends `-${infer R}`
  ? `-${Trunc<R>}` // 如果是负数,则添加负号,并对正数处理
  : `${T}` extends `${infer F}.${any}` // 如果有小数点
    ? F extends '' // 判断整数位是否为空
      ? '0' // 为空返回0
      : F // 不为空返回整数位
    : `${T}`; // 不是小数,直接返回字符串

第二种方案(第一种简化)

ts 复制代码
type Trunc<T extends string | number> = `${T}` extends `${infer N}.${any}` ? N extends '' | '-' | '+' ? `${N}0` : N : `${T}`;

第三种方案(填充0)

ts 复制代码
type FillZero<T extends string | number> = `${T}` extends `${infer N extends '' | '-' | '+'}.${infer R}` ? `${N}0.${R}` : `${T}`;
type Trunc<T extends string | number> = FillZero<T> extends `${infer N}.${any}` ? N : `${T}`;

10、IndexOf(获取索引)

Github去练习

ts 复制代码
type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // expected to be 2
type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1

实现

ts 复制代码
type IsEqual<T, S> = (<T>() => T extends S ? 1 : 0) extends (<S>() => S extends T ? 1 : 0) ? true : false;
type IndefOf<T extends unknown[], S, V extends 0[] = []> = S extends T[number]
  ? T extends [infer F, ...infer R]
    ? Equal<F, S> extends true
      ? V['length']
      : IndefOf<R, S, [...V, 0]>
    : -1
  : -1;

解释:
1、先通过S extends T[number]判断元组内是否有S,没有直接返回-1,不用寻找索引了
2、添加一个参数存储下标,找到时,返回下标,没找到时索引加一,再对剩余的处理

评论

0条评论

logo

暂无内容,去看看其他的吧~