Open43

type challenges【上級編】

yutake27yutake27

Simple Vue

何も思いつかないくらい難しかった。

分かりやすい解説があった
https://zenn.dev/link/comments/048f5a51027735

答えは↓のようになる

// computedの型が関数のためそのままアクセスすると関数型が帰ってきてしまうので、関数の返り値の型が返るようにする
type GetComputed<C> = C extends Record<string, (...args: any[]) => any>
  ? { [key in keyof C]: ReturnType<C[key]> }
  : never

declare function SimpleVue<D, C, M>(options: {
  data: (this: {}) => D, // thisにアクセスできないようにする
  computed: C & ThisType<D & C>,
  methods: M & ThisType<D & GetComputed<C> & M>,
}): any

またいつかチャレンジしたい

yutake27yutake27

Currying 1

カリー化する

カリー化とは、複数の引数を取る関数を、それぞれの 1 つの引数を取る関数の列に変換するテクニックです。

解答

関数の引数と返り値をinferで推定して、そいつをもとにカリー化する_currying型を作成した。
引数が空の時に() => ReturnValueをすぐに返す必要がある。

type _currying<Args extends unknown[], Ret> = Args extends [infer Head, ...infer Rest]
  ? (arg0: Head) => _currying<Rest, Ret>
  : Ret

declare function Currying<T>(fn: T): T extends (...args: infer U) => infer V
  ? U extends []
    ? () => V
    : _currying<U, V>
  : never
yutake27yutake27

Union To Intersection

UnionからIntersectionに変換

type I = Union2Intersection<'foo' | 42 | true> // expected to be 'foo' & 42 & true

自分の解答(不正解)

unionだからdistributionを使うんだろうなと思って↓のコードを書いてみたがダメだった。

type UnionToIntersection<U, T = U> = U extends U ? [T] extends [U] ? U : U & UnionToIntersection<Exclude<T, U>> : never

これだとUnionToIntersection<'a' | 'b'> → 'a' & 'b' | 'b' & 'a' → neverになる

解答

type UnionToIntersection<U> = (U extends infer R ? (x: R) => any : never) extends (x: infer V) => any ? V : never

解説

  1. (U extends infer R ? (x: R) => any : never)でunion distributionを発生
  • 例えば'a' | 'b'の場合(x: 'a') => any | (x: 'b') => anyが生成される
  1. (x: R) => any extends (x: infer V) => anyで引数の型を推論
  • ここで引数推論する引数は(x: 'a') => any | (x: 'b') => anyの引数。
  • (x: 'a') => any | (x: 'b') => anyはaも受け取れてbも受け取ることができる関数。つまり(x: 'a' & 'b') => anyと同義。
  • そのため最終的に(x: 'a') => any | (x: 'b') => anyの引数の型の推論結果は'a' & 'b'になる。
  • 以上でintersectionがとれる

参考

https://github.com/type-challenges/type-challenges/issues/775#issue-794682962

https://qiita.com/suin/items/93eb9c328ee404fdfabc#comment-5218b3e9d13d93dfc98f

yutake27yutake27

Get Required

optional propertyを除外

解答

Propertyを必須にした時との差を見て、差分があったらoptional, なかったらoptionalではないと判定
(判定の際はRequiredを使う。自前実装で-?をやった時はダメだった。)

type GetRequired<T> = { [k in keyof T as T[k] extends Required<T>[k] ? k : never]: T[k] };
yutake27yutake27

Get Optional

今度はoptionol propetyのみを抽出

解答

GetRequiredの反対をすれば良い

type GetOptional<T> = { [k in keyof T as T[k] extends Required<T>[k] ? never : k]: T[k] };
yutake27yutake27

Required Keys

オブジェクトから必須プロパティのキーをunionで返す。

解答

Get Requiredと同じようにオブジェクトからRequiredプロパティだけのオブジェクトを取得して、そのキーを取得

type RequiredKeys<T> = keyof {
  [k in keyof T as T[k] extends Required<T>[k] ? k : never]: any
}
yutake27yutake27

Optional Keys

オブジェクトから任意プロパティのキーをunionで返す。

解答

type OptionalKeys<T> = keyof {
  [k in keyof T as T[k] extends Required<T>[k] ? never: k]: any
}
yutake27yutake27

Capitalize Words

各文字の先頭を大文字に変換

解答

まず↓のコードを書いてみた

type EndOfWords = ' ' | '.' | ','
type CapitalizeWords<S extends string> = S extends `${infer Head}${EndOfWords}${infer Rest}`
  ? `${Capitalize<Head>} ${CapitalizeWords<Rest>}`
  : Capitalize<S>

これだとhello world,hogeのように、文字列中に区切り文字が複数ある場合にどちらも区切り方の組み合わせの数だけdistributionが発生してしまう。

なので区切り文字をunionで管理するのではなく、リストで管理するようにして、区切り文字1文字ずつ処理を行うようにした。↓

type EndOfWordsList = [' ', '.', ',', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '{', '}', '|', '🤣']
type CapitalizeWords<S extends string, WordsList extends string[] = EndOfWordsList> = WordsList extends [infer Word extends string, ...infer RestWords extends string[]]
  ? S extends `${infer Head}${Word}${infer Rest}`
  ? `${Capitalize<Head>}${Word}${CapitalizeWords<Rest, WordsList>}`
  : CapitalizeWords<S, RestWords>
  : Capitalize<S>
yutake27yutake27

C-printf Parser

C言語のprintf関数likeな型を実装する。

条件として↓の型が与えられる。

type ControlsMap = {
  c: 'char'
  s: 'string'
  d: 'dec'
  o: 'oct'
  h: 'hex'
  f: 'float'
  p: 'pointer'
}

この型を用いて文字列中のフォーマットプレースホルダを抽出する。
例えば"The result is %d.",という文字列が与えられた場合は['dec']を返す。

解答

${infer Head}%${infer Next}${infer Tail}で%の次の文字をparseすることができた。
parseした文字がControlsMapに入っているか確認して、入っていたらmapの値を取り出す。

type ParsePrintFormat<S extends string, Ret extends string[] = []> = S extends `${infer Head}%${infer Next}${infer Tail}`
  ? Next extends keyof ControlsMap
    ? ParsePrintFormat<`${Head}${Tail}`, [...Ret, ControlsMap[Next]]>
    : ParsePrintFormat<`${Head}${Tail}`, Ret>
  : Ret
yutake27yutake27

Vue Basic Props

Vueのpropsを定義する

解答

↓のようにPropsを変換する型を定義したところクラスインスタンスだけ通らず、直し方が分からなかったのでgive upした。

type ConvertProps<P extends object> = {
  [k in keyof P]: P[k] extends { type: infer T }
    ? T extends any[]
      ? T[number] extends (...args: any) => any
        ? ReturnType<T[number]>
        : never
      : T extends (...args: any) => any
        ? ReturnType<T>
        : T
    : P[k] extends (...args: any) => any
      ? ReturnType<P[k]>
      : any
}

type GetComputed<C> = C extends Record<string, (...args: any[]) => any>
  ? { [k in keyof C]: ReturnType<C[k]> }
  : never

declare function VueBasicProps<P extends object, D, C, M>(options: {
  props: P,
  data: (this: ConvertProps<P>) => D,
  computed: C & ThisType<D & C>,
  methods: M & ThisType<ConvertProps<P> & D & GetComputed<C> & M>,
}): any

正解

instanceのtypeを取得するInstanceTypeという型があるのでそれを使うとtypeof ClassAにならずに済む

type ConvertProps<P extends object> = {
  [k in keyof P]: P[k] extends { type: infer T }
    ? T extends any[]
      ? T[number] extends (...args: any) => any
        ? ReturnType<T[number]>
        : never
      : T extends new (...args: any) => any
        ? T extends typeof String | typeof Boolean | typeof Number
          ? ReturnType<T>
          : InstanceType<T>
        : T
    : P[k] extends (...args: any) => any
      ? ReturnType<P[k]>
      : any
}

type GetComputed<C> = C extends Record<string, (...args: any[]) => any>
  ? { [k in keyof C]: ReturnType<C[k]> }
  : never

declare function VueBasicProps<P extends object, D, C, M>(options: {
  props: P,
  data: (this: ConvertProps<P>) => D,
  computed: C & ThisType<D & C>,
  methods: M & ThisType<ConvertProps<P> & D & GetComputed<C> & M>,
}): any
yutake27yutake27

Typed Get

lodashのgetを実装

type Data = {
  foo: {
    bar: {
      value: 'foobar';
      count: 6;
    };
    included: true;
  };
  hello: 'world';
};
type A = Get<Data, 'hello'>; // 'world'
type B = Get<Data, 'foo.bar.count'>; // 6
type C = Get<Data, 'foo.bar'>; // { value: 'foobar', count: 6 }

解答

${infer Head}.${infer Rest}.前を抜き出し、それがkeyof TをextendsしていたらTから値を取り出す という操作を繰り返すことで取得できた。

type Get<T extends object, K extends string> = K extends keyof T
  ? T[K]
  : K extends `${infer Head}.${infer Rest}`
    ? Head extends keyof T
      ? T[Head] extends object
        ? Get<T[Head], Rest>
        : never
      : never
    : never
yutake27yutake27

String to Number

文字列から数値に変換

解答

次のような手順による変換を考えた

  1. 文字列から1文字目を取り出す
  2. その文字を数値に変換(文字と数値のMapを用いる)
  3. 数値のままだと扱いづらいので、数値をその数値分の長さを持った配列に変換して保持しておく
  4. 2文字目を取り出して同じように数値に変換→配列に変換
  5. 前の文字から生成された配列は一つ上の位のものなので、配列長を10倍して今の文字から生成した配列と結合
  6. この操作を文字列全て見終わるまで続け、最後に最終的な配列長を返す

上記手順を実装したところ通った。ただし再帰の上限により9999までしか変換できない。

type NumberMap = {
  '0': 0,
  '1': 1,
  '2': 2,
  '3': 3,
  '4': 4,
  '5': 5,
  '6': 6,
  '7': 7,
  '8': 8,
  '9': 9,
}

type ToList<N extends number, Ret extends any[] = []> = Ret['length'] extends N
  ? Ret
  : ToList<N, [...Ret, any]>

type TenTimes<T extends any[], Count extends any[] = [], Ret extends any[] = []> = Count['length'] extends 10
  ? Ret
  : TenTimes<T, [...Count, any], [...Ret, ...T]>

type ToNumber<S extends string, Ret extends any[] = []> = S extends `${infer Head}${infer Rest}`
  ? Head extends keyof NumberMap
    ? ToNumber<Rest, [...TenTimes<Ret>, ...ToList<NumberMap[Head]>]>
    : never
  : Ret['length']

他の人の解答

すごいと思った解答が↓

type ToNumber<S extends string, T extends any[] = []> = S extends `${T["length"]}` 
  ? T["length"]
  : ToNumber<S, [...T, any]>

ものすごくシンプル。
ただ再帰の上限で44までしか計算できないらしい。

yutake27yutake27

Tuple Filter

Tupleから指定された型をフィルタリング

解答

除外する型にunionが与えられた場合にうまく対処できず不正解だった。union distributionを無駄にゴニョゴニョやってしまった。

type IsNever<T> = T[] extends never[] ? true : false
type _EqualSome<T, U> = IsNever<U> extends false
  ? U extends U
    ? Equal<T, U>
    : never
  : Equal<T, U>

type EqualSome<T, U, E = _EqualSome<T, U>> = Equal<E, boolean> extends true
  ? true
  : E

type q = EqualSome<undefined, undefined | null>
type w = EqualSome<never, never | null | undefined> // falseになってしまう
type t = _EqualSome<never, never | null | undefined> // falseになってしまう
type r = EqualSome<null, never | null | undefined>

type FilterOut<T extends any[], F, Ret extends any[] = []> = T extends [infer Head, ...infer Rest]
  ? EqualSome<Head, F> extends true
    ? FilterOut<Rest, F, Ret>
    : FilterOut<Rest, F, [...Ret, Head]>
  : Ret

正解

Equalとか使わずに普通にextendsでよかったらしい。ただしdistributionが発生しないようにするために[]で囲む。

type FilterOut<T extends any[], F> 
  = T extends [infer R, ...infer Rest]
      ? [R] extends [F]
        ? FilterOut<Rest, F>
        : [R, ...FilterOut<Rest, F>]
      : []
yutake27yutake27

Tuple to Enum Object

TupleからEnum likeなobjectを生成

解答

TupleにindexをつけるためのKeyWithIndexという型を作成した。
indexを付与する場合にはこの型を用いることでいけた。

type AddIndexToList<T extends readonly string[], Ret extends [string, number][] = []> = T extends readonly [...infer Rest extends string[], infer Tail extends string]
  ? AddIndexToList<Rest, [[Tail, Rest['length']], ...Ret]>
  : Ret

type KeyWithIndex<T extends readonly string[], U extends [string, number][] = AddIndexToList<T>> = {
  [k in U[number] as k[0]]: k[1]
}

type Enum<T extends readonly string[], N extends boolean = false, U extends object = KeyWithIndex<T>> = {
  readonly [k in T[number] as Capitalize<k>]: N extends true ? k extends keyof U ? U[k] : never : k
}
yutake27yutake27

printf

  Expect<Equal<Format<'abc'>, string>>,
  Expect<Equal<Format<'a%sbc'>, (s1: string) => string>>,
  Expect<Equal<Format<'a%dbc'>, (d1: number) => string>>,
  Expect<Equal<Format<'a%%dbc'>, string>>,
  Expect<Equal<Format<'a%%%dbc'>, (d1: number) => string>>,
  Expect<Equal<Format<'a%dbc%s'>, (d1: number) => (s1: string) => string>>,

こんな感じに変換する型を作成

解答

割とシンプルに%の次の文字がsかdだったらstringかnumberを引数に持たせて、その次以降の文字を再帰的に処理した結果を関数の返り値にすればいけた

type Format<T extends string> = T extends `${infer prefix}%${infer C}${infer suffix}`
  ? C extends 's'
    ? (s1: string) => Format<suffix>
    : C extends 'd'
      ? (d1: number) => Format<suffix>
      : Format<suffix>
  : string
yutake27yutake27

Length of string 2

文字列の長さを取得。再帰上限に引っかからないように工夫をする必要があるらしい。

解答

type LengthOfString<S extends string, A extends any[] = []> = S extends `${any}${infer Rest}`
  ? LengthOfString<Rest, [...A, any]>
  : A['length']

特に工夫せずともテストケースは通ってしまった。

工夫するとしたら↓のように10文字を1回の再帰で取得か

type LengthOfString<S extends string, A extends any[] = []> = S extends `${any}${any}${any}${any}${any}${any}${any}${any}${any}${any}${infer Rest}`
  ? LengthOfString<Rest, [...A, any, any, any, any, any, any, any, any, any, any]>
  : S extends `${any}${infer Rest}`
    ? LengthOfString<Rest, [...A, any]>
    : A['length']
yutake27yutake27

Union To Tuple

UnionからTupleに変換

解答

難しかった、、、思いつかなかった

解説

Union to intersectionと似たような考えで行けるらしい。
https://github.com/type-challenges/type-challenges/issues/737 の解答に詳しい解説が載っていた。

// https://github.com/type-challenges/type-challenges/issues/737
/**
 * UnionToIntersection<{ foo: string } | { bar: string }> =
 *  { foo: string } & { bar: string }.
 */
type UnionToIntersection<U> = (
  U extends unknown ? (arg: U) => 0 : never
) extends (arg: infer I) => 0
  ? I
  : never;

/**
 * LastInUnion<1 | 2> = 2.
 */
type LastInUnion<U> = UnionToIntersection<
  U extends unknown ? (x: U) => 0 : never
> extends (x: infer L) => 0
  ? L
  : never;

/**
 * UnionToTuple<1 | 2> = [1, 2].
 */
type UnionToTuple<U, Last = LastInUnion<U>> = [U] extends [never]
  ? []
  : [...UnionToTuple<Exclude<U, Last>>, Last];
  1. UnionをUnionを引数として持った関数のintersectionに変換
  2. 1.の後、関数型を用いて関数のintersectionから最後の要素だけを取る(これでunionから最後の要素を取得することが可能)
  3. 1,2の操作を繰り返して1つずつ要素を取得する

詳しくはgithubの解説参照

yutake27yutake27

String join

join関数を実装

解答

type Join<D extends string, P extends string[], Ret extends string = ""> = P extends [infer Head extends string, ...infer Rest extends string[]]
  ? Ret extends ""
    ? Join<D, Rest, Head>
    : Join<D, Rest, `${Ret}${D}${Head}`>
  : Ret

declare function join<D extends string>(delimiter: D): <P extends string[]>(...parts: P) => Join<D, P>

初め

declare function join<D extends string, P extends string[]>(delimiter: D): (...parts: P) => Join<D, P>

と書いていて、Pの値が取得できなかった。

書く場所を変えたら通った。

yutake27yutake27

Deep Pick

↓のように再帰的にpickを行う


type obj = {
  name: 'hoge', 
  age: 20,
  friend: {
    name: 'fuga',
    age: 30,
    family: {
      name: 'baz',  
      age: 1 
    }
  }
}

type T1 = DeepPick<obj, 'name'>   // { name : 'hoge' }
type T2 = DeepPick<obj, 'name' | 'friend.name'>  // { name : 'hoge' } & { friend: { name: 'fuga' }}
type T3 = DeepPick<obj, 'name' | 'friend.name' |  'friend.family.name'>  // { name : 'hoge' } &  { friend: { name: 'fuga' }} & { friend: { family: { name: 'baz' }}}

解答

Keyを.区切りでparseしていき、.前の文字列が現在見ているobjectに含まれていたらそれを取得し、再帰的に_DeepPickを繰り返す。

Keyにはunionが与えられ、最終的な解答はintersectionが求められているので以前やったUinonToIntersectoinでunionからintersectionに変換する。

という方針で実装したらいけた

type UnionToIntersection<U> = (U extends infer R ? (x: R) => any : never) extends (x: infer V) => any ? V : never

type _DeepPick<T extends object, K extends string> = K extends `${infer Head}.${infer Rest}`
  ? Head extends keyof T
    ? T[Head] extends object
      ? {[k in Head] : DeepPick<T[Head], Rest>}
      : never
    : never
  : K extends keyof T
    ? Pick<T, K>
    : never

type DeepPick<T extends object, K extends string> = UnionToIntersection<_DeepPick<T, K>>
yutake27yutake27

Pinia

Piniapという状態管理ライブラリの型を実装する

SimpleVueと似たような感じ

解答

gettersは定義上は関数だが外から見ると関数ではなく変数に見えるようにするのがポイント

これを実現するためにGetObjectReturnTypeという型を定義した。

あとはテストケース通りstate, getters, actionsの型を定義して、storeの型も定義してやればいける。

getterはReadonlyなことにも注意

type GetObjectReturnType<T> = { [key in keyof T as key extends `${infer k}`? k : never]: T[key] extends (...args: any) => any ? ReturnType<T[key]> : never}

declare function defineStore<S, G, A>(store: {
  id: string;
  state: () => S;
  getters: G & ThisType<GetObjectReturnType<G> & Readonly<S>>;
  actions: A & ThisType<S & Readonly<G> & A>
}): S & Readonly<GetObjectReturnType<G>> & A
yutake27yutake27

Camelize

各プロパティ名をキャメルケースに変換。ネストしているプロパティも変換する。

解答

snake_caseからCamelCaseに変換する型は以前やったので持ってきた。

配列の場合は各配列の要素ごとにCamelCaseにする必要があったので、それを行うCamelizeArrayという型を作成した。あとは再帰的にそれらを呼んでやるだけ。

type CamelCase<S extends string, isHead extends boolean = true> = S extends `${infer Head}${infer Rest}`
  ? Rest extends `_${infer _Rest}`
    ? `${Head}${CamelCase<Capitalize<_Rest>, false>}`
    : isHead extends true
      ? `${Lowercase<Head>}${CamelCase<Uncapitalize<Rest>, false>}`
      : `${Head}${CamelCase<Uncapitalize<Rest>, false>}`
  : S

type CamelizeArray<T extends object[]> = T extends [infer Head extends object, ...infer Rest extends object[]]
  ? [Camelize<Head>, ...CamelizeArray<Rest>]
  : []

type Camelize<T extends object> = T extends object[]
  ? CamelizeArray<T>
  : {
    [k in keyof T as k extends string ? CamelCase<k> : never]: T[k] extends object ? Camelize<T[k]> : T[k]
  }
yutake27yutake27

Drop String

文字列から、指定された文字を削除する

type Butterfly = DropString<'foobar!', 'fb'> // 'ooar!'

解答

難しくなかった。

除外する文字列をそのまま扱うのが大変なので、unionに変換し、そのunionを各文字がextendしているかどうかを判定するだけでいけた。

type StringToUnion<T extends string, Ret extends string[] = []> = T extends `${infer Head}${infer Rest}`
  ? StringToUnion<Rest, [...Ret, Head]>
  : Ret[number]

type DropString<S extends string, R extends string, Ret extends string = ""> = S extends `${infer Head}${infer Rest}`
  ? Head extends StringToUnion<R>
    ? DropString<Rest, R, Ret>
    : DropString<Rest, R, `${Ret}${Head}`>
  : Ret
yutake27yutake27

Split

文字列を指定された文字で分割するsplit関数の型を実装

解答

基本的な部分はすぐ実装できたが細かい例外ケースが面倒だった。

// 例外ケース
  Expect<Equal<Split<'Hi! How are you?', ''>, ['H', 'i', '!', ' ', 'H', 'o', 'w', ' ', 'a', 'r', 'e', ' ', 'y', 'o', 'u', '?']>>,
  Expect<Equal<Split<'', ''>, []>>,
  Expect<Equal<Split<'', 'z'>, ['']>>,
  Expect<Equal<Split<string, 'whatever'>, string[]>>,
type Split<S extends string, SEP extends string, Ret extends string[] = []>  = S extends `${infer H extends string}${SEP}${infer R extends string}`
  ? Split<R, SEP, [...Ret, H]>
  : S extends ""
    ? Ret extends []
      ? S extends SEP
        ? Ret
        : [S]
      : Ret
    : Equal<S, string> extends true
      ? S[]
      : [...Ret, S]

他の人の解答見たらもう少し綺麗に条件分岐していた

yutake27yutake27

ClassPublicKeys

classのpublicなキーを取得

解答

type ClassPublicKeys<T extends object> = keyof T

試しに書いてみたら通った。

yutake27yutake27

IsRequiredKey

↓のようにkeyがrequiredかどうかを判定

IsRequiredKey<{ a: number; b?: string }, 'a'>

解答

undefinedがT[K]をextendsしているかどうかでoptionalかどうか判定できるのでそれを使う。
複数キーを指定する場合でもtrue | false => false, true | true => trueのようにunionが最終的にマージされる

type IsRequiredKey<T, K extends keyof T> = undefined extends T[K] ? false : true

他の人の解答

NonNullableを使うとよさそう

type IsRequiredKey<T, K extends keyof T> = T[K] extends NonNullable<T[K]> ? true : false
yutake27yutake27

Object From Entries

↓のようにプロパティ名と型を持ったTupleのUnionからオブジェクトを生成

interface Model {
  name: string;
  age: number;
  locations: string[] | null;
}

type ModelEntries = ['name', string] | ['age', number] | ['locations', string[] | null];

type result = ObjectFromEntries<ModelEntries> // expected to be Model

解答

tupleのunionが与えられるのでそれをうまく扱うのがポイント

type UnionToIntersection<U> = (U extends infer R ? (x: R) => any : never) extends (x: infer V) => any ? V : never
type _UnionFromEntries<T extends [string, any]> = T extends T
  ?  {
  [k in T[0]]: T[1]
} : never

type ObjectFromEntries<T extends [string, any]> = UnionToIntersection<_UnionFromEntries<T>>

union distributionを発生させて各プロパティごとのオブジェクトのunionを生成する_UnionFromEntriesを作成して、その後UnionToIntersectionでunionからintersectionに変換するようにした。

この時点で型的には等しかったが、Equal関数が通らなかったので、もう1アレンジした↓

type UnionToIntersection<U> = (U extends infer R ? (x: R) => any : never) extends (x: infer V) => any ? V : never
type _UnionFromEntries<T extends [string, any]> = T extends T
  ?  {
  [k in T[0]]: T[1]
} : never

type _ObjectFromEntries<T extends [string, any]> = UnionToIntersection<_UnionFromEntries<T>>

type ObjectFromEntries<T extends [string, any], U = _ObjectFromEntries<T>> = {
  [k in T[0]]: k extends keyof U ? U[k] : never
}

これで通った

他の人の解答

他の人の解答見たらもっとエレガントなものがあった

https://github.com/type-challenges/type-challenges/issues/3382

type ObjectFromEntries<T extends [string, any]> = {
  [K in T[0]]: T extends [ K, any ] ? T[1] : never
}

https://github.com/type-challenges/type-challenges/issues/6515

type ObjectFromEntries<T extends [string,any]> = {
  [K in T as K[0]]:K[1]
}
yutake27yutake27

IsPalindrome

回文かどうかを判定

解答

type IsPalindrome<T extends string | number> = `${T}` extends `${infer Head}${infer Rest}`
  ? Rest extends `${infer Center}${Head}`
    ? IsPalindrome<Center>
    : Rest extends ""
      ? true
      : false
  : true

先頭の文字をとって、最後の文字が先頭の文字と一致しているかを判定し一致していたら再帰的にIsPalindromeを実行する

最初`${T}` extends `${infer Head}${infer Center}${infer Tail}`で1発で先頭と最後の文字を取れるかと思ったが流石に取れなかった。

yutake27yutake27

MutableKeys

mutableなpropertyのkeyを取得

解答

readonlyかどうかをEqual<Pick<T, k>, Readonly<Pick<T, k>>>で判定

type MutableKeys<T> = keyof {
  [k in keyof T as Equal<Pick<T, k>, Readonly<Pick<T, k>>> extends true ? never : k]: T[k]
}
yutake27yutake27

Intersection

配列の各要素から共通の要素を取得

type Res = Intersection<[[1, 2], [2, 3], [2, 2]]>; // expected to be 2
type Res1 = Intersection<[[1, 2, 3], [2, 3, 4], [2, 2, 3]]>; // expected to be 2 | 3
type Res2 = Intersection<[[1, 2], [3, 4], [5, 6]]>; // expected to be never
type Res3 = Intersection<[[1, 2, 3], [2, 3, 4], 3]>; // expected to be 3
type Res4 = Intersection<[[1, 2, 3], 2 | 3 | 4, 2 | 3]>; // expected to be 2 | 3
type Res5 = Intersection<[[1, 2, 3], 2, 3]>; // expected to be never

解答

type InitialValue<T extends unknown[]> = T[0] extends number[]
  ? T[0][number]
  : T[0]

type Intersection<T extends unknown[], Ret = InitialValue<T>> = T extends [infer Head, ...infer Rest extends unknown[]]
  ? Head extends number[]
    ? Intersection<Rest, Extract<Ret, Head[number]>>
    : Intersection<Rest, Extract<Ret, Head>>
  : Ret

Extract<1 | 2, 2 | 3> // 2のようにUnionから共通の要素を取得できるのでそれを使ってリストの各要素から共通要素をとっていく

他の人の解答

もっと短い回答があった

type Intersection<T> =
  T extends [infer First, ...infer Rest] ? (
    (First extends unknown[] ? First[number] : First) & Intersection<Rest>
  ) : unknown
yutake27yutake27

Binary to Decimal

二進数から十進数に変換

回答

数値を保存する用の配列を持っておく。
先頭の数字から見ていく。まず配列の長さを2倍にして、現在見ている数字が1だったらその配列に要素を足して、0だったら何もしないという操作を繰り返す。

type BinaryToDecimal<S extends string, Ret extends unknown[] = []> = S extends `${infer Head}${infer Rest}`
  ? Head extends '1'
    ? BinaryToDecimal<Rest, [...Ret, ...Ret, any]>
    : BinaryToDecimal<Rest, [...Ret, ...Ret]>
  : Ret['length']
yutake27yutake27

Object Key Paths

オブジェクトの各プロパティの名前をユニオンで繋げる。
その際に子孫プロパティだったらparent.childのようにする。また配列の場合はparent.0 | parent[0] | parent.[0]のようにする

回答

配列の扱いが面倒だった。keyof [hoge, fuga]で0, 1が取れそうだったのでそれを使ってkeyがnumberの場合は[]をつけたりするようにしたらいけた。

type KeyWithPrefix<T extends object, Prefix extends string = "", K extends keyof T = keyof T> = Prefix extends ""
  ? K
  : K extends string | number
    ? `${Prefix}.${K}` | (K extends number ? `${Prefix}${"." | ""}[${K}]` : never)
    : never

type ObjectKeyPaths<T extends object, Prefix extends string = "", K extends keyof T = keyof T> = K extends K
  ? T[K] extends object
    ? K extends string | number
      ? Prefix extends ""
        ? KeyWithPrefix<T, Prefix> | ObjectKeyPaths<T[K], `${K}`>
        : KeyWithPrefix<T, Prefix> | ObjectKeyPaths<T[K], `${Prefix}.${K}`>
      : never
    : KeyWithPrefix<T, Prefix>
  : never
yutake27yutake27

Two Sum

第一引数に数値の配列、第二引数に数値を渡して第一引数の配列内の2つの数値を足した数が第二引数の数値に一致する場合があるかどうかを判定

type sum1 = TwoSum<[3, 2, 4], 6> // true
type sum2 = TwoSum<[2, 7, 11, 15], 15> // false

回答

配列内の全てのペアの組み合わせを取得するAllPairという型を作成し、それを1つずつ足していき一致する場合があるかを判定したらいけた。無駄に長い...

type _AllPair<T extends number[], Ret extends number[] = []> = T extends [infer Head extends number, ...infer Rest extends number[]]
  ? _AllPair<Rest, [...Ret, Head]> | _AllPair<Rest, Ret>
  : Ret

type AllPair<T extends number[], P = _AllPair<T>> = P extends [infer Head extends number, infer Second extends number, ...any]
  ? [Head, Second]
  : never

type NumberToArray<T extends number, Arr extends any[] = []> = Arr['length'] extends T ? Arr : NumberToArray<T, [...Arr, any]>
type Plus<T extends number, U extends number> = [...NumberToArray<T>, ...NumberToArray<U>]['length']

type _TowSum<T extends number[], U extends number, P extends [number, number] = AllPair<T>> = P extends P
  ? Plus<P[0], P[1]> extends U
    ? true
    : false
  : never

type TwoSum<T extends number[], U extends number, V = _TowSum<T, U>> = Equal<V, boolean> extends true
  ? true
  : V

他の人の解答見ても同じくらいの長さだったのでそんなに簡潔には書けないみたい。

yutake27yutake27

Assign

Target objectに対してorigin object listのobjectのプロパティで上書きをしていく

type Target = {
  a: 'a'
  d: { 
    hi: 'hi'
  }
}

type Origin1 = {
  a: 'a1',
  b: 'b'
}


type Origin2 = {
  b: 'b2',
  c: 'c'
}

type Answer = {
   a: 'a1',
   b: 'b2',
   c: 'c'
   d: { 
      hi: 'hi'
  }
}

解答

listの要素を一つずつ見ていって、Target objectのプロパティとOrigin objectのプロパティが重複している場合はOrigin objectのプロパティで上書きをする。それ以外はそのままという操作を繰り返す。

Originに渡されたobjectがRecord<string, unknown>以外の場合は無視をしなければいけなかった。

type Assign<T extends Record<string, unknown>, U> =
  U extends [infer Head, ...infer Rest]
    ? Head extends Record<string, unknown>
      ? Assign<{
          [k in keyof T | keyof Head as k]: k extends keyof Head ? Head[k] : k extends keyof T ? T[k] : never
        }, Rest>
      : Assign<T, Rest>
    : T
yutake27yutake27

Maximum

配列から最大の値を取り出す

解答

2つの数値を比較して大きい方の数値を返すBiggerという型を作成して、配列の先頭から2つずつ取り出して数値の比較をして、大きい方を配列に入れてまた大小を比較という操作を繰り返すことで最大値を取得できた。

type Bigger<T extends number, U extends number, TA extends unknown[] = [], UA extends unknown[] = []> = TA['length'] extends T
  ? U
  : UA['length'] extends U
    ? T
    : Bigger<T, U, [...TA, unknown], [...UA, unknown]>

type Maximum<T extends number[], Ret = never> = T extends [infer Head extends number, infer Second extends number, ...infer Rest extends number[]]
  ? Maximum<[Bigger<Head, Second>, ...Rest], Bigger<Head, Second>>
  : Ret
yutake27yutake27

Capitalize Nest Object Keys

objectのkeyを再帰的にCapitalizeする
リストの場合はリスト内の各objectのkeyもCapitalizeする

解答

リストの各要素に対してCapitalizeするCapitalizeObjectArrayを定義して、与えられたobjectが配列だったらその型を使い、それ以外の場合は普通のCapitalizeをするようにしたらいけた

type CapitalizeObjectArray<T extends object[], Ret extends object[] = []> = T extends [infer Head, ...infer Rest extends object[]]
  ? CapitalizeObjectArray<Rest, [...Ret, CapitalizeNestObjectKeys<Head>]>
  : Ret

type CapitalizeNestObjectKeys<T> = T extends object[] ? 
  CapitalizeObjectArray<T>
  : {
    [k in keyof T as k extends string ? Capitalize<k> : never]: T[k] extends object ? CapitalizeNestObjectKeys<T[k]> : T[k]
  }
yutake27yutake27

Replace Union

Equal<UnionReplace<Function | Date | object, [[Date, string], [Function, undefined]]>, undefined | string | object>

こんな感じでunionを与えられた置換配列に基づいて置き換える

解答

シンプルにunionの要素1つずつと、配列の各要素が一致するかどうかをみていって一致していたら置換、一致してなかったら配列の次の要素と一致しているかを確認という操作を繰り返したらいけた。

type UnionReplace<T, U extends [any, any][]> = T extends T
  ? U extends [[infer HeadFrom, infer HeadTo], ...infer Rest extends [any, any][]]
    ? Equal<T, HeadFrom> extends true
      ? HeadTo
      : UnionReplace<T, Rest>
    : T
  : never
yutake27yutake27

FizzBuzz

3の倍数でFizz, 5の倍数でBuzz, 15の倍数でFizzBuzzというやつ

解答

面倒だが地道にやる系のやつ。

まず数値を配列に変換する型を定義。
その型を使って3の倍数、5の倍数かどうかを判定する型をそれぞれ定義。
これを使って数字が与えられた時にnumber | Fizz | Buzz | FizzBuzzを返す型を定義。

type NumToArr<T, Ret extends unknown[] = []> = Ret['length'] extends T
  ? Ret
  : NumToArr<T, [...Ret, unknown]>

type Div3<T, A extends unknown[] = NumToArr<T>> = A extends [unknown, unknown, unknown, ...infer Rest extends unknown[]]
  ? Div3<T, Rest>
  : A extends []
    ? true
    : false

type Div5<T, A extends unknown[] = NumToArr<T>> = A extends [unknown, unknown, unknown, unknown, unknown, ...infer Rest extends unknown[]]
  ? Div5<T, Rest>
  : A extends []
    ? true
    : false

type NumToFizzBuzz<N, D3 = Div3<N>, D5 = Div5<N>> = D3 extends true
  ? D5 extends true
    ? "FizzBuzz"
    : "Fizz"
  : D5 extends true
    ? "Buzz"
    : N extends number
      ? `${N}`  
      : never

type FizzBuzz<N extends number, Ret extends string[] = []> = Ret['length'] extends N
  ? Ret
  : FizzBuzz<N, [...Ret, NumToFizzBuzz<[...Ret, unknown]['length']>]>
yutake27yutake27

Run-length encoding

AAABCCXXXXXXYのような文字列が与えられた場合、エンコーディングされた文字列3AB2C6XYを返す型を作成。また、デコーダーも作成。

解答

だいぶ長いがやっていることは単純。
エンコーダは今見ている文字と前の文字が一致していたら配列に保存して次の文字を見る。不一致だったらそこまで保存した配列を使ってエンコードする。

デコーダは数字と文字のセットごとにパースして、数字の分だけの長さを持った文字列を作成する型を作り、それを使ってセットごとに変換していく。

type EncodeFromArray<T extends string[]> = T extends []
  ? never
  : T['length'] extends 1
    ? T[0]
    : `${T['length']}${T[0]}`

type NumberMap = {
  '2': 2,
  '3': 3,
  '4': 4,
  '5': 5,
  '6': 6,
  '7': 7,
  '8': 8,
  '9': 9,
}

type Number = keyof NumberMap

type ArrToStr<Arr extends string[], Ret extends string = ""> = Arr extends [infer Head extends string, ...infer Rest extends string[]]
  ? ArrToStr<Rest, `${Ret}${Head}`>
  : Ret

type DecodeFromStr<NumStr extends Number, Str extends string, Arr extends string[] = []> = Arr['length'] extends NumberMap[NumStr]
  ? ArrToStr<Arr>
  : DecodeFromStr<NumStr, Str, [...Arr, Str]>

namespace RLE {
  export type Encode<S extends string, Arr extends string[] = [], Ret extends string = ""> = S extends `${infer Head}${infer Rest}`
    ? Arr extends []
      ? Encode<Rest, [Head], Ret>
      : Head extends Arr[0]
        ? Encode<Rest, [...Arr, Head], Ret>
        : Encode<Rest, [Head], `${Ret}${EncodeFromArray<Arr>}`>
    : Arr extends []
      ? Ret
      : `${Ret}${EncodeFromArray<Arr>}`

  export type Decode<S extends string, Ret extends string = ""> = S extends `${infer N extends Number}${infer Str}${infer Rest}`
    ? Decode<Rest, `${Ret}${DecodeFromStr<N, Str>}`>
    : S extends `${infer Head}${infer Rest}`
      ? Decode<Rest, `${Ret}${Head}`>
      : Ret
}
yutake27yutake27

Tree path array

↓のようなPath型を作成

declare const example: {
  foo: {
    bar: {
      a: string
    }
    baz: {
      b: number
      c: number
    }
  }
}

type p = Path<typeof example>
// ["foo"] | ["foo", "bar"] | ["foo", "bar", "a"] | ["foo", "baz"] | ["foo", "baz", "b"] | ["foo", "baz", "c"]

解答

pathを保存するための配列を用意しておき、オブジェクトの各プロパティを見ていってプロパティの値がオブジェクトだったら配列にプロパティ名を入れて再帰的にPathを実行し、そうでない場合は配列に保存しておいたそれまでのpathと現在見ているプロパティ名を返す。

type Path<T, Ret extends unknown[] = [], K extends keyof T = keyof T> = K extends K
  ? T[K] extends Record<string, unknown>
    ? [...Ret, K] | Path<T[K], [...Ret, K]>
    : [...Ret, K]
  : never

他の人の解答

もっと簡潔な解き方をしている人がいた https://github.com/type-challenges/type-challenges/issues/18292
美しい

type Path<T> = T extends Record<PropertyKey, unknown>
  ? {
      [P in keyof T]: [P, ...Path<T[P]>] | [P];
    }[keyof T]
  : never;
yutake27yutake27

Is Negative Number

負の数かどうかを判定。

例外としてnumberを与えられた時とunionを与えられたときはneverを返す

解答

ほとんどポイントは例外を処理できるかどうかだけ。負の数かどうかは文字列に変換したときにマイナスが先頭につくかどうかで判定。

type IsUnion<T, U = T> = T extends T
  ? [U] extends [T]
    ? false
    : true
  : never

type IsException<T extends number> = Equal<T, number> extends true
  ? true
  : IsUnion<T> extends true
    ? true
    : false


type IsNegativeNumber<T extends number> = IsException<T> extends true
  ? never
  : `${T}` extends `-${any}`
  ? true
  : false
yutake27yutake27

これにて上級編終了。

SimpleVueやUnionToTuple, UnionToIntersectionみたいな初見では何も思いつかないみたいなやつ以外はほとんど自力でできた。

最後の方はだいぶスピーディーに実装できていたので成長を感じた。