这是indexloc提供的服务,不要输入任何密码
Skip to content

2257 - MinusOne (🎥 Video Explanation and Solution) #22797

@dimitropoulos

Description

@dimitropoulos

2257- MinusOne

Considering all the amazing things you can do with TypeScript, this feels like one that should be easy, but it's anything but. You need to create temporary Array accumulators and other messy business. Worth a look on "wow factor" alone.

🎥 Video Explanation

Release Date: 2023-02-16 19:00 UTC

MinusOne

🔢 Code

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

type A1 = MinusOne<1>
type B1 =  0;
type C1 = Expect<Equal<A1, B1>>;

type A2 = MinusOne<55>
type B2 =  54;
type C2 = Expect<Equal<A2, B2>>;

type A3 = MinusOne<3>
type B3 =  2;
type C3 = Expect<Equal<A3, B3>>;

type A4 = MinusOne<100>
type B4 =  99;
type C4 = Expect<Equal<A4, B4>>;

type A5 = MinusOne<1101>
type B5 =  1100;
type C5 = Expect<Equal<A5, B5>>;

type A6 = MinusOne<0>;
type B6 = -1;
type C6 = Expect<Equal<A6, B6>>;

// type A7 = MinusOne<9007199254740992>;
// type B7 = 9007199254740991;
// type C7 = Expect<Equal<A7, B7>>;

// ============= Your Code Here =============
type Tuple<
  N extends number,
  T extends unknown[] = []
> =
  0 extends 1
  ? never
  : T["length"] extends N
    ? T
    : Tuple<N, [...T, unknown]>;

type MinusOne<T extends number> =
  Tuple<T> extends [unknown, ...infer R]
  ? R["length"]
  : -1;

// ============== Alternatives ==============
// credit to @Drincann
type Reverse<S extends string> =
  S extends `${infer Head}${infer Tail}`
  ? `${Reverse<Tail>}${Head}`
  : S
;
type Times<T extends unknown[]> = {
  '0': [],
  '1': [...T],
  '2': [...T, ...T,],
  '3': [...T, ...T, ...T,],
  '4': [...T, ...T, ...T, ...T,]
  '5': [...T, ...T, ...T, ...T, ...T,],
  '6': [...T, ...T, ...T, ...T, ...T, ...T,],
  '7': [...T, ...T, ...T, ...T, ...T, ...T, ...T,],
  '8': [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T,],
  '9': [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T,],
  '10': [...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T, ...T,]
}
type _List<
  N extends string,
  Base extends any[] = [any],
  Acc extends any[] = []
> =
  N extends `${infer Head extends keyof Times<any>}${infer Tail}`
  ? (
    Tail extends ''
      /* to avoid generating tuples with a length of 10000 */
    ? [...Acc, ...Times<Base>[Head]] 
    : _List<
        Tail,
        Times<Base>['10'],
        [...Acc, ...Times<Base>[Head]]
      >
  )
  : never;
type List<N extends number> = _List<Reverse<`${N}`>>;
type MinusOne<
  T extends number,
  Arr extends any[] = List<T>
> =
  Arr extends [...infer Head, unknown]
  ? Head['length']
  : -1
;

type MinusOne<
  T extends number,
  Tuple extends unknown[] = []
> =
  T extends 0
    ? -1
    : [unknown, unknown, ...Tuple]['length'] extends T
      ? [unknown, ...Tuple]['length']
      : [unknown, ...Tuple]['length'] extends T
        ? Tuple['length']
        : MinusOne<T, [unknown, unknown, ...Tuple]>

// credit to @santosmarco-caribou
// THIS ONE PASSES C7
type MinusMap = {
  '0': -1
  '1': 0
  '2': 1
  '3': 2
  '4': 3
  '5': 4
  '6': 5
  '7': 6
  '8': 7
  '9': 8
}
type _GetHeadAndLast<
  T,
  Acc extends string = ''
> =
  T extends `${infer Head}${infer Tail}`
  ? Tail extends ''
    ? [Acc, Head]
    : _GetHeadAndLast<Tail, `${Acc}${Head}`>
  : never;
type GetHead<T> = _GetHeadAndLast<T>[0]
type GetLast<T> = _GetHeadAndLast<T>[1]
type ToNumber<T> =
  T extends `${infer U extends number}`
  ? U
  : never;
type MinusOne<T extends number> =
  `${T}` extends infer _T extends string
  ? _T extends keyof MinusMap
    ? MinusMap[_T]
    : ToNumber<
        GetLast<_T> extends '0'
          ? `${GetHead<_T> extends '1'
            ? ''
            : MinusOne<GetHead<_T>>}9`
          : `${GetHead<_T>}${MinusMap[GetLast<_T>]}`
      >
  : never;

