Zenn
Open44

Type Challenges(中級編)

tac-tac-gotac-tac-go

Type Lookup

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00062-medium-type-lookup/README.ja.md

実装

type LookUp<U,T> = U extends {type : T} ? U : never;

もしUにユニオン型を代入すると、ユニオン分配 になり、ユニオン型のそれぞれの要素に対して、Conditional Typeが適用されるようになる。この知識があれば解ける問題。Uがプロパティtype、値Tを持つものであるときに型を返せばいい。

tac-tac-gotac-tac-go

Pop

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00016-medium-pop/README.ja.md

実装

type Pop<T extends any[]> = T['length'] extends 0  ? [] : T extends [...infer Arg, any] ? Arg : never

難易度的には中級ではあるが、初級のFirst of Arrayとほとんど同じ解き方で解ける。First Of Arrayではinferを使う解き方を提示したが、今回はその逆で最後以外の要素が知りたいので、最後以外の要素に対して inferを使い推論を行う。一つ注意点としては、テストケースとして空の配列が入力したときは空の配列を返すように求めてられているので length を使い長いさを求めて0のときは空の配列を返却する。

tac-tac-gotac-tac-go

Deep Readonly

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00009-medium-deep-readonly/README.ja.md

実装

type DeepReadonly<T> = {
  readonly [k in keyof T] : keyof T[k] extends never ? T[k] : DeepReadonly<T[k]>
}

再帰的に オブジェクトのプロパティに readonly をつけていく。ネストが深くなったとしてもreadonlyをつけていく必要があるので再帰処理を書く必要がある。プリミティブ型に対して T[k] のようにアクセスをおこなってもプロパティを持たないので never となる。それをもとに Conditional Types 処理を書くと実装可能である。

tac-tac-gotac-tac-go

Get Return Type

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00002-medium-return-type/README.ja.md

実装

type MyReturnType<T extends (...args : any[]) => any> = T extends (...args : any[]) => infer U ? U : never;

EasyのParametersに近い問題。Parametersは引数に関する型を調べる必要があったが、今回は戻り値の型を調べる必要がある。Tは関数として型制限をし、予測したい戻り値の部分にinferとして推測をおこない、その値を返すようにする。

tac-tac-gotac-tac-go

Last of Array

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00015-medium-last/README.ja.md

実装

type Last<T extends any[]> = T['length'] extends 0 ? never : T extends [...any,infer U] ? U : 0;

配列の長さが0のとき never を返す必要があるので length プロパティで長さを調べ Conditional Types で条件分岐。最後の型が知りたいのでそれを調べるためにinferを使う。

もしくは配列を何かしら含むときはU を返しそれ以外のときはneverを返す

type Last<T extends any[]> = T extends [...any, infer L] ? L : never
tac-tac-gotac-tac-go

Omit

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00003-medium-omit/README.ja.md

実装

type MyOmit<T, K extends keyof T> = {
  [Key in keyof T as Key extends K ? never : Key] : T[Key];
}

Mapped Types の なかで、キーの値を状況によって分岐させたい場合、型アサーションである as を使用する。

neverのものはインデックスアクセスの際にも値が取得されないので、結果的に指定したキーは除去されOmitと同じ形になる。
また、Exclude を用いた実装も考えられる。
ただし、これだと readonly がつくようなプロパティだとタイプエラーになる。

type MyOmit<T,U> = {
  [k in Exclude<keyof T,U>] : T[k]
}

Exclude を用いた場合、元の型Tとの関連性が維持されるわけではなく新しい型が作られたと認識してしまうと、 keyof のあとに as を続けることで元の型Tとの関連性が維持されreadonly修飾子が消えることがなく、処理をすることができる。ちなみに readonlyなどを保持したまま処理できる特性をHomomorphic Mapped Types という。

tac-tac-gotac-tac-go

Merge

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00599-medium-merge/README.ja.md

実装

type Merge<T, U> = {
  [K in keyof T | keyof U] : K extends keyof U ? U[K] : K extends keyof T ? T[K] : never
};

二つのkeyofの値を | でつないでユニオン型が作れるかがポイントとなる。あとは extendsConditional Types で条件分岐をして、特定のキーが含まれれば Uの方を優先して、それ以外であればTのキーでMapped Typesをおこなうようにする。エラーを防ぐためにキーが存在しない場合はneverを使うことも必要。

tac-tac-gotac-tac-go

String to Union

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00531-medium-string-to-union/README.ja.md

実装

type StringToUnion<T extends string> =
  T extends `${infer F}${infer R}`
    ? F | StringToUnion<R>
    : never;

テンプレートリテラルとinferと再帰処理を組み合わせる。TypeScriptのテンプレートリテラル型において、inferを使用すると、一致する文字列の最短部分が取り出すことができる。inferを二つ続けることで最初のinferで一文字目のみを取得することができ、それ以外の処理をまた再帰処理をし最終的にUnion型でつなげることで処理が可能となる。

tac-tac-gotac-tac-go

Tuple to Union

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00010-medium-tuple-to-union/README.ja.md

実装

type TupleToUnion<T extends readonly any[]> = T[number]

非常にシンプルな実装である。Tは配列を期待するようにして、その配列に対してnumberを使うとユニオン型で値を取得できる。少しややこしい実装ではあるが、再帰処理とinferを組み合わせることで以下のような実装でも実現できる。

type TupleToUnion<T> = T extends [infer U,...infer P] ? U | TupleToUnion<P> : never
tac-tac-gotac-tac-go

Trim

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00108-medium-trim/README.ja.md

実装

type Space = ' ' | '\t' | '\n';
type Trim<T extends string> = T extends `${Space}${infer R}` | `${infer R}${Space}` ? Trim<R> : T

テストケースでは空白以外にも改行コードやタブなどが含まれているためそれらを除去する必要がある。
String to Union という中級の問題で文字に対して inferを使うことで最小限マッチングができる解放を紹介した。今回ポイントとなるのは、テンプレートリテラルにinfer以外の文字も普通に埋め込めることがわかるかどうかである。スペースを埋め込みスペースがあれば再帰処理をおこないそうでなければそのままの文字を返す処理をすれば最終的には得たい結果が得られるようになる。

tac-tac-gotac-tac-go

Flatten

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

実装

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

ネストされた配列に対して、すべての配列をスプレッド展開し一次元配列にする問題。
知識としては今までのものを組み合わせることで実装可能。extends が多くて見づらくはあるが、おこなっていることとしては、配列の特定の値が配列なのかを調べそれであればスプレッド演算子を使って再帰処理をおこなっているだけである。

tac-tac-gotac-tac-go

Replace

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00116-medium-replace/README.ja.md

実装

type Replace<S extends string,FROM extends string,TO extends string> = 
                          FROM extends "" 
                          ? S 
                          : S extends `${infer L}${FROM}${infer R}` 
                          ?  `${L}${TO}${R}`  :  S

S,FROM,TOともにすべて文字列であるという制約をつける。
そして置換文字が空白の場合は文字をそのまま返し、そうじゃない場合に処理をおこなう。
inferで左の文字と右の文字を推測させ、その間に置換対象文字が入っていれば処理をおこなう。
ただいinferは最小マッチングなので左右の文字がそれぞれ0文字に割り当てられることもありえる。

tac-tac-gotac-tac-go

ReplaceAll

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00119-medium-replaceall/README.ja.md

実装

type ReplaceAll<S extends string,From extends string,To extends string> = 
                      From extends ""
                      ? S 
                      : S extends `${infer L}${From}${infer R}`
                      ? `${L}${To}${ReplaceAll<R,From,To>}`
                      : S

上のReplaceとほとんど同じ問題、唯一違う点としては再帰処理をおこないすべての置換対象に対して処理を行うという点のみ。Template Literal Types のなかでもう一度typeを読み出せるか、また Templateの中なののでジャネリックにはドルマークをつけずに呼び出せる。唯一注意点をあげるとすればそこくらい。比較的easyな問題。

tac-tac-gotac-tac-go

Chainable Options

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00012-medium-chainable-options/README.ja.md

実装

type Chainable<T = {}> = {
  option: <K extends string,V>(Key: K extends keyof T ? never : K ,value:V) => Chainable<Omit<T,K> & Record<K,V>>,
  get(): T;
};

今までの問題よりも急に難易度が上がった印象。
option は引数を受け取り、Chainable を返す必要のある関数。getはそのままジェネリックTを返せばいい。
まず、Tのデフォルト引数としてオブジェクトの {} を入力する。objectと記載してもよい。
option は、期待する入力としてキーは文字列なのでextendsで型制約をおこなう。もしTのプロパティに含まれていればneverとする。そうしてKeyの値を算出し、最終的にはOmitで新しく作りたいキーを削除したものと新しくRecordで作られたオブジェクトで & で繋ぐことで新しいオブジェクトを作り出せる。

tac-tac-gotac-tac-go

Append Argument

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00191-medium-append-argument/README.ja.md

実装

type AppendArgument<Fn extends (...args:any) => any,K> = Fn extends (...args:infer P) => infer R ? (...args:[...P,K]) => R : never;

今までの応用的な問題。関数型Fnは期待値として関数を受け取るのでextendsで制約を作る。
もしFnが引数をもった関数の場合スプレッド構文を使い配列のなかで展開して新しいタプルを作ることで新しい型が追加される。少し混乱した場所として

type Case1 = AppendArgument<(a: number, b: string) => number, boolean>
type Result1 = (a: number, b: string, x: boolean) => number

テストケースでいきなり x という引数名が出てきてxなんて出てきてないと思っていたが、ここでいうx(aやbも)は任意引数であり名前はなんでも良い。テストのためにつくられた変数。typeが同じシグネチャであるかどうかは型の順番と数が重要であり、それが一致すればテストケースに合格できる。

tac-tac-gotac-tac-go

Append to object

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

実装

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

比較的簡単な問題。Mapped Types で新しいオブジェクトを生成する。Tのプロパティと新しいキーのユニオンを生成し、そのキーがTであればオブジェクトTの値をT[k]で返し、そうじゃなければ新しい値Vを返す。

tac-tac-gotac-tac-go

KebabCase

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00612-medium-kebabcase/README.ja.md

実装

type KebabCase<S extends string> = S extends `${infer S1}${infer S2}` 
                                  ? S2 extends Uncapitalize<S2> 
                                  ? `${Uncapitalize<S1>}${KebabCase<S2>}` 
                                  : `${Uncapitalize<S1>}-${KebabCase<S2>}` 
                                  :  S

知識としては今までの問題と代わりわなく、一つ特殊なことといえば Uncapitalize という文字の一文字目を小文字にする Template Literal Types を使えるかどうか。
infer S1 には文字Sの最初に一文字目、infer S2にはそれ以降の文字が入っている。
1文字目は無条件で小文字化するので2文字目から判定しその文字が小文字であればケバブケースにして、そうじゃなければ再び再帰処理をおこない文字を連結していく。最終的にinfer S2には空文字が入るのでそこで再帰処理は完了し、そこから文字の結果が返されていくので処理は完了する。

tac-tac-gotac-tac-go

Absolute

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

実装

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

これは比較的簡単な問題。気をつけることとしてTは数字しか受け取らないように型制約をおこなうこと。
Tがもし符号付きの文字であった場合にinferで文字を推論しマイナスが取り除かれた文字を返してあげればよい。

tac-tac-gotac-tac-go

Length of String

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

実装

type strToArray<T> = T extends `${infer _}${infer R}` ? [unknown,...strToArray<R>] : []
type LengthOfString<T> = strToArray<T>['length']

文字リテラルの長さを計算する問題。配列型にすると'length` で長さが得られるのは初級編でも実践済み。
問題はどう値を得るかというところ。

例えば Tの入力が abc であったときの挙動をあげる。

LengthOfString<"abc">:
[unknown, ...LengthOfString<"bc">]
[unknown, unknown, ...LengthOfString<"c">]
[unknown, unknown, unknown, ...LengthOfString<"">]
🔽
[unknown, unknown, unknown]
[unknown, ...[unknown, ...[unknown, ...[]]]]
[unknown, unknown, ...[unknown, ...[]]]
[unknown, unknown, unknown, ...[]]
[unknown, unknown, unknown]

ここで鍵になるのは配列のなかでスプレッド構文を利用した再帰処理をおこなうことで最終的に配列のなかでスプレッド構文が利用された形になり、配列が得られるということである。
そうして得られた配列に対して新しいtypeを宣言し値を得る。再帰処理のときunknownをつかっているが、1としてもよいし値はなんでもよい。

tac-tac-gotac-tac-go

AnyOf

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/00949-medium-anyof/README.ja.md

実装

type Falsy = 0 | "" | false | []  | { [K: string]: never }  | undefined | null; 
type AnyOf<T extends readonly any[]> = T[number] extends Falsy ? false : true;

配列の各要素に条件一致させて判定していくので numberで配列参照する。そしてテストケースに合格するためにあらゆるテストケースをtypeで定義しそれを満たしたらfalse、満たさなかったらtrueと定義する。

tac-tac-gotac-tac-go

IsAlphabet

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/35252-medium-isalphabet/README.md

実装

type IsAlphabet<S extends string> = Lowercase<S> extends Uppercase<S> ? false : true

いくつか実装は考えられる。解法の一つとして大文字と小文字にしたときの文字の挙動の違いを紹介する。
アルファベットは大文字小文字にすると文字が変化するが、そうでない文字はそのままの挙動であることをいかし、trueとfalseに分岐する。

tac-tac-gotac-tac-go

Compare Array Length

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/34007-medium-compare-array-length/README.md

実装

type CompareArrayLength<T extends any[], U extends any[]> = T['length'] extends U['length'] ? 0 : keyof T extends keyof U ? -1 : 1

二つの配列の長さを比較し、文字同じ長さなら0、TがUより大きい場合は1、そうでなければ-1を返す実装。
大小記号を使って単純な比較はできないのでextendsをつかって、条件を網羅していく。
まず T['length'] extends U['length'] ? 0 は同じ値になったときの処理を記述する。
その後 keyof T extends keyof U で配列に含まれるインデックスをユニオン型として取り出し、それが網羅されているかどうかで分岐条件を決める。
配列のkeyof出力は以下のようになるとイメージしてもらえばよい。配列はインデックスをキーに持つ特殊なオブジェクトなのでkeyofのようにプロパティを取り出そうとするとインデックスが取得されるようになる。

T = [1, 2], U = [3, 4, 5]
keyof T = 0 | 1, keyof U = 0 | 1 | 2
tac-tac-gotac-tac-go

CheckRepeatedChars

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/09142-medium-checkrepeatedchars/README.md

実装

type CheckRepeatedChars<T extends string, U = never> = T extends `${infer P}${infer R}`
                                                    ? P extends U
                                                    ? true
                                                    : CheckRepeatedChars<R,U | P>
                                                    : false

重複文字を検出する問題。解法としてはいくつか考えられるが、一つとして新しいUというジェネリックをつくり、それに過去に検索した文字列を持たせることで文字列の重複を見つけ出すという実装をおこなった。Uは初期値ではneverだが、CheckRepeatedChars<R,U | P> 再帰的に文字を呼び出すことでUには過去に検索した文字列Pが代入されていく。これを利用し、もしPがUに含まれているのであればtrueにするように実装していく。

もう一つ、Uの状態を持たせないでジェネリック型Tを使うだけで実装する方法も紹介。

type CheckRepeatedChars<T extends string> = T extends `${infer L}${infer R}` ? R extends `${any}${L}` ? true : CheckRepeatedChars<R> : false

TをL,Rに分解する。そしてRが任意の文字列とLと一致する場合は重複したことになるのでtrue、そうじゃないときは再帰処理。見つからなかったらfalseにする。個人的には下の解の方がわかりやすいが、新しくジェネリック型を定義して値を更新していくのも良く使うテクニックなので紹介した。

tac-tac-gotac-tac-go

Reverse

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/03192-medium-reverse/README.md

実装

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

処理のステップとしては以下のように進んでいく。
次第にRestの配列自体が短くなっていき、空配列であればそれをそのまま返し処理が終了する。
再帰処理を使い、スプレッドで前に残りの配列、後ろに最初の数値を展開していくシンプルな実装である。

[...Reverse<[2, 3]>, 1]
[...Reverse<[3]>, 2]
[...Reverse<[]>, 3]

また、Lという初期値が空配列のジェネリックを作成し、再帰処理で Lに値を追加していき逆配列を取得することも考えられる。実装の内容としては上記の実装と同じである。

type Reverse<T extends any[],L extends any[] = []> = T extends [infer P,...infer R] ? Reverse<R,[P,...L]> : L
tac-tac-gotac-tac-go

Number Range

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/08640-medium-number-range/README.md

実装

type Utils<L,C extends any[] = [],R=L> = C['length'] extends L ? R : Utils<L,[...C,0],C['length'] | R>
type NumberRange<L,H> =  L | Exclude<Utils<H>, Utils<L>>

考え方としては、0〜Lまでのユニオンと0〜Hまでの二つを作る。そしてその二つのユニオンから重複部分を削除することで、特定の範囲のユニオンを生成することができる。
Excludeしたときに、始点の値も削除されるので、L | でその値も追加しておくことが必要。
type-challenge では配列の範囲を作るときのテクニックとして再帰処理を使うことが多い。ある配列にスペレッドと特定の数(unknownとか0でもなんでもいい)を追加していくことで配列の長さが長くなるのでそれを利用する。

tac-tac-gotac-tac-go

Drop Char

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/02070-medium-drop-char/README.md

実装

type DropChar<S, C extends string> = S extends `${infer Head}${C}${infer Tail}`
                                     ? DropChar<`${Head}${Tail}`, C> 
                                     : S;

Trim Left とかなり似た問題である。Trim Leftは左端の文字を削除する必要があったので、削除したい文字を左端に定義すればよかったが、今回は状況として間に入るケースもあるのでinferの二つで消したい文字を囲む。
そして条件を満たすものがあればそれを削除したものを再帰処理にかけ対象の文字が削除されるまで処理を続けていく。

'butter fly!' からスペースを削除する例を考える。

${infer Head}${C}${infer Tail} が以下のように分解される。
Head = 'butter'
C = ' '
Tail = 'fly!'
tac-tac-gotac-tac-go

Without

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/05117-medium-without/README.md

実装

type ToUnion<U> = U extends any[] ? U[number] : U
type Without<T extends any[], U> = T extends [infer P,...infer R] ? P extends ToUnion<U> ? Without<R,U> : [P,...Without<R,U>] : T

注意点としては条件 Uが整数ではなく配列として複数値渡されるテストケースがあるので、配列に対してユニオン型を生成する。そして配列のなかで一文字ずつ条件Uと一致しているかを検証し、一致していたら切り取ったものを再度処理しもし合致するものがなければ、その値は配列そして残りつつ、再帰処理をおこなっていく。

tac-tac-gotac-tac-go

Integer

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/10969-medium-integer/README.md

実装

      
    
    
      
    
type Integer<T extends number> = number extends T ? never : `${T}` extends `${infer P}.${infer U}` ? never : T

いくつか実装が考えられる。まず一つ目として、Tが数値であるかどうかを number extends T で判定している。 Tがリテラル型の場合、numberという抽象的な型に包含されないので、falseとなり数値であるという判定になる。あとはテンプレート文字を使い数値を文字列に変換して、その文字列の間に . があるかどうかで整数を判定する。

type Integer<T extends number> = `${T}` extends `${bigint}` ? T : never

もう一つの実装を紹介する。テンプレートリテラルのなかでbigint 型のすべての値が文字列型として展開される。もしTが整数であれば具体的な数値はすべての数値を包含する抽象的な型に包含されるため、整数値であると判断することができ、そのままTを返せばよい。

tac-tac-gotac-tac-go

Zip

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/04471-medium-zip/README.md

実装

type Zip<T, U> = T extends [infer TL,...infer TR] ? U extends [infer UL,...infer UR] ? [[TL,UL],...Zip<TR,UR>] : [] : []

Pythonのzip関数のようなものを実装する問題。考え方としてはシンプルで、TとUというジェネリックをそれぞれ配列であるかを検証する。そしてもし両方の条件を満たせば最初の要素同士を配列として再帰処理を返していくようにする。

tac-tac-gotac-tac-go

IsTuple

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/04484-medium-istuple/README.md

実装

type IsTuple<T> = [T] extends [never] 
                  ? false 
                  : T extends ReadonlyArray<unknown> 
                  ? number extends T['length'] 
                  ? false 
                  : true 
                  : false

