-
[Type-challenges] 296, 298, 459, 527, 529Front-End/TypeScript 2024. 3. 30. 05:29
🍀 목차
296 - Permutation
298 - Length of String
459 - Flatten
527 - Append to object
529 - Absolute296 - 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'];
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}`;
양수라면 유지, 음수라면 양수로 변환하여 출력한다. 문자열로 바꾸는 부분을 유의해야 한다.
참고자료