[TypeScript] Type Challenges #2257 - MinusOne

53 阅读5分钟

题目描述

给定一个正整数作为类型的参数,要求返回的类型是该数字减 1。

例如:

type Zero = MinusOne<1> // 0
type FiftyFour = MinusOne<55> // 54

题解

方案一

// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'

type cases = [
  Expect<Equal<MinusOne<1>, 0>>,
  Expect<Equal<MinusOne<55>, 54>>,
  Expect<Equal<MinusOne<3>, 2>>,
  Expect<Equal<MinusOne<100>, 99>>,
  Expect<Equal<MinusOne<1101>, 1100>>,
  Expect<Equal<MinusOne<0>, -1>>,
  Expect<Equal<MinusOne<9_007_199_254_740_992>, 9_007_199_254_740_991>>,
]


// ============= Your Code Here =============
type ParseInt<T extends string> = 
  T extends `${infer Digit extends number}`
    ? Digit
    : T;

type ReverseStr<T extends string> = 
  T extends `${infer F}${infer R}`
    ? `${ReverseStr<R>}${F}`
    : T;

type RemoveLeadingZero<T extends string> = 
  T extends `0${infer R}`
    ? R extends ""
      ? "0"
      : `${RemoveLeadingZero<R>}`
    : T;

type InnerMinusOne<T extends string> =
  T extends `${infer F extends number}${infer R}`
    ? F extends 0
      ? `9${InnerMinusOne<R>}`
      : `${[9, 0, 1, 2, 3, 4, 5, 6, 7, 8][F]}${R}`
    : T;

type InnerPlusOne<T extends string> = 
  T extends "9"
    ? "01"
    : T extends `${infer F extends number}${infer R}`
      ? F extends 9
        ? `0${InnerPlusOne<R>}`
        : `${[1, 2, 3, 4, 5, 6, 7, 8, 9][F]}${R}`
      : T;

type MinusOne<T extends number> = 
  T extends 0
    ? -1
    : `${T}` extends `-${infer Num}`
      ? ParseInt<`-${RemoveLeadingZero<
          ReverseStr<InnerPlusOne<ReverseStr<`${Num}`>>>
        >}`>
      : ParseInt<RemoveLeadingZero<ReverseStr<InnerMinusOne<ReverseStr<`${T}`>>>>>;

解释

1.ParseInt<T>

  • 将字符串类型的数字转换为数字类型

  • 如果T是一个数字字符串,返回对应的数字类型;否则返回T

2.ReverseStr<T>

  • 将字符串T颠倒

  • 递归地将字符串的每个字符移到末尾,直到字符串为空

3.RemoveLeadingZero<T>

  • 移除字符串T开头的0

  • 如果字符串以0开头,递归地移除开头的0;如果字符串为空,返回0

4.InnerMinusOne<T>

  • 对字符串T的第一个字符减一

  • 如果第一个字符是0,递归地对剩余字符串减一,并在前面加上9

  • 如果第一个字符不是0,返回减一后的字符和剩余字符串

5.InnerPlusOne<T>

  • 对字符串T的第一个字符加一

  • 如果第一个字符是9,递归地对剩余字符串加一,并在前面加上0

  • 如果第一个字符不是9,返回加一后的字符和剩余字符串

6.MinusOne<T>

  • 如果T0,返回-1

  • 如果T是负数,对负数部分加一,然后返回负数

  • 如果T是正数,对正数部分减一,然后返回结果

推导过程

1.MinusOne<1>

  • ReverseStr<'1'> = '1'

  • InnerMinusOne<'1'> = '0'

  • ReverseStr<'0'> = '0'

  • RemoveLeadingZero<'0'> = '0'

  • ParseInt<'0'> = 0

  • 最终结果是0