// credit to @gaac510
// THIS ONE PASSES C7
type OneLess = {
  readonly "9": "8";
  readonly "8": "7";
  readonly "7": "6";
  readonly "6": "5";
  readonly "5": "4";
  readonly "4": "3";
  readonly "3": "2";
  readonly "2": "1";
  readonly "1": "0";
};
type MinusOne<
  T extends number | string,
  Digits extends string[] = [],
  Processed extends string[] = [],
  Rejoined extends string = ""
> =
  `${T}` extends `${infer First}${infer Remainder}`
  
  // STAGE 1: split the digits
  ? MinusOne<Remainder, [...Digits, First]>
  
  // STAGE 2: process each digits
  : Digits extends [
      ...infer OtherDigits extends string[],
      infer RightMostDigit
    ]
    ? RightMostDigit extends keyof OneLess
      // When not 0
      ? MinusOne<
          "",
          [],
          [
            ...OtherDigits,
            OneLess[RightMostDigit],
            ...Processed
          ]
        >

      // When 0
      : MinusOne<
          "",
          OtherDigits,
          ["9", ...Processed]
        >
  
  // STAGE 3: Join digits into a string.
  : Processed extends [
      infer First extends string,
      ...infer Remainder extends string[]
    ]
    
    ? MinusOne<"", [], Remainder, `${Rejoined}${First}`>
  
  // STAGE 4: Handle edge cases and return result
  : Rejoined extends "9"
    // When it was 0 turned into 9
    ? -1
    : Rejoined extends `0${infer Answer extends number}`
      // When the left most digit is 0
      ? Answer

      : Rejoined extends `${infer Answer extends number}`
        // Convert to number
        ? Answer
        : never;

// ================== NOPE ==================
// hits recursion error:
//
// Type instantiation is excessively deep
// and possibly infinite.ts(2589)
type Pop<T extends unknown[]> =
  T extends [...infer Head, unknown]
  ? Head
  : never
;
type MinusOne<
  T extends number,
  Acc extends unknown[] = []
> =
  Acc['length'] extends T
  ? Pop<Acc>['length']
  : MinusOne<T, [...Acc, 0]>
;

// fails C6, credit to @elias-prine
type ParseInt<T extends string> =
  T extends `${infer Digit extends number}`
  ? Digit
  : never;
type ReverseString<S extends string> =
  S extends `${infer First}${infer Rest}`
  ? `${ReverseString<Rest>}${First}`
  : '';
type RemoveLeadingZeros<S extends string> =
  S extends '0'
  ? S
  : S extends `${'0'}${infer R}`
    ? RemoveLeadingZeros<R>
    : S;
type InternalMinusOne<S extends string> =
  S extends `${infer Digit extends number}${infer Rest}`
  ? Digit extends 0
    ? `9${InternalMinusOne<Rest>}`
    : `${[9, 0, 1, 2, 3, 4, 5, 6, 7, 8][Digit]}${Rest}`
  : never;
type MinusOne<T extends number> =
  ParseInt<
    RemoveLeadingZeros<
      ReverseString<
        InternalMinusOne<
          ReverseString<`${T}`>
        >
      >
    >
  >;

// fails C6, credit to @matcher-ice
type DigitToArray = {
  "0": [],
  "1": [unknown],
  "2": [unknown, unknown],
  "3": [unknown, unknown, unknown],
  "4": [unknown, unknown, unknown, unknown],
  "5": [unknown, unknown, unknown, unknown, unknown],
  "6": [unknown, unknown, unknown, unknown, unknown, unknown],
  "7": [unknown, unknown, unknown, unknown, unknown, unknown, unknown],
  "8": [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown],
  "9": [unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown]
};
type CreateArrayByLength<
  N extends string,
  R extends unknown[] = []
> =
  N extends `${infer First}${infer Rest}`
  ? First extends keyof DigitToArray
    ? CreateArrayByLength<
        Rest,
        [
          ...R, // 0
          ...R, // 1
          ...R, // 2
          ...R, // 3
          ...R, // 4
          ...R, // 5
          ...R, // 6
          ...R, // 7
          ...R, // 8
          ...R, // 9
          ...DigitToArray[First]]
        >
    : never
  : R
;
type MinusOne<T extends number> =
  CreateArrayByLength<`${T}`> extends
    [unknown, ...infer Rest]
  ? Rest["length"]
  : never
;

➕ More Solutions

For more video solutions to other challenges: see the umbrella list! #21338

Metadata

Metadata

Assignees

No one assigned

    Labels

    2595answerShare answers/solutions to a questionenin English

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions