如何在TypeScript中生成数字文字类型的增量版本?

ltskdhd1  于 2023-05-19  发布在  TypeScript
关注(0)|答案(5)|浏览(121)

是否可以从数字类型T得到值为T+1的数字类型Y

type one = 1

type Increment<T extends number> = ???

type two = Increment<one> // 2

P.S.目前,我已经硬编码了递增值的接口,但问题是硬编码的,因此受到限制:

export type IncrementMap = {
    0: 1,
    1: 2,
    2: 3,
piwo6bdm

piwo6bdm1#

这个答案已经过时了,因为TS4.1中引入了递归条件类型。
我会像这样硬编码:

type Increment<N extends number> = [
  1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
  21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,
  38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54, // as far as you need
  ...number[] // bail out with number
][N]

type Zero = 0
type One = Increment<Zero> // 1
type Two = Increment<One>  // 2

type WhoKnows = Increment<12345>; // number

正如我在其他评论中所说的,目前还没有对这种自然递归类型的强大支持。我希望它能得到支持,但它不在那里。在实践中,我发现如果某个东西可以处理长度达到20左右的元组,那就足够好了,但您的经验可能会有所不同。
无论如何,如果有人在这里提出了一个解决方案,它不是硬编码的,但对于任意数字(其中Increment<123456789>将计算为123456790)也能很好地工作和执行,我很有兴趣看看它。也许将来有一天它会成为语言的一部分。

kpbwa7wx

kpbwa7wx2#

稍微不同的解决方案。但是有同样的1000次递归调用的限制

type Arr<N extends number, T extends any[] = []> = T['length'] extends N ? T : Arr<N, [...T, any]>

type Inc<N extends number> = [...Arr<N>, any]['length']

type I20 = Inc<19>
mzsu5hc0

mzsu5hc03#

这个解决方案不是硬编码的,但由于TypeScript的递归限制,它没有用处。当我测试它时,它不能处理大于45的Number2Nat

type SomeNat = [...unknown[]];
type Zero = [];
type Succ<N extends SomeNat> = [...N, unknown];
type Nat2Number<N extends SomeNat> = N["length"];
type Dec<N extends SomeNat> = N extends [unknown, ...infer T] ? T : never;
type Add<N extends SomeNat, M extends SomeNat> = [...N, ...M];
type Sub<N extends SomeNat, M extends SomeNat> = M extends Zero ? N : Sub<Dec<N>, Dec<M>>;
type Number2Nat<I extends number, N extends SomeNat = Zero> = I extends Nat2Number<N> ? N : Number2Nat<I, Succ<N>>;
qlzsbp2j

qlzsbp2j4#

TypeScript仍然不支持数字文字类型的数学运算。在microsoft/TypeScript#26382上有一个长期的开放特性请求。
但是现在,如果必须的话,您可以使用模板文字类型通过将数字文字转换为字符串文字来增加数字文字类型;对该字符串文字执行逐字符decimal加法,并将所得字符串文字转换回数字文字。
坦率地说,对于任何合理的用例来说,这可能是矫枉过正的,但这是可能的,而且不会给编译器带来太多负担。
在下文中,我们将输入限制为表示小于Number.MAX_SAFE_INTEGER的非负整数的数字文字。对于任何其他输入(例如,number本身,负数,分数,非常大的数字),输出将只是number。如果有必要,可以通过仔细查看Number.toString(10)number s的JavaScript编码规则(双精度64位二进制格式IEEE 754)来解决这些问题,但我不打算在这里打扰这些问题。
这就是实现:

type _IncDigit = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];

type _Inc<T extends string> =
  T extends `${infer F extends `${number}` & keyof _IncDigit}${infer R}` ? (
    _Inc<R> extends infer IR extends string ? (
      IR extends `C${infer RR}` ?
      `${F extends "9" ? "C" : ""}${_IncDigit[F]}${RR}` :
      `${F}${IR}`
    ) : never
  ) : "C";

type Increment<T extends number> =
  number extends T ? number :
  `${T}` extends `${string}${"." | "+" | "-" | "e"}${string}` ? number : (
    _Inc<`${T}`> extends infer IT extends string ?
    IT extends `C${infer R}` ? `1${R}` : IT : never
  ) extends `${infer N extends number}` ? N : never;

_IncDigit类型是一个实用程序元组,它编码了如何递增一个数字而不用担心carry;所以_IncDigit[0]1_IncDigit[5]6,一直到_IncDigit[9]0
然后_Inc<T>是基本的递增操作,它假设T是有效数字输入的字符串表示。如果T至少有一个字符,则将其拆分为第一个字符F和字符串R的其余部分,并立即递归确定_Inc<R>,即递增字符串其余部分的结果。这里我们将进位编码为"C"的初始“数字”。如果_Inc<R>的结果以进位标志位开始,则递增F;否则我们就不管了如果F递增并等于9,则我们前置"C",否则不前置。最后,递增空字符串的结果就是进位标志。
最后,Increment<T>负责将字符串转换为数字,将数字转换为字符串,以及验证输入。如果仍然有进位标志,它将被转换为1
让我们测试一下:

type Inc0 = Increment<0> // 1
type Inc5 = Increment<5> // 6
type Inc9 = Increment<9> // 10
type Inc8675309 = Increment<8675309>; // 8675310

type IncFrac = Increment<2.5> // number
type IncNeg = Increment<-2> // number
type IncTinyFrac = Increment<1.0e-4>; // number
type IncHuge = Increment<9.9e99> // number    
type IncNumber = Increment<number>; // number

type Inc3299999999999999 = Increment<3299999999999999>
// type Inc3299999999999999 = 3300000000000000

type CloseToMax = Increment<8999999999999999>
// type CloseToMax = 9000000000000000

type MaxSafeInteger = Increment<9007199254740991>
// type MaxSafeInteger = 9007199254740992

type TooBig = Increment<9007199254740992>
// type TooBig = number

看起来不错
不过,我还是不知道这种方法是否真的适用于任何正常的用例。如果你是为了好玩,那很好。但是,如果你发现自己想要在某个地方为一些生产代码库做这件事,请仔细考虑你实际上是否需要在类型系统中重新实现数学运算。很有可能,一种受限的方法(例如硬编码从0100的所有数字递增的结果等)也将满足您的需求。
Playground链接到代码

ghhaqwfi

ghhaqwfi5#

下面的解决方案没有n > 999的递归限制。唯一的限制是n.toString().length > 1000;这似乎是javascript识别数字的能力的一个极限。一旦超过了这个限制,typescript将把递增的类型显示为number

type IncrementMap = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
type LastDigitMap = {
  10: 0;
  11: 1;
  12: 2;
  13: 3;
  14: 4;
  15: 5;
  16: 6;
  17: 7;
  18: 8;
};

type LastCharacter<T extends string> = T extends `${infer First}${infer Last}`
  ? Last extends ''
    ? First
    : LastCharacter<Last>
  : T;

export type _Increment<
  Number extends string,
  Carry extends 0 | 1 = 0,
  Result extends string = '',
> = Number extends ''
  ? Carry extends 0
    ? Result
    : `${Carry}${Result}`
  : LastCharacter<Number> extends `${infer LastDigit extends number}`
  ? IncrementMap[LastDigit] extends infer Incremented extends number
    ? Number extends `${infer Rest}${LastDigit}`
      ? Incremented extends keyof LastDigitMap
        ? _Increment<Rest, 1, `${LastDigitMap[Incremented]}${Result}`>
        : `${Rest}${Incremented}${Result}`
      : never
    : never
  : never;

type Increment<T extends number> = _Increment<
  `${T}`,
  1
> extends `${infer Result extends number}`
  ? Result
  : never;

type Case1 = Increment<1>;
type Case2 = Increment<9>;
type Case3 = Increment<999>;
type Case4 = Increment<1899999999999999>;

给定解决方案的逻辑类似于将大数求和为字符串。我们从最右边的字符开始并添加一个。
加法是用IncrementMap类型完成的,其中索引是我们试图递增的数字,值是我们想要获得的实际递增数字。
链接到playground

相关问题