2.MinusOne<55>

  • ReverseStr<'55'> = '55'

  • InnerMinusOne<'55'> = '45'

  • ReverseStr<'45'> = '54'

  • RemoveLeadingZero<'54'> = '54'

  • ParseInt<'54'> = 54

  • 最终结果是54

3.MinusOne<100>

  • ReverseStr<'100'> = '001'

  • InnerMinusOne<'001'> = '990'

  • ReverseStr<'990'> = '099'

  • RemoveLeadingZero<'099'> = '99'

  • ParseInt<'99'> = 99

  • 最终结果是99

方案二

type ParseInt<T extends string> = 
  T extends `${infer Digit extends number}`
    ? Digit
    : T;

type Expand<T extends number | string> =  
  `${T}` extends `${infer D extends number}${infer V}` 
    ? [D, ...Expand<V>] 
    : [];

type DigitMinusOne<T extends number[]> = 
  T extends [...infer U extends number[], infer V extends number] 
    ? V extends 0 
      ? [...DigitMinusOne<U>, 9]
      : [...U, [9, 0, 1, 2, 3, 4, 5, 6, 7, 8][V]] 
    : [];

type DigitPlusOne<T extends number[]> = 
  T extends [...infer U extends number[], infer V extends number] 
    ? V extends 9 
      ? [...DigitPlusOne<U>, 0]
      : [...U, [1, 2, 3, 4, 5, 6, 7, 8, 9, 0][V]] 
    : [];

type Collapse<T extends number[], N extends number = 0> = 
  T extends [infer D extends number, ...infer V extends number[]] 
    ?
      Collapse<
        V,
        N extends 0 ? D : `${N}${D}` extends `${infer ND extends number}` ? ND : never
      >
    : N;

type MinusOne<T extends number> = 
  T extends 0 
    ? -1 
    : `${T}` extends `-${infer N}`
      ? ParseInt<`-${Collapse<DigitPlusOne<Expand<N>>>}`>
      : Collapse<DigitMinusOne<Expand<T>>>

解释

1.Expand<T>

  • T转换为数字数组

  • 递归地将T的每个字符转换为数字并存储在数组中

2.DigitMinusOne<T>

  • 对数字数组T的最后一个数字减一

  • 如果最后一个数字是0,递归地对前面的数字减一,并在末尾加上9

  • 如果最后一个数字不是0,返回减一后的数字和前面的数字

3.DigitPlusOne<T>

  • 对数字数组T的最后一个数字加一

  • 如果最后一个数字是9,递归地对前面的数字加一,并在末尾加上0

  • 如果最后一个数字不是9,返回加一后的数字和前面的数字

4.Collapse<T>

  • 将数字数组T转换为数字

  • 递归地将数组中的每个数字拼接成一个字符串,并转换为数字

5.MinusOne<T>

  • 如果T0,返回-1

  • 如果T是负数,对负数部分加一,然后返回负数

  • 如果T是正数,对正数部分减一,然后返回结果

推导过程

1.MinusOne<1>

  • Expand<1> = [1]

  • DigitMinusOne<[1]> = [0]

  • Collapse<[0]> = 0

  • 最终结果是0

2.MinusOne<55>

  • Expand<55> = [5, 5]

  • DigitMinusOne<[5, 5]> = [5, 4]

  • Collapse<[5, 4]> = 54

  • 最终结果是54

3.MinusOne<100>

  • Expand<100> = [1, 0, 0]

  • DigitMinusOne<[1, 0, 0]> = [0, 9, 9]

  • Collapse<[0, 9, 9]> = 99

  • 最终结果是99

方案三

type Expand<T extends number | string> =  
  `${T}` extends `${infer D extends number}${infer V}` 
    ? [D, ...Expand<V>] 
    : [];
  
type DigitMinusOne<T extends number[]> = 
  T extends [...infer U extends number[], infer V extends number] 
    ? V extends 0 
      ? [...DigitMinusOne<U>, 9]
      : [...U, [9, 0, 1, 2, 3, 4, 5, 6, 7, 8][V]] 
    : [];

