Skip to content

Instantly share code, notes, and snippets.

@vezaynk
Created July 12, 2022 06:24
Show Gist options
  • Save vezaynk/310b14006af4531bb827e84111a3db15 to your computer and use it in GitHub Desktop.
Save vezaynk/310b14006af4531bb827e84111a3db15 to your computer and use it in GitHub Desktop.
interface StringDigitToNumber {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
}
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0';
type NumericStringAsDigitArray<T extends `${Digit}${string}`> =
T extends `${infer L extends Digit}${infer F extends string}` ?
F extends `${Digit}${string}` ? [StringDigitToNumber[L], ...NumericStringAsDigitArray<F>] : [StringDigitToNumber[L]] : never;
type Flatten<T extends any[]> =
T extends [infer First, ...infer Rest]
? First extends any[]
? Flatten<[...First, ...Rest]>
: [First, ...Flatten<Rest>]
: []
type OneTwoThree = NumericStringAsDigitArray<"123">;
// ^? type OneTwoThree = [1, 2, 3]
@vezaynk
Copy link
Author

vezaynk commented Jul 12, 2022

Interestingly numbers have worse inference than strings. This forces us to use the StringDigitToNumber[L] bit. Try removing it and see what happens!

Spoiler: It's [Digit, Digit, Digit] because numbers don't get inferred from string literals.

@vezaynk
Copy link
Author

vezaynk commented Jul 12, 2022

With that said, you basically want to avoid generating numbers for as long as you can, depending on what you're doing. The moment you replace strings with numbers, it becomes tricky.

interface StringDigitToNumber {
  '0': 0,
  '1': 1,
  '2': 2,
  '3': 3,
  '4': 4,
  '5': 5,
  '6': 6,
  '7': 7,
  '8': 8,
  '9': 9,
}
interface PreviousNumber {
  '0': '9',
  '1': '0',
  '2': '1',
  '3': '2',
  '4': '3',
  '5': '4',
  '6': '5',
  '7': '6',
  '8': '7',
  '9': '8',
}
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0';


type NumericStringAsDigitArray<T extends `${Digit}${string}`> = 
T extends `${infer L extends Digit}${infer F extends string}` ? 
    F extends `${Digit}${string}` ? [L, ...NumericStringAsDigitArray<F>] : [L] : never;

type OneTwoThree = NumericStringAsDigitArray<"123">;
//    ^?

type MinusOneArray<T extends Digit[]> = CleanLeadingZero<
T extends [...infer R extends Digit[], infer L extends Digit] ? 
    L extends "0" ? 
        [...MinusOneArray<R>, PreviousNumber[L]]
    : [...R, PreviousNumber[L]]
: never>;

type CleanLeadingZero<T extends Digit[]> = T extends ['0', ...infer R extends Digit[]] ? R : T;
type Test1 = MinusOneArray<["1", "2", "3"]>;
//    ^? ["1","2","2"]
type Test2 = MinusOneArray<["1", "0", "0"]>;
//    ^? ["9", "9"]

@vezaynk
Copy link
Author

vezaynk commented Jul 12, 2022

Going to bed.

Using others' solutions, we can join and convert the result to a number. A function MinusOne<> generic is waiting:

interface StringDigitToNumber {
  '0': 0,
  '1': 1,
  '2': 2,
  '3': 3,
  '4': 4,
  '5': 5,
  '6': 6,
  '7': 7,
  '8': 8,
  '9': 9,
}
interface PreviousNumber {
  '0': '9',
  '1': '0',
  '2': '1',
  '3': '2',
  '4': '3',
  '5': '4',
  '6': '5',
  '7': '6',
  '8': '7',
  '9': '8',
}
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '0';


type NumericStringAsDigitArray<T extends `${Digit}${string}`> = 
T extends `${infer L extends Digit}${infer F extends string}` ? 
    F extends `${Digit}${string}` ? [L, ...NumericStringAsDigitArray<F>] : [L] : never;

type OneTwoThree = NumericStringAsDigitArray<"123">;
//    ^?

type MinusOneArray<T extends Digit[]> = CleanLeadingZero<
T extends [...infer R extends Digit[], infer L extends Digit] ? 
    L extends "0" ? 
        [...MinusOneArray<R>, PreviousNumber[L]]
    : [...R, PreviousNumber[L]]
: never>;

type CleanLeadingZero<T extends Digit[]> = T extends ['0', ...infer R extends Digit[]] ? R : T;
type Test1 = MinusOneArray<["1", "2", "3"]>;
//    ^?
type Test2 = MinusOneArray<["1", "0", "0"]>;
//    ^?
type Join<
  T extends any[], 
  U extends string | number,
  R extends string = ''
> = 
  T extends [infer F,...infer L]?
    L['length'] extends 0?
      `${R extends ''?'':`${R}${U}`}${F&string}`
      :Join<L,U,`${R extends ''?'':`${R}${U}`}${F&string}`>
  :R

  type Test3 = Join<Test2, ''>;

  type Mul10<I extends ReadonlyArray<any> = []> = [...I, ...I, ...I, ...I, ...I, ...I, ...I, ...I, ...I, ...I]

type Dec2Arr = {
  '0': [],
  '1': [1],
  '2': [1, 1],
  '3': [1, 1, 1],
  '4': [1, 1, 1, 1],
  '5': [1, 1, 1, 1, 1],
  '6': [1, 1, 1, 1, 1, 1],
  '7': [1, 1, 1, 1, 1, 1, 1],
  '8': [1, 1, 1, 1, 1, 1, 1, 1],
  '9': [1, 1, 1, 1, 1, 1, 1, 1, 1],
}

type DecChar = keyof Dec2Arr

type Str2Arr<S extends string, Remain extends ReadonlyArray<any> = []> =
  S extends DecChar ? [...Mul10<Remain>, ...Dec2Arr[S]] :
  S extends `${infer Leading}${infer Trailing}` ? (
    Leading extends DecChar
    ? Str2Arr<Trailing, [...Mul10<Remain>, ...Dec2Arr[Leading]]> : []
  ) : []

type ToNumber<S extends string> = Str2Arr<S>['length']

type result = ToNumber<Join<MinusOneArray<NumericStringAsDigitArray<'10000'>>,''>>; // 9999

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment