iamkanguk.dev

[타입챌린지-MEDIUM] 5153: IndexOf 본문

Type Challenge

[타입챌린지-MEDIUM] 5153: IndexOf

iamkanguk 2024. 4. 9. 18:57

문제

https://github.com/iamkanguk97/type-challenges/blob/main/questions/05153-medium-indexof/README.md

 

type-challenges/questions/05153-medium-indexof/README.md at main · iamkanguk97/type-challenges

Collection of TypeScript type challenges with online judge - iamkanguk97/type-challenges

github.com

 

풀이

/* _____________ Your Code Here _____________ */
/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type IndexOf<T, U, L extends unknown[] = []>
  = T extends [infer A, ...infer B] 
    ? Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>
    : -1

type cases = [
  Expect<Equal<IndexOf<[1, 2, 3], 2>, 1>>,
  Expect<Equal<IndexOf<[2, 6, 3, 8, 4, 1, 7, 3, 9], 3>, 2>>,
  Expect<Equal<IndexOf<[0, 0, 0], 2>, -1>>,
  Expect<Equal<IndexOf<[string, 1, number, 'a'], number>, 2>>,
  Expect<Equal<IndexOf<[string, 1, number, 'a', any], any>, 4>>,
  Expect<Equal<IndexOf<[string, 'a'], 'a'>, 1>>,
  Expect<Equal<IndexOf<[any, 1], 1>, 1>>,
]

 

풀이 해설

아예 생각지도 못했다. 진짜 내가 많이 부족하다고 느꼈다.

필자는 배열을 순회해야겠다 싶었는데 전혀 다른 풀이법이었다.

당분간은 지금까지 풀었던 타입 챌린지를 복습하면서 타입스크립트 공식문서도 틈틈이 읽어봐야 할 것 같다.

 

다른 분들의 풀이를 보면 이해가 정말 쉽게 된다. 항상 느끼는데 왜 문제를 보면 이런 풀이법이 떠오르지 않을까 싶다 ㅠ 많은 노력을 해야겠다.

  • type IndexOf<T, U, L extends unknown[] = []>: 문제에서는 T와 U만 있었을 것이다. 하지만 우리는 제네릭 L을 추가할 것인데, 타입은 배열 타입이고 Default는 빈 배열이다.
  • T extends [infer A, ...infer B] ? <> : -1: 일단 크게 한번 보자. T 배열을 A 원소 1개와, 나머지 원소들을 합친 B 배열로 나눌 수 있다면 <>를 실행하고, 그렇지 않으면 -1을 반환한다는 것이다. -1을 반환한다는 것은 원소를 못찾았을 경우를 의미한다. 해당 설명만 보면 으이? 할 수 있는데 아래 설명도 같이 보면 이해가 될 것이라고 생각한다.
  • (위의 괄호) Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>: 제일 핵심 부분이다!!
    • Equal<A, U>: 문제에서 제공하는 util 라이브러리를 사용하였다. 사용하지 않을 것이라면 직접 Equal 타입을 구현해도 무방하다. 여기서 A는 위에서 배열을 두개로 나눌 때 첫 원소를 의미한다. 그리고 U는 문제에서 찾으려고 하는 값이다.
    • extends true ? L['length'] : IndexOf<B, U, [...L, A]>
      • Equal의 결과가 true이면 L의 길이를 반환하고, 그렇지 않으면 IndexOf를 재귀적으로 실행한다는 것이다.
      • 이 때, IndexOf에 B, U, [...L, A]를 넣는데 한번 살펴보자.
        • B는 위에서 나눈 배열을 의미한다.
        • U는 문제에서 찾으려고 하는 값 그대로 넣으면 된다.
        • [...L, A] 는 비교한 값들을 누적한다는 의미이다.

 

일단 코드적으로만 해석해서 설명해봤는데 아마 잘 이해가 될 지는 모르겠다. 그런데 머리속으로 생각하면 그렇게 어렵지 않다.

딱 한마디로만 정의하면 "L에다가 기전에 비교한 값들 A를 누적하는 것이다. 현재값인 A가 U랑 일치하면 L 배열의 길이가 A의 인덱스이기 때문에 그대로 반환하면 된다."

 

예시를 하나 들어서 설명해보겠다. 테스트 케이스는 2번째 케이스를 사용하겠다.

 

<First Step>

- T = [2, 6, 3, 8, 4, 1, 7, 3, 9] // U = 3 // L = []

- T extends [infer A, ...infer B] >>> A: 2, B: [6, 3, 8, 4, 1, 7, 3, 9] >>> 나눌 수 있기 때문에 <> 진행 (위에 <> 에요!)

- Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>

   -- Equal<A, U>: A(2)와 U(3)은 같지 않기 때문에 False. 따라서 조건에 의해 IndexOf 부분을 실행한다.

   -- IndexOf<B, U, [...L, A]>: B = [6, 3, 8, 4, 1, 7, 3, 9] // U = 3 // [...L, A] = [2]

 

<Second Step>

- T = [6, 3, 8, 4, 1, 7, 3, 9] // U = 3 // L = [2]

- T extends [infer A, ...infer B] >>> A: 6, B: [3, 8, 4, 1, 7, 3, 9] >>> 나눌 수 있기 때문에 <> 진행 (위에 <> 에요!)

- Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>

   -- Equal<A, U>: A(6)와 U(3)은 같지 않기 때문에 False. 따라서 조건에 의해 IndexOf 부분을 실행한다.

   -- IndexOf<B, U, [...L, A]>: B = [3, 8, 4, 1, 7, 3, 9] // U = 3 // [...L, A] = [2, 6]

 

<Third Step>

- T = [3, 8, 4, 1, 7, 3, 9] // U = 3 // L = [2, 6]

- T extends [infer A, ...infer B] >>> A: 3, B: [8, 4, 1, 7, 3, 9] >>> 나눌 수 있기 때문에 <> 진행 (위에 <> 에요!)

- Equal<A, U> extends true ? L['length'] : IndexOf<B, U, [...L, A]>

   -- Equal<A, U>: A(3)와 U(3)은 서로 같기 때문에 L['length']를 반환한다!

   -- 따라서 현재 L['length']는 2이기 때문에 2가 반환되고, 실제로 3은 index가 2인 것을 확인할 수 있다.


<2023-04-10 추가>

5317번 LastIndexOf 라는 문제가 있는데 비슷하게 풀어볼 수 있으니까 한번 풀어보길 바란다!