Tがタプル型であるかを判定する問題。
[T] extends [never] はTがneverなときfalseを返すために必要な実装。T extends never では never extends neverのとき左側のneverはないものとして扱うためfalseの条件分岐には到達しない。そのため[]をつけてT 全体を1つの型として扱い、比較をおこなう。

T extends ReadonlyArray<unknown> これはTがreadonly型の配列であるかを判定している。
number extends T['length']、ここでTがタプルの場合具体的な値を持つためfalseとなりつまりこれがタプルということになり、Tが配列の場合はnumberとなるため、number extends T['length'] がtrueとなり、falseのほうに条件分岐する。応用力が問われる問題。
ちなみに、ReadonlyArray<unknown> readonly unknown[]と書いても問題ない。

tac-tac-gotac-tac-go

Trunc

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/05140-medium-trunc/README.md

実装

type Trunc<T extends number | string> = `${T}` extends `${infer P}.${any}` ? (`${P}` extends "-" | "" ? `${P}0` : `${P}`) : `${T}`

Tを小数点抜きの文字に変換するコード。基本的には ${infer P}.${any}のコードで小数点を見つけ出すのが肝。ただし、テストケースに合格するためには .3-3.2 など . の前に記号や数字がない場合も検出しなければならない。コードを追加し、そのような場合でも 0 が返るようにしている。

tac-tac-gotac-tac-go

OmitByType

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/02852-medium-omitbytype/README.md

実装

type OmitByType<T, U> = {
  [K in keyof T as T[K] extends U ? never : K] : T[K]
}

解法としてはOmitとほとんど同じで、Omitはプロパティ名から削除するものを選択するのに対してこの問題は、バリューの値をもとにして削除する対象を決める。
なので asのあとにKではなく、T[K] を指定し該当する値が存在するかどうかを判定する必要がある。

tac-tac-gotac-tac-go

All

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/18142-medium-all/README.md

実装

type All<T extends any[],F> = T extends [infer P,...infer U] ? Equal<F,P> extends true ? All<U,F> : false : true

実装方法としては配列の一つ一つを比較して、その値が正しければtrueを返し続ける処理をおこなう。最後配列が空になればtrueとなり、全ての配列に対して値が同じ配列であることを確認できたことを示している。Equal に関しては type-challenges で使われている等価比較の型が用意されているので使用する。

import type { Equal, Expect } from '@type-challenges/utils'
tac-tac-gotac-tac-go

Parse URL Params

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/09616-medium-parse-url-params/README.md

実装

type ParseUrlParams<T extends string> = T extends `${string}:${infer S}` 
                                        ? S extends `${infer P}/${infer R}` 
                                        ? P | ParseUrlParams<R> 
                                        : S 
                                        : never

URLのパラメータ部分で特定の形式の文字を取得し合致するものがあればユニオンとして接続していく問題。実装としては非常にシンプル。もし合致するものがあれば P | ParseUrlParams<R> で新しく発見された文字に連結していきながら再帰処理をおこなっていく。

tac-tac-gotac-tac-go

IsNever

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/01042-medium-isnever/README.md

実装

type IsNever<T> = [T] extends [never] ? true : false

この問題のキーとしては、neverを評価する際にタプルにすること。
never要素が存在しない ことを意味する型なので、分解の結果として条件型自体が評価されることなく never が返されます。
そのため、タプルで括ることで、T 全体が 1 つの要素として扱われて、ユニオン型として分解されずにそのまま値を評価することができる。

tac-tac-gotac-tac-go

Flip

問題
https://github.com/type-challenges/type-challenges/blob/main/questions/04179-medium-flip/README.md

実装

type Flip<T extends Record<string,string | boolean | number>> = {
  [k in keyof T as `${T[k]}`] : k
}

オブジェクトのキーとバリューを入れ替える問題。
[k in keyof T as `${T[k]}`] : k 自体の記述は as の使い方も含めて過去に出てきたので難しくはないかと思う。ここでのキーになるのは、RecordT[k]のときキーになりうる値のみを制限することでエラーになることを防ぐ。ここでTはstringがキーのstring | boolean | number が値のオブジェクトとして入力値を制限することで、エラーが発生するのを防ぐ。

作成者以外のコメントは許可されていません