type DigitPlusOne<T extends number[]> = 
  T extends [...infer U extends number[], infer V extends number] 
    ? V extends 9 
      ? [...DigitPlusOne<U>, 0] 
      : [...U, [1, 2, 3, 4, 5, 6, 7, 8, 9][V]] 
    : [1];

type Collapse<T extends number[], N extends number = 0> = 
  T extends [infer D extends number, ...infer V extends number[]] 
    ?
      Collapse<
        V,
        N extends 0 ? D : `${N}${D}` extends `${infer ND extends number}` ? ND : never
      >
    : N;
  
type DigitStep<T extends number[], IsPos extends boolean> = 
  IsPos extends true 
    ? DigitMinusOne<T> 
    : DigitPlusOne<T>;
    
type IsPositive<T extends number> = `${T}` extends `-${number}` | '0' ? false : true;

type Unsigned<T extends number> = `${T}` extends `-${infer N extends number}` ? N : T;

type Signed<T extends number, IsPos extends boolean> = 
  IsPos extends true ? T : `-${T}` extends `${infer N extends number}` ? N : never;

type MinusOneSigned<T extends number, IsPos extends boolean> = 
  Signed<Collapse<DigitStep<Expand<T>, IsPos>>, IsPos>;

type MinusOne<T extends number> = MinusOneSigned<Unsigned<T>, IsPositive<T>>;

解释

1.Expand<T>

  • T转换为数字数组

  • 递归地将T的每个字符转换为数字并存储在数组中

2.DigitMinusOne<T>

  • 对数字数组T的最后一个数字减一

  • 如果最后一个数字是0,递归地对前面的数字减一,并在末尾加上9

  • 如果最后一个数字不是0,返回减一后的数字和前面的数字

3.DigitPlusOne<T>

  • 对数字数组T的最后一个数字加一

  • 如果最后一个数字是9,递归地对前面的数字加一,并在末尾加上0

  • 如果最后一个数字不是9,返回加一后的数字和前面的数字

4.Collapse<T>

  • 将数字数组T转换为数字

  • 递归地将数组中的每个数字拼接成一个字符串,并转换为数字

5.DigitStep<T, IsPos>

  • 根据IsPos的值,选择对数字数组T进行减一或加一操作.DigitStep<T, IsPos>

6.IsPositive<T>

  • 检查数字T是否为正数

  • 如果T是负数或0,返回false;否则返回true

7.Unsigned<T>

  • 如果T是负数,返回其绝对值;否则返回T

8.Signed<T, IsPos>

  • 根据IsPos的值,返回T的正数或负数形式

9.MinusOneSigned<T, IsPos>

  • 对数字T进行减一操作,并根据IsPos的值返回正数或负数

10.MinusOne<T>

  • 对数字T进行减一操作

推导过程

1.MinusOne<1>

  • Unsigned<1> = 1

  • IsPositive<1> = true

  • Expand<1> = [1]

  • DigitStep<[1], true> = DigitMinusOne<[1]> = [0]

  • Collapse<[0]> = 0

  • Signed<0, true> = 0

  • 最终结果是0

2.MinusOne<55>

  • Unsigned<55> = 55

  • IsPositive<55> = true

  • Expand<55> = [5, 5]

  • DigitStep<[5, 5], true> = DigitMinusOne<[5, 5]> = [5, 4]

  • Collapse<[5, 4]> = 54

  • Signed<54, true> = 54

  • 最终结果是54

3.MinusOne<100>

  • Unsigned<100> = 100

  • IsPositive<100> = true

  • Expand<100> = [1, 0, 0]

  • DigitStep<[1, 0, 0], true> = DigitMinusOne<[1, 0, 0]> = [0, 9, 9]

  • Collapse<[0, 9, 9]> = 99

  • Signed<99, true> = 99

  • 最终结果是99