🌱 ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Type-challenges] 296, 298, 459, 527, 529
    Front-End/TypeScript 2024. 3. 30. 05:29
    🍀 목차
    296 - Permutation
    298 - Length of String
    459 - Flatten
    527 - Append to object
    529 - Absolute

     

     

    296 - Permutation

    // 주어진 유니언 타입을 순열 배열로 바꾸는 Permutation 타입을 구현하세요.
    
    // 예시
    type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type Permutation<T> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Permutation<'A'>, ['A']>>,
      Expect<Equal<Permutation<'A' | 'B' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>,
      Expect<Equal<Permutation<'B' | 'A' | 'C'>, ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']>>,
      Expect<Equal<Permutation<boolean>, [false, true] | [true, false]>>,
      Expect<Equal<Permutation<never>, []>>,
    ]

     

     정답을 보며 공부했다.

     

     순열을 만들 때 일반적으로 재귀 알고리즘을 사용하듯이 여기서도 Permutation 타입을 재귀로 구현한다. 기저조건은 마지막 테스트 케이스처럼 never이라면 빈 배열을 리턴하는 것이다. 순열을 만들 수 없다는 뜻이다.

    그런데 아래로 작성했을 때, Permutation<never>은 true가 아닌 never이 나왔다.

    type Permutation<T> = T extends never ? true : false;
    
    type test = Permutation<never>; // never

     

    아래와 같이 작성해야 생각하던 true가 반환된다.

     

    type Permutation<T> = [T] extends [never] ? true : false;
    
    type test = Permutation<never>; // true

     

    never extends never은 true지만 T extends never은 never로 반환되는 이유는 분산 조건부 타입과 관련이 있다.

    해당 문서를 보면, 조건부 타입이 제네릭 타입에 작용하는 경우, 유니온 타입이 분산형이 된다고 한다. T가 1 | 2 | 3이라면 T extends never ? true : false(1 extends never ? true : false) | (2 extends never ? true : false) | (3 extends never ? true : false)가 되는 것이다. 그리고 이 결괏값을 묶어 다시 유니온 타입으로 반환한다.

     

     never은 유니온 시 빈 유니온으로 간주된다. a | never은 a가 되는 것이다. 조건부 분배할 때는 never을 무시하게 된다. 빈 유니온에 조건을 분배하는 것이 의미가 없기 때문이다. 그렇기 때문에 never이 포함된 조건부 분배에서는 never이 유지될 수 있도록 타입 T를 변경시켜줘야 한다. [T]T[]처럼 작성하면 never 타입이 유지되어 분배가 올바르게 수행된다. 

    [T] extends [never]

     

    마지막 테스트 케이스를 통과하기 위해 기본 틀을 아래와 같이 잡아준다.

    type Permutation<T> = [T] extends [never] ? [] : true;

     

     true부분은 어떻게 작성해야 할까?

    다시 한번, 조건부 타입에 유니온 타입을 주게 되면 분산되어 계산된다.

     

     만들어야 할 로직은 현재 요소를 고정하고 현재 요소를 제외한 나머지 요소들에 대해 재귀 호출 하는 것이다. T extends T 형식으로 작성하게 될 경우, 항상 true기에 결과의 의미는 없지만 각 요소에 대해 로직을 실행할 수 있게 해 준다.

     

     현재 요소를 제외한 나머지 요소를 표현할 때는 Exclude를 사용하면 된다. 

    type Exclude<T,U> = T extends U ? never : T; // 유틸리티 타입 사용해도 된다.

     

     이제 default 값으로 T를 갖는 U를 추가해서 Exclude가 잘 될 수 있도록 한다.

    type Exclude<T,U> = T extends U ? never : T;
    type Permutation<T, U = T> = [T] extends [never] ? [] : U extends U ? [U, ...Permutation<Exclude<T,U>>] : never;

     


    298 - Length of String

    // String#length처럼 동작하는 문자열 리터럴의 길이를 구하세요.
    
    /* _____________ 여기에 코드 입력 _____________ */
    
    type LengthOfString<S extends string> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<LengthOfString<''>, 0>>,
      Expect<Equal<LengthOfString<'kumiko'>, 6>>,
      Expect<Equal<LengthOfString<'reina'>, 5>>,
      Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>,
    ]

     

     배열처럼 length 요소로 접근할 수 있지 않을까 생각했지만 문자열 리터럴 타입은 컴파일 시 상수로 처리되어 동적인 속성 접근을 허용하지 않는다고 한다. 그렇기에 배열로 변환하여 length 속성을 추출할 수 있다.

     

    type LengthOfString<S extends string> = S['length'];

     

    number로 추론된다.
    number로 추론될 뿐이다.

     

    type StringToArray<S> = S extends `${infer first}${infer rest}` ? [first, ... StringToArray<rest>] : [];
    type LengthOfString<S extends string> = StringToArray<S>['length'];

     


    459 - Flatten

    // 주어진 배열을 플랫한 배열 타입으로 바꾸는 Flatten 타입을 구현하세요.
    
    // 예시
    type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type Flatten = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Flatten<[]>, []>>,
      Expect<Equal<Flatten<[1, 2, 3, 4]>, [1, 2, 3, 4]>>,
      Expect<Equal<Flatten<[1, [2]]>, [1, 2]>>,
      Expect<Equal<Flatten<[1, 2, [3, 4], [[[5]]]]>, [1, 2, 3, 4, 5]>>,
      Expect<Equal<Flatten<[{ foo: 'bar', 2: 10 }, 'foobar']>, [{ foo: 'bar', 2: 10 }, 'foobar']>>,
    ]

     

    type Flatten<T> = 
    T extends [] ? [] :
    T extends [infer First, ...infer Rest] ? 
    First extends any[] ? 
    [...Flatten<First>,...Flatten<Rest>]
    : [First, ...Flatten<Rest>]
    : T;

     

    527 - Append to object

    // 주어진 인터페이스에 새로운 필드를 추가한 object 타입을 구현하세요. 
    // 이 타입은 세 개의 인자를 받습니다.
    
    // 예시
    type Test = { id: '1' }
    type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
    /* _____________ 여기에 코드 입력 _____________ */
    
    type AppendToObject<T, U, V> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type test1 = {
      key: 'cat'
      value: 'green'
    }
    
    type testExpect1 = {
      key: 'cat'
      value: 'green'
      home: boolean
    }
    
    type test2 = {
      key: 'dog' | undefined
      value: 'white'
      sun: true
    }
    
    type testExpect2 = {
      key: 'dog' | undefined
      value: 'white'
      sun: true
      home: 1
    }
    
    type test3 = {
      key: 'cow'
      value: 'yellow'
      sun: false
    }
    
    type testExpect3 = {
      key: 'cow'
      value: 'yellow'
      sun: false
      moon: false | undefined
    }
    
    type cases = [
      Expect<Equal<AppendToObject<test1, 'home', boolean>, testExpect1>>,
      Expect<Equal<AppendToObject<test2, 'home', 1>, testExpect2>>,
      Expect<Equal<AppendToObject<test3, 'moon', false | undefined>, testExpect3>>,
    ]

     

     

     기존 객체의 key-value는 아래와 같이 만들 수 있다.

    type AppendToObject<T, U, V> = {
      [key in keyof T] : T[key]
    };

     

     여기서 새로운 U라는 key값에 V라는 value를 넣어야 하므로

     

    기존 key값이 아니라면 T[key]가 아닌 V로 연결해 주는 부분이 있어야 할 테고,

    type AppendToObject<T, U , V> = {
      [key in keyof T] : key extends keyof T ? T[key] : V;
    };

     

    위의 로직은 당연히 현재 T의 key밖에 조회가 되지 않으니 유니온 타입을 사용하여 U도 key가 될 수 있음을 추가한다.

     

    type AppendToObject<T, U , V> = {
      [key in keyof T | U] : key extends keyof T ? T[key] : V;
    };

     

    오류가 발생한다. 타입이 지정되지 않은 U는 string | number | symbol 이 될 수 있는 type keyof T에 할당할 수 없기 때문이다. U를 직접 keyof T의 타입과 동일하게 해 주거나, keyof any를 사용하여 모든 값의 유형을 나타낸다고 할 수 있다.

    type AppendToObject<T, U extends string | number | symbol , V> = {
      [key in keyof T | U] : key extends keyof T ? T[key] : V;
    };
    
    // Or
    
    type AppendToObject<T, U extends keyof any , V> = {
      [key in keyof T | U] : key extends keyof T ? T[key] : V;
    };


    529 - Absolute

    // number, string, 혹은 bigint을 받는 Absolute 타입을 만드세요. 
    // 출력은 양수 문자열이어야 합니다.
    
    // 예시
    type Test = -100
    type Result = Absolute<Test> // expected to be "100"

     

    /* _____________ 여기에 코드 입력 _____________ */
    
    type Absolute<T extends number | string | bigint> = any
    
    /* _____________ 테스트 케이스 _____________ */
    import type { Equal, Expect } from '@type-challenges/utils'
    
    type cases = [
      Expect<Equal<Absolute<0>, '0'>>,
      Expect<Equal<Absolute<-0>, '0'>>,
      Expect<Equal<Absolute<10>, '10'>>,
      Expect<Equal<Absolute<-5>, '5'>>,
      Expect<Equal<Absolute<'0'>, '0'>>,
      Expect<Equal<Absolute<'-0'>, '0'>>,
      Expect<Equal<Absolute<'10'>, '10'>>,
      Expect<Equal<Absolute<'-5'>, '5'>>,
      Expect<Equal<Absolute<-1_000_000n>, '1000000'>>,
      Expect<Equal<Absolute<9_999n>, '9999'>>,
    ]

     

    type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer U}` ? U : `${T}`;

     

     양수라면 유지, 음수라면 양수로 변환하여 출력한다. 문자열로 바꾸는 부분을 유의해야 한다.

     

     

    참고자료

    https://github.com/type-challenges/type-challenges/blob/main/questions/00296-medium-permutation/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00298-medium-length-of-string/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00459-medium-flatten/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00527-medium-append-to-object/README.ko.md

    https://github.com/type-challenges/type-challenges/blob/main/questions/00529-medium-absolute/README.ko.md

    댓글

🍀 Y0ungZ dev blog.
스크롤 버튼