毎日Type<Challenge>[]
はじめに
このスクラップではTypeScriptの型について豊富な問題を備えているType<Challenge>[]
を毎日3つずつ解いて投稿していきます。
はじめの18日分はQiitaに投稿していたものを削除してこちらに移行しています。
1日目
【入門】Hello World
問題の詳細はこちらです。
問題
以下のコートを変形して
// expected to be string
type HelloWorld = any
以下のテストを通るようにしなさい。
// you should make this work
type test = Expect<Equal<HelloWorld, string>>
解答
type HelloWorld = string
解説
入門なので何の捻りもない問題でした。HelloWorldの型がanyではなく、stringになれば良いのでその部分を置換しました。
ちなみに問題のテストで使われているEqualですが、第一引数と第二引数に渡された方を比較して正しければtrue間違えていればfalseを返す型となっています。
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
複雑かつ難解でわかりにくいですが、こちらの記事がEqualについてとても分かりやすかったです。
Equalはtrueのみを許容する型です。
type Expect<T extends true> = T
【初級】Pick
問題の詳細はこちらです。
問題
組み込みの型ユーティリティPick<T, K>を使用せず、TからKのプロパティを抽出する型を実装します。
例えば:
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
解答
type MyPick<T, K extends keyof T> = { [k in K]: T[k] }
解説
先ほどの問題に比べると難易度は一段階どころじゃないくらい上がったと思います。Mapped Typesを知らなければ解けない気がしますが、基本的な形であるので(知っておくべきだし)まさに初級と感じました。
型引数で第一引数にT、そして第二引数にはTのkeyの一部となるようなKを取るように設定しました。つまりKはTのキーで作られたユニオン型となっています(単体の時もありますが)。そしてKから一つ一つキーとなる値を取ってきてそれに対応するTのプロパティのみを取得しています。
私の解答ではk in K
としてますが、他の方の回答を見るとk
の部分をP
で書かれているものが多かったです。さらに実際のtsの実装でも
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
となっていました。Kの一部ということでpartから取ってきているのかなと考えられます。今後はこの部分はPで回答したいと思いました。
【初級】Readonly
問題の詳細はこちらです。
問題
組み込みの型ユーティリティReadonly<T>を使用せず、T のすべてのプロパティを読み取り専用にする型を実装します。実装された型のプロパティは再割り当てできません。
例えば:
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
解答
type MyReadonly<T> = { readonly [P in keyof T]: T[P] };
解説
Mapped Typesを知っているもしくは、先ほどの問題が解いたことがあれば容易な問題だったのではないでしょうか(readonlyを知ってれば)。Tを再生成する場合は
type MyReadonly<T> = { [P in keyof T]: T[P] };
のように書きますが、今回はreadonlyにしなければならないので、生成時に一手間加えました。改行の差分はありますが、tsの実装でも同様の実装方法となっています。
2日目
【初級】Tuple to Object
問題の詳細はこちらです。
問題
タプルを受け取り、その各値のkey/valueを持つオブジェクトの型に変換する型を実装します。
例えば:
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'mode
解答
type TupleToObject<T extends readonly any[]> = { [P in T[number]]: P }`
解説
2問目、3問目で利用した、Mapped Typesとタプルの扱いが重要となる問題でした。
タプル型TをT[number]のように表現すると中身のユニオン型を得られるのでそれを活かした問題でした。
ちなみにですが、他の方の回答を見てみると前回P in T
の形ではPを使っていることが多いので今後はPを使うという話をしましたが、ここではP以外も多く使われてました(K、valueやkeyなど)。
例
type T = [number, string, boolean];
type U = T[number]; // number | string | boolean
解答を試すPlaygroundではTupleToObjectの引数は初めから入力されていました。そのため触れてきませんでしたが、引数の部分に注目してみるとT extends readonly any[]
となっています。Playgroundのテストでは型がreadonlyのタプルなのでreadonlyにしなければ通らないのですが、もしreadonlyでなかったとしても配列やタプルを扱う場合は極力readonlyにした方が良いです。そうすることで型に対するドキュメント性の向上や、安全性が高まります。
【初級】First of Array
問題の詳細はこちらです。
問題
配列Tを受け取り、その最初のプロパティの型を返すFirst<T>を実装します。
例えば:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
解答
type First<T extends any[]> = T extends [infer U, ...any] ? U : never;
解説
これまでとは毛色が異なる問題でした。他の方の解答に以下のようなものがあり、私の解答はスマートではないことに気づきました。
type First<T extends any[]> = T extends [] ? never : T[0];
空配列でなければ、先頭を返すというものです。
私の解答は応用が効くという点で紹介します。ここではinferを用いて型推論をおこなっています。Tが配列で、最初に型推論が可能なUがあればUを返してUがなければneverを返すようになっています。Firstは配列しか受け取れませんので、最初の値がないものは空配列として、最初の値Uを推論して返すことができるわけです。
【初級】Length of Tuple
問題の詳細はこちらです。
問題
タプルTを受け取り、そのタプルの長さを返す型Length<T>を実装します。
例えば:
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
解答
type Length<T extends readonly any[]> = T extends { length: infer U } ? U : never;
解説
他の解答を見るとまた簡単な解答がありまして、
type Length<T extends readonly any[]> = T['length'];
で良いそうです。私の解答はinferでlengthにアクセスして取得したやり方です。
3日目
【初級】Exclude
問題の詳細はこちらです。
問題
組み込みの型ユーティリティExclude <T, U>を使用せず、Uに割り当て可能な型をTから除外する型を実装します。
例えば:
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
解答
type MyExclude<T, U> = T extends U ? never : T;
解説
これは挙動を知っているかのクイズですね。
TにUが含まれていたときのみ、neverを返すようにすることで、Uを除いたTが返るようになります。tsの本家の実装も同じようになっています。
【初級】Awaited
問題の詳細はこちらです。
問題
Promise ライクな型が内包する型をどのように取得すればよいでしょうか。
例えば:Promise<ExampleType>という型がある場合、どのようにして ExampleType を取得すればよいでしょうか。
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
解答
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
解説
アルゴリズム的な要素が顔を出してきて、面白くなってきました。
Promiseが剥がれるまでPromiseが内包する型の推論を行いました。inferを使った推論を再起的に行って解答を得ています。
他者の解答を見てみると以下のようなものが見受けられました。
type MyAwaited<T> = T extends Promise<infer P> ? P extends Promise<unknown> ? MyAwaited<P> : P : never
このケースではTにPromiseを渡さなかった場合はneverを返すようになります(前者はそのままTを返します)。最初のTの検査でPromiseでなければneverが返されるからです。次にPの中身を見てPromiseであればMyAwaitedに渡して再起的に検査、PromiseでなければPを返しています。
挙動の違いは前者はPromiseでなかった場合は再起先で値を返し、後者はPromiseでなかった場合はすぐに中身を返して再起させないという違いから生まれています。テストケースにはPromiseがないものが含まれていないのでどちらも解答として問題ないと考えています。。
tsではAwaitedとして以下のように実装されています。
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F): any } ?
F extends ((value: infer V, ...args: any) => any) ?
Awaited<V> :
never :
T;
Awaitedの解説は長くなるので別で書こうと思います。どちらにせよ、tsによって実装されたAwaitedはPromiseでない型に対してそのまま型を返すので私と同様の解答が本家に近いと考えられます。
【初級】If
問題の詳細はこちらです。
問題
条件値C、 Cが truthy である場合の戻り値の型T、Cが falsy である場合の戻り値の型Fを受け取るIfを実装します。 条件値C はtrueかfalseのどちらかであることが期待されますが、T と F は任意の型をとることができます。
例えば:
type A = If<true, 'a', 'b'>; // expected to be 'a'
type B = If<false, 'a', 'b'>; // expected to be 'b'
解答
type If<C extends boolean, T, F> = C extends true ? T : F;
解説
先ほどまでと比べるととても基本的で簡単な問題が出てきました。Cの方をチェックして、TとFで出し分けるようにしています。
4日目
【初級】Concat
問題の詳細はこちらです。
問題
JavaScript のArray.concat関数を型システムに実装します。この型は 2 つの引数を受け取り、受け取ったイテレータの要素を順に含む新しい配列を返します。
例えば:
type Result = Concat<[1], [2]>; // expected to be [1, 2]
解答
type Concat<T extends readonly any[], U extends readonly any[]> = [...T, ...U];
解説
配列において、スプレッド構文はtsでも有効ですから配列同様に書くことができます。そのため解答のようにシンプルに書くことができます。この問題では配列しか渡されないとして、引数で配列であることを検査しています。
【初級】Includes
問題の詳細はこちらです。
問題
JavaScriptのArray.include関数を型システムに実装します。この型は、2 つの引数を受け取り、trueやfalseを出力しなければなりません。
例えば:
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
解答
type Includes<T extends readonly any[], U> = T extends [infer V, ...infer W]
? Equal<V, U> extends true
? true
: Includes<W, U>
: false;
解説
最初に考えた形では動きませんでしたし、これまでの問題の中では一番難しく感じました。下のように解いたところ、
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
下のテストケースでは通りませんでした。
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
T[number]がUの一部であることだけを確認しているので、UがTに含まれていることが保証されていませんでした。つまりTの一部がUと完全に一致する必要がありました。
それを解決するために、Tの最初の値から順番に取って一致しているか比較して検査するようにしました。完全一致していることの実装は大変なのでテストで使っているものを拝借して行いました。以下のようなフローで解答を得ています。
- Tの中身が存在することを確認する
- 存在すればTを最初の値と残りの配列で分け、最初の値がUと完全一致するか確かめる
- 一致したらtrueを返す
- 一致しなければ残りの配列をTとして最初から行う
- 存在しなければfalseを返す
- 存在すればTを最初の値と残りの配列で分け、最初の値がUと完全一致するか確かめる
【初級】Push
問題の詳細はこちらです。
問題
Array.pushのジェネリックバージョンを実装します。
例えば:
type Result = Push<[1, 2], '3'> // [1, 2, '3']
解答
type Push<T extends readonly any[], U> = [...T, U];
解説
concatのUが値バージョンですね。Uをそのまま追加するので展開する必要はありません(できません)。Includesの難しさが際立ちます。
ちなみにですが、よく使う似たような型として以下のものがあります。
type NotEmptyArray<T> = [T, ...T[]]
形が似ているだけで、意味は全く違います。これは長さが1以上の中身の型がTである配列を表しています。最初にTがあることで0個の時に型エラーが起きるようになっています。先ほどの例でUを含むような配列にしたのと同様でこれはTを含むような配列を定義したからです。表現として知っているとどこかで使えると思います。
5日目
【初級】Unshift
問題の詳細はこちらです。
問題
Array.unshiftの型バージョンを実装します。
例えば:
type Result = Unshift<[1, 2], 0> // [0, 1, 2,]`
解答
type Unshift<T extends readonly any[], U> = [U, ...T];
解説
初級の問題もあと2問ですが、これまで解いてきて同難易度とされているものであっても難易度にかなり差があると感じました。
今回はその中で簡単に感じたものの一つです。前回と前後が逆になったもので、配列の最初にUを入れて後ろにTを展開しました。
【初級】Parameters
問題の詳細はこちらです。
問題
組み込みの型ユーティリティParameters<T>を使用せず、Tからタプル型を構築する型を実装します。
例えば:
const foo = (arg1: string, arg2: number): void => {}
type FunctionParamsType = MyParameters<typeof foo> // [arg1: string, arg2: number]
解答
type MyParameters<T extends (...args: any[]) => any> = T extends (...args: infer V) => any ? V : never;
解説
型推論でパラメータの型を取ってきて、それを返しています。この型に当てはまらない場合は関数ではないのでneverを返してます(その場合はそもそも受け取れないのでエラーになるが)。
【中級】Get Return Type
問題の詳細はこちらです。
問題
組み込みの型ユーティリティReturnType<T>を使用せず、Tの戻り値の型を取得する型を実装します。
例えば
const fn = (v: boolean) => {
if (v)
return 1
else
return 2
}
type a = MyReturnType<typeof fn> // should be "1 | 2"
解答
type MyReturnType<T> = T extends (...args: any) => infer U ? U : never;
解説
初の中級問題でした。ただ、内容は初級と何が違うかわかりませんでした(引数にextendsがないくらい?)。
一つ前の問題では、パラメータを推論して結果を返してましたが、この問題では返り値を推論して返すようにしています。
私の解答では関数以外の推論でもエラーにならないので引数にFunctionを追加して
type MyReturnType<T extends Function> = T extends (...args: any) => infer U ? U : never;
のようにしても良いかもしれません。
6日目
【中級】Omit
問題の詳細はこちらです。
問題
組み込みの型ユーティリティOmit<T, K>を使用せず、TのプロパティからKを削除する型を実装します。
例えば
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}
解答
type MyOmit<T, K extends keyof T> = { [P in Exclude<keyof T, K>]: T[P] };
解説
Excludeは既に実装済みなのでそれを用いて行いました。行なっていることはただのMapped Typesのkeyに制約を加えただけです。Exclude(外部に定義した型)を使わなければPの操作(除外や追加など)ができないため、操作する前に絞り出しました。tsでの実装もこのようになっています。
他の解答を除いてみたら以下のような解答がありました。
type MyOmit<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? never : Key]: T[Key]
};
Keyをas以降で再マッピングして行う方法です。これを用いれば確かに、外部に型を作る必要なく解答することができます。この機能はts4.1からのようなので以前のバージョンを使用する方は注意してください。
【中級】readonly
問題の詳細はこちらです。
問題
2つの型引数TとKを取るMyReadonly2<T, K>を実装します。
Kが指定されている場合は、Tの中のKのプロパティのみを読み取り専用にします。Kが指定されていない場合は、通常のReadonly<T>と同様に、すべてのプロパティを読み取り専用にします。
例えば
interface Todo {
title: string
description: string
completed: boolean
}
const todo: MyReadonly2<Todo, 'title' | 'description'> = {
title: "Hey",
description: "foobar",
completed: false,
}
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property
todo.completed = true // OK
解答
type MyReadonly2<T, K extends keyof T = keyof T> = {
readonly [P in keyof T] : T[P]
} & Omit<T,K>
解説
初めて自力では解けませんでした。難易度が上がったというよりかは知識不足ゆえだったと考えています。
前回の問題のように
type MyOmit<T, K extends keyof T> = {
[Key in keyof T as Key extends K ? readonly Key : Key]: T[Key]
};
と書けばできると考えていましたが、できませんでした。それもそのはずで、readonlyは[]の前に書く必要があるからです。
解答には他者の解答を貼りました。まずMapped Typesによって全ての中身がreadonlyなオブジェクトを作成します。そして今回readonlyにしない要素を取り出してそれとIntersection Typesを取ります。これらは同様のkeyとobjectがあったとき、片方がreadonlyそしてもう片方がreadonlyでなかったときreadonlyがないkeyとvalueとして型が生成されるのを活かしています。
問題に使った例を見て動きを確認してみます。
{ readonly [P in keyof T]: T[P] }
これをTodoに適当すると
type ReadonlyTodo = {
readonly title: string;
readonly description: string;
readonly completed: boolean;
};
となります。次に
Omit<T, K>
をTodoに適応すると
type OmitTodo = {
completed: boolean;
};
となります。この二つでIntersection Typesをとるので、title、descriptionはReadonlyTodoのまま、completedはOmitTodoに負けて、
type Todo = {
readonly title: string;
readonly description: string;
completed: boolean;
};
になります。もし、completedが元からreadonlyの場合はReadonlyTodoと同じ型になるので、元にreadonlyが含まれていても問題なく動作します。
【中級】Deep Readonly
問題の詳細はこちらです。
問題
オブジェクトのすべてのパラメーター(およびそのサブオブジェクトを再帰的に)を読み取り専用にするDeepReadonly<T>を実装します。
この課題ではオブジェクトのみを扱っていると想定してください。配列、関数、クラスなどは考慮する必要はありません。しかし、可能な限り様々なケースをカバーすることで、自分自身に挑戦することができます。
例えば
type X = {
x: {
a: 1
b: 'hi'
}
y: 'hey'
}
type Expected = {
readonly x: {
readonly a: 1
readonly b: 'hi'
}
readonly y: 'hey'
}
type Todo = DeepReadonly<X> // should be same as `Expected`
解答
type DeepReadonly<T> = {
readonly [Key in keyof T]: T[Key] extends
object
? T[Key] extends Function
? T[Key]
: DeepReadonly<T[Key]>
: T[Key]
};
解説
Mapped Typeの派生です。readonlyを付与できなくなるまで再起的に呼び出すような仕組みになっています。
keyはreadonlyに変換する部分以外は通常と何も変わりません。valueはobjectであれば、readonlyにするためにDeepReadonlyに突っ込んで、そうでなければそのまま返すようにしています。ここで一つ注意が必要です。objectはプリミティブ以外を指すので、関数や配列もobjectになります。そのためreadonlyにできない関数の場合はそのまま返すようにしています。そして、今回のテストケースでは問題になりませんでしたが、objectは関数や配列だけでなく正規表現なども含みます。そのためこれをアプリの開発環境で振り回すのは避けるべきだと考えられます。
7日目
【中級】Tuple of Union
問題の詳細はこちらです。
問題
タプルの値からユニオン型を生成するTupleToUnion<T>を実装します。
例えば
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
解答
type TupleToUnion<T extends any[]> = T extends [infer U, ...infer V] ? U | TupleToUnion<V> : never;
解説
解答後に複雑なことをしていることに気づきました。中級ですが、以下のように簡単に書くことができました。
type TupleToUnion<T extends any[]> = T[number];
解答に書いた方は、先頭から一つずつ再起的に取ってくるやり方です。
【中級】Chainable Options
問題の詳細はこちらです。
問題
JavaScript では、チェイン可能なオプションがよく使われます。しかし、TypeScript に切り替えたとき、正しく型を付けることができますか?
この課題では、オブジェクトでもクラスでも何でもいいので、 option(key, value) と get() の 2 つの関数を提供する型を定義してください。option では、与えられたキーと値を使って現在の config の型を拡張できます。最終的な結果は get で取得することにしましょう。
例えば
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
// expect the type of result to be:
interface Result {
foo: number
name: string
bar: {
value: string
}
}
この問題を解くために js/ts のロジックを書く必要はありません。型レベルのロジックだけを書いてください。
key は string のみを受け付け、value は任意の型を受け付けると仮定しても構いません。同じ key が 2 回渡されることはありません。
解答
type Chainable<T extends object = {}> = {
option<K extends string, V>(
key: K,
value: V
): Chainable<{ [key in K]: V } & Omit<T, K>>;
get(): T;
};
解説
問題や設定の多さからも読み取れるようにかなり難易度が上がったと思います。形式としてはoptionを呼び出すたびにオブジェクトを更新して行き、getが呼び出された時に最新状態のオブジェクトを返すようになっています。getで返す値はChainableの引数となるように書き、初期値は空のオブジェクトを渡しています。
optionの実装について説明します。optionはkey
とvalue
をそれぞれKとVとして受け取ります。そしてoptionの返り値として、再度optionやgetを呼び出せるようにChainableを返しています。Chainableの中身はKをキー、VをバリューにするオブジェクトとChainalbeに渡されたTからKがkeyのものを消したものです。つまりKをkeyとする古い値が存在した場合それを削除して、新たに追加されたkeyに置き換えています。これによってこの中身が更新されたオブジェクトの型となり、次のChainableのgetで渡された値を返すだけで良くなります。
【中級】Last of Array
問題の詳細はこちらです。
問題
配列 T を受け取り、その最後の要素の型を返す汎用的な Last<T> を実装してください。
例えば
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1
解答
type Last<T extends any[]> =T extends [...any, infer U] ? U : never;
解説
先ほどの問題と比べると、随分簡単になりました。
配列の最後の値を推測して取ってきています。
8日目
【中級】Pop
問題の詳細はこちらです。
問題
配列 T を受け取り、最後の要素を除いた配列を返す汎用的な Pop<T> を実装してください。
例えば
type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]
type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]
おまけ: 同様に Shift、 Push、 Unshift も実装できますか?
解答
type Pop<T extends any[]> = T extends [...infer U, any] ? U : [];
解説
実質4問出されました。今日は2倍解くことになりそうです。
配列の長さが1以上であれば最後を除いた配列を推論して返し、0であれば空配列を返すようにしています。
同様にShiftは
type Shift<T extends any[]> = T extends [any, ...infer U] ? U : never;
Pushは
type Push<T extends any[], U> = [...T, U];
Unshiftは
type Unshift<T extends any[], U> = [U, ...T];
【中級】Promise.all
問題の詳細はこちらです。
問題
Promise ライクなオブジェクトの配列を受け取る関数 PromiseAll に型を付けてください。戻り値は Promise<T> である必要があります。ここで、T は解決された結果の配列です。
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)
解答
declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{[P in keyof T]: Awaited<T[P]>}>
解説
valuesに渡される引数とTのやりとりが難しく、手こずりました。
valuesに渡された配列からreadonlyを取ったものをTとして定義します。そしてPromiseAllの返り値としてTをPromiseで囲んで返します。これで多くの場合問題ありませんが、Tの一部にPromiseが含まれていた場合うまくいきません。そこでtsに実装されているAwaitedを用いることでPromiseが含まれていたとしてもその中身を出力できるようにしています。
【中級】Type Lookup
問題の詳細はこちらです。
問題
Union 型から特定の型を属性を使って取得したいことがあります。
この課題では、Cat | Dog という Union 型に共通する type というフィールドを使って、対応する型を取得します。つまり、以下の例のように、 LookUp<Dog | Cat, 'dog'> の場合は Dog を、LookUp<Dog | Cat, 'cat'> の場合は Cat を取得することになります。
interface Cat {
type: 'cat'
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
interface Dog {
type: 'dog'
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
color: 'brown' | 'white' | 'black'
}
type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
解答
type LookUp<U, T> = U extends { type: T } ? U : never;
解説
これまで解いてきた問題で一番、実用的な問題が出てきました。
Uがtypeをkeyとして、Tをバリューにする値を持っていた場合はUをそのまま返し、持っていなければneverを返しています。これによって絞り込みが可能となります。
9日目
【中級】Trim Left
問題の詳細はこちらです。
問題
文字列を受け取り、先頭の空白を削除した新しい文字列を返す TrimLeft<T> を実装します。
例えば
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
解答
type Trash = ' ' | '\n' | '\t';
type TrimLeft<S extends string> = S extends `${Trash}${infer s}` ? TrimLeft<s> : S;
解説
まず、ゴミを定義します。ゴミとは空白とタブ文字列と改行文字列を指します。
そして、先頭にゴミがあったときはそれを除いて再起的に検査します。最終的に先頭にゴミがなくなったら受け取った文字列をそのまま返します。当たり前ですが、アプリなどの運用で使うときはTrashではなくWhiteSpaceなどの命名にすべきだと思います。
【中級】Trim
問題の詳細はこちらです。
問題
文字列を受け取り、両端の空白を削除した新しい文字列を返す Trim<T> を実装します。
例えば
type trimed = Trim<' Hello World '> // expected to be 'Hello World'
解答
type Trash = ' ' | '\n' | '\t';
type Trim<S extends string> = S extends `${Trash}${infer s}` ? Trim<s> : S extends `${infer s}${Trash}` ? Trim<s> : S;
解説
先ほどの先頭に加えて後方もやるようにしました。前がOKだったら後ろからも検査するようにしています。
【中級】Capitalize
問題の詳細はこちらです。
問題
文字列の最初の文字を大文字に変換し、それ以外はそのままにする Capitalize<T> を実装します。
例えば
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
解答
type MyCapitalize<S extends string> = S extends `${infer T}${infer U}` ? `${Uppercase<T>}${U}` : S;
解説
問題としては優しい部類だったと思いますが、Uppercase
を知らなかったので調べて解答しました。
文字列の先頭を取ってきてそれをUpperCaseで大きくして元に戻しています。
Uppercase
の方を見てみると
type Uppercase<S extends string> = intrinsic;
となっています。intrinsicはコンパイラの内部に書かれた、実装が見えない型に割り振られるキーワードです。
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;
この似たような操作として三つも同じ型に指定されています。
10日目
【中級】Replace
問題の詳細はこちらです。
問題
文字列Sに含まれる文字FromをToに一度だけ置き換える型Replace<S, From, To>
を実装します。
例えば
type replaced = Replace<'types are fun!', 'fun', 'awesome'>; // expected to be 'types are awesome!'
解答
type Replace<S extends string, From extends string, To extends string> =
S extends `${infer T}${From}${infer U}`
? From extends ''
? S
: `${T}${To}${U}`
: S;
解説
まず、Fromと一致する文字列が含まれている場合、その最初の位置以前Tと以降Uで分けます。そしてそれをToを真ん中に挟み込むことでFromをToに置き換えた文字列が完成します。問題は空文字列のときで、この場合は何も変えないということなので、そのままSを返すようにしました。
【中級】Replace All
問題の詳細はこちらです。
問題
文字列Sに含まれる部分文字列FromをToに置き換える型ReplaceAll<S, From, To>
を実装します。
例えば
type replaced = ReplaceAll<'t y p e s', ' ', ''>; // expected to be 'types'
解答
type ReplaceAll<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer T}${From}${infer V}`
? `${T}${To}${ReplaceAll<`${V}`, From, To>}`
: S;
解説
まず、前の問題と異なり先にFromが''
であった場合を先に弾いています。後の再起処理で分岐させるよりも本質とはズレた不要な条件は先に弾いていた方が考慮がなくなるのでそうしました。さてFromが''
でなかった時ですが、Fromが含まれているか否かを考えています。Fromが含まれていなければそのまま返すようにしています。Fromが含まれていた場合はTとToを外に出して再起的に置き換えしていきます。TとToを外に出したのはfoobarのような文字列があったときにobをbに置き換えた時にfobarではなく、fbarになるのを避けるためです。つまり検査済みの文字列を再検査させないためというわけです。
お得意の過去の問題を再起的にするだけかと思いましたが、TとToを外に出す処理に学びがありました。
【中級】Append Argument
問題の詳細はこちらです。
問題
与えられた関数型 Fn と任意の型 A に対して、第一引数に Fn を取り、第二引数に A を取り、Fn の引数に A を追加した関数型 G を生成します。
例えば、
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
// expected be (a: number, b: string, x: boolean) => number
解答
type AppendArgument<Fn extends Function, A> =
Fn extends (...args: infer T) => infer U
? (...args: [...T, A]) => U
: never;
解説
関数の引数と返り値を推論して、引数にAを追加したものを返却します。
11日目
【中級】Permutation
問題の詳細はこちらです。
問題
Union 型を Union 型の値の順列を含む配列に変換する順列型を実装します。
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, K=T> =
[T] extends [never]
? []
: K extends K
? [K, ...Permutation<Exclude<T, K>>]
: never
解説
難しくて答えられなかったです。このような問題を解けるようになりたいです。
解答に使用したのはこちらのissueで、解説も丁寧に載っていました。
まず最初の部分が[T] extends [never]
です。これによってTがneverであることを保証しているようです(T[] extends never[]
でも良いです)。ちなみにT extends never ? true : false
はTがneverであってもfalse扱いとなります。これはextendsの仕様のせいです。extendsは例えばT extends U
のような形の時、Tをユニオン型として扱います。これはTが'A'|'B'
の時'A' extends U | 'B' extends U
と分けるためです。そのためneverは'a'|never
が'A'
となるようにnever|
は型なしになってしまうことを考慮するとT extends never ? true : false
はextends never
となり当てはまるものがなくなるのでfalseになってしまうのです。
続いてK extends K
です。例えば1|2|3
をKとすると先ほどの説明を考慮すると1 extends 1|2|3
、2 extends 1|2|3
、3 extends 1|2|3
となります。そしてこの場合確実に含まれるのでそれぞれ[1, ...Permutation<Exclude<T, 1>>]
、[2, ...Permutation<Exclude<T, 2>>]
、[3, ...Permutation<Exclude<T, 3>>]
が呼び出されます。そうして繰り返していくと、、、解答を得ることができます。
【中級】Length of String
問題の詳細はこちらです。
問題
String#length と同じように、文字列リテラルの長さを計算します。
解答
type LengthOfString<S extends string, SArr extends string[] = []> =
S extends `${infer T}${infer Rest}`
? LengthOfString<Rest, [T, ...SArr]>
: SArr['length'];
解説
配列のようにS['length']で取得はできませんでした。ただ、配列の長さは取得できることを利用して文字列を一文字ずつ配列に埋め込んでできた配列の長さを測ることで解答できると思いました。
配列の作成は文字列の先頭を一つずつ取ってきて、LengthOfStringに追加したSArrを成長させることで実装しています(初期値は空配列です)。最終的に取得できる文字列がなくなったときに、SArrのlenghtを取得して解答しています。
【中級】Flatten
問題の詳細はこちらです。
問題
この課題では、受け取った配列をフラット化した型を出力する型を書く必要があります。
例えば:
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]> // [1, 2, 3, 4, 5]
解答
type Flatten<T extends any[]> =
T extends [infer U, ...infer V]
? U extends any[]
? [...Flatten<U>, ...Flatten<V>]
: [U, ...Flatten<V>]
: [];
解説
典型的な再起の形でした。
まず、Tの中身があるか確認します。なければ空配列を返しています。あればさらに分岐に進んでいきます。配列の最初の要素を取ってきて、それが配列であればそれを平坦にするため再度Flattenにかけます。そして、残った配列Vの平坦化も必要なのでそっちにもFlattenをかけて、両者の配列を返します。最初の要素が配列でなければこれ以上平坦化できないので、最初の要素と残った配列Vを平坦化したものの配列を返します。
12日目
【中級】Append to Object
問題の詳細はこちらです。
問題
インターフェースに新しいフィールドを追加する型を実装します。この型は、3 つの引数を受け取り、新しいフィールドを持つオブジェクトを出力しなければなりません。
例えば、
type Test = { id: '1' };
type Result = AppendToObject<Test, 'value', 4>; // expected to be { id: '1', value: 4 }
解答
type AppendToObject<T, U extends keyof any, V> = { [P in U | keyof T]: P extends keyof T ? T[P]: V };
解説
TというオブジェクトにUをkey、Vをvalueとして追加する問題です。Uは何かのキーとなるような型としました。Mapped Typesによってオブジェクトを組み直すのですが、keyにはTのキーとUのユニオン型を取ります。そして、valueはPがTのキーの場合はT[P]Uの場合はVを与えるようにしました。
【中級】Absolute
問題の詳細はこちらです。
問題
srting, number または bigint を受け取り、正の数を出力する Absolute 型を実装します。
例えば
type Test = -100;
type Result = Absolute<Test>; // expected to be "100"
解答
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer U}` ? U : `${T}`;
解説
bigintの扱いがわからなかったのでnumberとstringで動くように作ったところbigintも動きました。まず、Tをstring化します。numberはそのまま、bigintはnumberのように形を変えてstringになります。マイナスを取らなければいけないので-があれば-を取ったもの、マイナスがなければstring化したものをそのまま返します。
【中級】String to Union
問題の詳細はこちらです。
問題
受け取った文字列を Union 型に変換する型を実装します。
例えば
type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
解答
type StringToUnion<T extends string> = T extends `${infer U}${infer V}` ? U | StringToUnion<V> : never
解説
少し前に解いたTuple to Unionとやっていることはほとんど同じです。あまりに似ていたので問題を間違えたか不安になりました。
先頭から文字を一つずつ取ってきて、それ以降の文字列をさらにStringToUnionを呼び出して両者をユニオン型として取得します。これを繰り返すと完成します。
13日目
【中級】Merge
問題の詳細はこちらです。
問題
2 つの型をマージして新しい型を作ります。2 つ目に指定した型のキーは 1 つ目の型のキーを上書きします。
例えば
type foo = {
name: string;
age: string;
}
type coo = {
age: number;
sex: string
}
type Result = Merge<foo,coo>; // expected to be {name: string, age: number, sex: string}
解答
type Merge<F, S> = {
[P in keyof F | keyof S]: P extends keyof S
? S[P]
: P extends keyof F
? F[P]
: never
};
解説
Mapped TypesでMerge後のオブジェクトを作ります。keyはFとSのキーのユニオンとします。そして、2つ目のキーが一つ目を上書きするという設定から、先にPがSのキーであるか確かめてSのキーだったらS[P]をvalueとします。次に、PがFのキーだったらF[P]をvalueにするようにします。そんなことはないですが、どちらのキーでもなければneverとします。
【中級】KebabCase
問題の詳細はこちらです。
問題
キャメルケースもしくはパスカルケースの文字列を、ケバブケースに置換する方を実装します。
FooBarBaz -> foo-bar-baz
例えば
type FooBarBaz = KebabCase<"FooBarBaz">;
const foobarbaz: FooBarBaz = "foo-bar-baz";
type DoNothing = KebabCase<"do-nothing">;
const doNothing: DoNothing = "do-nothing";
解答
type Repeat<S> = S extends `${infer T}${infer U}`
? T extends Capitalize<T>
? T extends Uncapitalize<T>
? `${T}${Repeat<U>}`
: `-${Uncapitalize<T>}${Repeat<U>}`
: `${T}${Repeat<U>}`
: S;
type KebabCase<S> = S extends `${infer T}${infer U}` ? `${Uncapitalize<T>}${Repeat<U>}` : '';
解説
型Repeatはいい感じの命名ができませんでした。
まずケバブケースにするにあたって二つの場所に分けられます。それはコードからもわかるとおり一文字目とそれ以降です。一文字目は大文字であった場合小文字にするだけですが、二文字目以降は大文字であった場合先頭に-
をつけて小文字にする必要があります。そのため、KebabCaseでは一文字目にだけ対応させて二文字目以降はRepeatに操作させます。
KebabCaseでは最初の文字を小文字にして、残りの部分をRepeatに渡しています。Repeatでは先頭文字が大文字でなければそのまま後続の文字をRepeatに渡してループさせます。大文字であればそれが同時に小文字としても扱われないかチェックして、扱われなければ-
と小文字にして後続の文字列をRepeatに私てループさせています。
【中級】Diff
問題の詳細はこちらです。
問題
Get an Object that is the difference between O & O1
解答
type Diff<O, O1> = {
[P in
| Exclude<keyof O, keyof O1>
| Exclude<keyof O1, keyof O>]: P extends keyof O
? O[P]
: P extends keyof O1
? O1[P]
: never;
};
解説
問題文が英語で面食らいましたが、難易度としては高くなかったように感じました。
Mapped Typesでオブジェクトを作成しており、keyにはOに含まれないO1のキーとO1に含まれないOのキーのユニオン型を取りました。そしてvalueにはそれぞれを持つOまたはO1から抜き出しました。
14日目
【中級】AnyOf
問題の詳細はこちらです。
問題
Implement Python liked any function in the type system. A type takes the Array and returns true if any element of the Array is true. If the Array is empty, return false.
For example:
type Sample1 = AnyOf<[1, '', false, [], {}]> // expected to be true.
type Sample2 = AnyOf<[0, '', false, [], {}]> // expected to be false.
解答
type Any = 0 | '' | false | undefined | null | [] | {[key: string]: never};
type AnyOf<T extends readonly any[]> = T[number] extends Any ? false : true;
解説
前回の問題から英語ですので今後は英語でしか問題がないのかもしれません。
配列の中身が一つでもtruthy(真)だったらtrueを返すような型です。そのため最初にfalsyな型を定義して、それと同じものだけで構成されていたらfalseが買えるようにしました。
【中級】IsNever
問題の詳細はこちらです。
問題
Implement a type IsNever, which takes input type T. If the type of resolves to never, return true, otherwise false.
For example:
type A = IsNever<never> // expected to be true
type B = IsNever<undefined> // expected to be false
type C = IsNever<null> // expected to be false
type D = IsNever<[]> // expected to be false
type E = IsNever<number> // expected to be false
解答
type IsNever<T> = T[] extends never[] ? true: false;
解説
以前パーミュテーションを求める話で解説した通りです。[T] extends [never] ? true : false
でも同様に通りますが、T extends never ? true : false
は通りません。これはT extends U
の形が常にユニオン型のように扱うからで、neverをユニオン型としてみるとemptyになるからです。
【中級】IsUnion
問題の詳細はこちらです。
問題
Implement a type IsUnion, which takes an input type T and returns whether T resolves to a union type.
For example:
type case1 = IsUnion<string> // false
type case2 = IsUnion<string|number> // true
type case3 = IsUnion<[string|number]> // false
解答
type IsUnion<T, U=T> =
T[] extends never[]
? false
: T extends T
? U[] extends T[]
? false
: true
: never;
解説
neverは扱いが大変なので、まず前回同様の方法で検知してfalseを返します。次にT extends T
とすることで、Tがユニオンの場合複数の条件に分離させます。その後、Tの一部はUの一部であるがUはTの一部の一部ではないことを活かして、U[] extends T[]
と判定します。で一致すればfalse、一致しなければTがユニオンとしてtrueを返すようにしました。
15日目
【中級】ReplaceKeys
問題の詳細はこちらです。
問題
Implement a type ReplaceKeys, that replace keys in union types, if some type has not this key, just skip replacing, A type takes three arguments.
For example:
type NodeA = {
type: 'A'
name: string
flag: number
}
type NodeB = {
type: 'B'
id: number
flag: number
}
type NodeC = {
type: 'C'
name: string
flag: number
}
type Nodes = NodeA | NodeB | NodeC
type ReplacedNodes = ReplaceKeys<Nodes, 'name' | 'flag', {name: number, flag: string}> // {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string} // would replace name from string to number, replace flag from number to string.
type ReplacedNotExistKeys = ReplaceKeys<Nodes, 'name', {aa: number}> // {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never
解答
type ReplaceKeys<U, T, Y> = U extends any ? {
[key in keyof U]:
key extends T
? key extends keyof Y
? Y[key]
: never
: U[key];
} : never;
解説
問題が長ったらしいですが、解答は簡単でした。まず、お得意の方法U extends
でユニオン型を分割します。Uが何かによって条件分けをする必要がないのでanyとしています。そしてMapped Typesで解答のオブジェクトを作成します。keyは変わらないのでUのキーから構成して、キーがTに含まれていれば、Yからそのキーの値を取得してvalueとします(Yになければneverです)。キーがTに含まれていなければUの値をそのまま返します。
【中級】Remove Index Signature
問題の詳細はこちらです。
問題
Implement RemoveIndexSignature<T> , exclude the index signature from object types.
For example:
type Foo = {
[key: string]: any;
foo(): void;
}
type A = RemoveIndexSignature<Foo> // expected { foo(): void }
解答
type RemoveIndexSignature<T> = {
[P in keyof T as string extends P
? never
: number extends P
? never
: symbol extends P
? never
: P]: T[P];
};
解説
keyがstring、number、symbolのいずれかだったらneverをキーとし、それ以外だったらそのままPをkeyとしてT[P]をvalueとして返しています。
【中級】Percentage Parser
問題の詳細はこちらです。
問題
Implement PercentageParser. According to the /^(+|-)?(\d*)?(%)?$/ regularity to match T and get three matches.
The structure should be: [plus or minus, number, unit] If it is not captured, the default is an empty string.
For example:
type PString1 = ''
type PString2 = '+85%'
type PString3 = '-85%'
type PString4 = '85%'
type PString5 = '85'
type R1 = PercentageParser<PString1> // expected ['', '', '']
type R2 = PercentageParser<PString2> // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3> // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4> // expected ["", "85", "%"]
type R5 = PercentageParser<PString5> // expected ["", "85", ""]
解答
type Sign = '+' | '-';
type Percentage = '%';
type PercentageParser<A extends string> =
A extends `${infer T}${Percentage}`
? T extends `${infer U}${infer V}`
? U extends Sign
? [U, V, Percentage]
: ['', T, Percentage]
: ['', T, Percentage]
: A extends `${infer T}${infer U}`
? T extends Sign
? [T, U, '']
: ['', `${T}${U}`, '']
: ['', '', ''];
解説
まず、+と-のユニオンと%を別で定義します(%は単体なので準備する必要はないです)。そしていい感じに分岐を作ります。順番はPercentageの有無で分岐させ、signの有無でさらに分岐させ、残ってたのを真ん中に入れると言う風にしました。もっと綺麗な方法もありそうですが、他の解答は誤っているものが多く尚且つ綺麗なものはありませんでした。
16日目
【中級】Drop Char
問題の詳細はこちらです。
問題
Drop a specified char from a string.
For example:
type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'
解答
type DropChar<S extends string, C extends string> =
C extends ''
? S
: S extends `${infer T}${C}${infer U}`
? `${T}${DropChar<U, C>}`
: S;
解説
Cが空文字列である時はそのままSを返して、それ以外の時はちゃんと検査します。まず、SにCが含まれていることを確認し、含まれていなければそのままSを返します。Cが含まれていれば先頭の要素を取得して、それ以前の文字列をTそれ以降の文字列をUとします。TにはCが必ず含まれていないのに対してUは未知数なので、UをDropCharにUを渡して再検査します。そしてTと再検査したUを合成したものを解答としています。UをDropCharで再検査しなければ先頭の要素ひとつのみを消すものになります。
【中級】Minus One
問題の詳細はこちらです。
問題
Given a number (always positive) as a type. Your type should return the number decreased by one.
For example:
type Zero = MinusOne<1> // 0
type FiftyFour = MinusOne<55> // 54
解答
type ParseInt<T extends string> = T extends `${infer Digit extends number}` ? Digit : never
type ReverseString<S extends string> = S extends `${infer First}${infer Rest}` ? `${ReverseString<Rest>}${First}` : ''
type RemoveLeadingZeros<S extends string> = S extends '0' ? S : S extends `${'0'}${infer R}` ? RemoveLeadingZeros<R> : S
type InternalMinusOne<
S extends string
> = S extends `${infer Digit extends number}${infer Rest}` ?
Digit extends 0 ?
`9${InternalMinusOne<Rest>}` :
`${[9, 0, 1, 2, 3, 4, 5, 6, 7, 8][Digit]}${Rest}`:
never
type MinusOne<T extends number> = ParseInt<RemoveLeadingZeros<ReverseString<InternalMinusOne<ReverseString<`${T}`>>>>>
解説
久しぶりに解けない問題でした。
初め以下のように解答していました。Tの数だけ配列を追加して行って、最後に配列の長さを返しています。常に一つ後の配列と比較していたので、最後に返す配列の長さは-1されたものとなる分けです。
type MinusOne<T extends number, P extends never[] = []> =
[never, ...P]['length'] extends T
? P['length']
: MinusOne<T, [never, ...P]>;
しかし、MinusOne<1001>
のテストでは
Type instantiation is excessively deep and possibly infinite.
となってしましました。ループが深すぎるのが原因ですかね。
解答ではまず、数値を文字列化して逆順にしています。その後、最初の文字列を抜き出してまた数値に戻します。その数値を[9, 0, 1, 2, 3, 4, 5, 6, 7, 8]に対応したものに置き換えます。これによって一番下のくらいの数値を引き算しています。もし、0であれば繰り下がりを考慮しなければならないので次の桁にも同様の操作をします(これは繰り返されます)。さて、これによって引き算ができたので、元の数値順に戻すため再度逆順にします。そしてそれを数値にすれば完了と言いたいところですがその前に、先頭の0を省いています。10などを引いた時に09となることを防ぐためです。これによってMinusOneが完成します。
例 100の場合の操作
100 => '100' => '001' => '901' => '991' => '990' => '099' => '99' => 99
【中級】PickByType
問題の詳細はこちらです。
問題
From T, pick a set of properties whose type are assignable to U.
For Example
type OnlyBoolean = PickByType<{
name: string
count: number
isReadonly: boolean
isEnable: boolean
}, boolean> // { isReadonly: boolean; isEnable: boolean; }
解答
type PickByType<T, U> = {
[P in keyof T as U extends T[P] ? P : never]: T[P]
};
解説
Tの値でUと一致するものだけ返すようにしています。
17日目
【中級】StartsWith
問題の詳細はこちらです。
問題
Implement StartsWith<T, U> which takes two exact string types and returns whether T starts with U
For example
type a = StartsWith<'abc', 'ac'> // expected to be false
type b = StartsWith<'abc', 'ab'> // expected to be true
type c = StartsWith<'abc', 'abcd'> // expected to be false
解答
type StartsWith<T extends string, U extends string> = T extends `${U}${any}` ? true : false;
解説
先頭にUがマッチしたらtrueしなかったらfalseを返しました。
【中級】EndsWith
問題の詳細はこちらです。
問題
Implement EndsWith<T, U> which takes two exact string types and returns whether T ends with U
For example:
type a = EndsWith<'abc', 'bc'> // expected to be true
type b = EndsWith<'abc', 'abc'> // expected to be true
type c = EndsWith<'abc', 'd'> // expected to be false
解答
type EndsWith<T extends string, U extends string> = T extends `${any}${U}` ? true : false;
解説
末尾にUがマッチしたらtrueしなかったらfalseを返しました。
【中級】PartialByKeys
問題の詳細はこちらです。
問題
Implement a generic PartialByKeys<T, K> which takes two type argument T and K.
K specify the set of properties of T that should set to be optional. When K is not provided, it should make all properties optional just like the normal Partial<T>.
For example
interface User {
name: string
age: number
address: string
}
type UserPartialName = PartialByKeys<User, 'name'> // { name?:string; age:number; address:string }
解答
type MappedType<T> = {[P in keyof T]: T[P]};
type PartialByKeys<T, K extends keyof T = keyof T> = MappedType<Omit<T,K> & Partial<Pick<T,K>>>;
解説
今日の問題はとても簡単だったのでここで難易度が高い問題が出てきて安心しました。
まず与えられたTをオブジェクトに展開し直すMappedType型を用意します。
次に、Omitを使ってoptional化しないものとPickを使ってoptional化するものを分けて取得します。Pickで抜き取った方はPartialを利用してによってoptional化します。そしてこれらをIntersection Typesにかけます。このままでは綺麗なObjectにならないので、最初に作ったMappedTypeをかけて整形します。最後の整形部分のひと工夫がかなりポイントです。
18日目
【中級】RequiredByKeys
問題の詳細はこちらです。
問題
Implement a generic RequiredByKeys<T, K> which takes two type argument T and K.
K specify the set of properties of T that should set to be required. When K is not provided, it should make all properties required just like the normal Required<T>.
For example
interface User {
name?: string
age?: number
address?: string
}
type UserRequiredName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }
解答
type MappedType<T> = { [P in keyof T]: T[P] };
type RequiredByKeys<T, K extends keyof T | unknown = keyof T> = MappedType<
Omit<T, Extract<keyof T, K>> & Required<Pick<T, Extract<keyof T, K>>>
>;
解説
前回の問題PartialByKeysとほとんど同じですね。異なる箇所はKにneverが入る箇所です。その場合Omit<T, K>
やPcik<T, K>
が機能しないのでExtractでTのkeyだけ取り出しておこないました。RequiredにしないものはK以外のものがキーのTなのでOmitで弾いて、RequiredにするものはKがキーのTなのでPickで取得して、Requiredにしています。それらをInsertsection Typesで取ってオブジェクトを作り直しています。
【中級】Mutable
問題の詳細はこちらです。
問題
Implement the generic Mutable<T> which makes all properties in T mutable (not readonly).
For example
interface Todo {
readonly title: string
readonly description: string
readonly completed: boolean
}
type MutableTodo = Mutable<Todo> // { title: string; description: string; completed: boolean; }
解答
type Mutable<T extends object> = {
-readonly [P in keyof T]: T[P]
}
解説
とりあえずオブジェクトで動くように作ったところarrayもこれでreadonlyを外せました。その後、stringなどのプリミティブな値ではエラーが起きるようにobjectを受け取るようにしました。
本質を捉えた考察ではないですが、Readonlyの実装は
type Readonly<T> = { readonly [P in keyof T]: T[P]; }
となっているので、これでarrayをreadonlyを変換できるのであれば、上記のMutableもできると考えられます。また、配列はkeyがnumberのオブジェクトと考えればこの操作を行えることも妥当と考えられます。
【中級】OmitByType
問題の詳細はこちらです。
問題
From T, pick a set of properties whose type are not assignable to U.
For Example
type OmitBoolean = OmitByType<{
name: string
count: number
isReadonly: boolean
isEnable: boolean
}, boolean> // { name: string; count: number }
解答
type OmitByType<T, U> = {
[P in keyof T as T[P] extends U ? never : P]: T[P]
};
解説
T[P]がUと一致したときのみneverを返して含まれないようにしています。
19日目
【中級】ObjetEntries
問題の詳細はこちらです。
問題
Implement the type version of Object.entries
For example
interface Model {
name: string;
age: number;
locations: string[] | null;
}
type modelEntries = ObjectEntries<Model> // ['name', string] | ['age', number] | ['locations', string[] | null];
解答
type ObjectEntries<T, U = keyof T> = U extends keyof T
? [U, T[U] extends infer V | undefined ? V : T[U]]
: never;
解説
テストケースが個人的には疑問でした(汎化した型を作るという面では良いかもしれないが)。
お得意のextendsでTのキーを分離させます。そして、分離したUをkeyとして、T[U]がvalueとなるような配列を作っておしまいです。valueはテストケースが通るようにいじりました。undefinedと何か他の方のユニオン型の時はundefinedを省いた方を送るようにしています。Partialなオブジェクトのテストケースの対策です。この部分には不満が残りますが、他者の回答も似たようなものか、考慮されていない状態でテストは通らなかったためこのようにしました。
【中級】Shift
問題の詳細はこちらです。
問題
Implement the type version of Array.shift
For example
type Result = Shift<[3, 2, 1]> // [2, 1]
解答
type Shift<T extends readonly any[]> = T extends [any, ...infer U] ? U : [];
解説
これ8日目にやったような気がするんですが、気のせいですかね。
8日目のものを改造しました。readonlyを追加して、あとは空配列が渡った時にneverを返していたので空配列を渡すようにしました。
【中級】Tuple to Nested Object
問題の詳細はこちらです。
問題
Given a tuple type T that only contains string type, and a type U, build an object recursively.
type a = TupleToNestedObject<['a'], string> // {a: string}
type b = TupleToNestedObject<['a', 'b'], number> // {a: {b: number}}
type c = TupleToNestedObject<[], boolean> // boolean. if the tuple is empty, just return the U type
解答
type TupleToNestedObject<T extends any[], U> = T extends [infer V, ...infer W]
? V extends string
? { [P in V]: TupleToNestedObject<W, U> }
: never
: U;
解説
浅い部分から順番に作っていくように作りました。Tから先頭の要素を一枚ずつ剥がしていって、Tが空になるまでTupleToNestedObjectがvalueになるようにしてループさせました。Tが空になったらUを返して終わりにしました。途中にあるV extends stringですが、Mapped Typesを構築するために、Vがユニオンになるように書きました。他の解答を見てみると
type TupleToNestedObject<T extends string[], U> = T extends [
infer V extends string,
...infer W extends string[],
]
? { [P in V]: TupleToNestedObject<W, U> }
: U;
のようにinfer後に行っても問題なくできるようです。
20日目
【中級】Reverse
問題の詳細はこちらです。
問題
Implement the type version of Array.reverse
For example:
type a = Reverse<['a', 'b']> // ['b', 'a']
type b = Reverse<['a', 'b', 'c']> // ['c', 'b', 'a']
解答
type Reverse<T> = T extends [...infer U, infer V] ? [V, ...Reverse<U>] : [];
解説
後ろのものから空になるまで取得して再度配列を作っています。
type Reverse<T> = T extends [infer U, ...infer V] ? [...Reverse<V>, U] : [];
このような解答もあって、確かになと思いました。
【中級】Flip Arguments
問題の詳細はこちらです。
問題
Implement the type version of lodash's _.flip.
Type FlipArguments<T> requires function type T and returns a new function type which has the same return type of T but reversed parameters.
For example:
type Flipped = FlipArguments<(arg0: string, arg1: number, arg2: boolean) => void>
// (arg0: boolean, arg1: number, arg2: string) => void
解答
type Reverse<T> = T extends [...infer U, infer V] ? [V, ...Reverse<U>] : [];
type FlipArguments<T extends Function> = T extends (...args: infer U) => infer W
? (...args: Reverse<U>) => W
: never;
解説
Reverseの次の問題だったので解きやすかったです。Tから引数を取ってきてそれを反転させたもので関数を作って返しました。
【中級】FlattenDepth
問題の詳細はこちらです。
問題
Recursively flatten array up to depth times.
For example:
type a = FlattenDepth<[1, 2, [3, 4], [[[5]]]], 2> // [1, 2, 3, 4, [5]]. flattern 2 times
type b = FlattenDepth<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, [[5]]]. Depth defaults to be 1
If the depth is provided, it's guaranteed to be positive integer.
解答
type FlattenDepth<
T extends any[],
U extends number = 1,
V extends never[] = [],
> = T extends [infer W, ...infer X]
? W extends any[]
? V['length'] extends U
? [W, ...FlattenDepth<X, U, V>]
: [...FlattenDepth<W, U, [never, ...V]>, ...FlattenDepth<X, U, V>]
: [W, ...FlattenDepth<X, U, V>]
: [];
解説
まずTが空配列かどうかチェックしつつ、先頭とそれ以降で分けます。空の時は空配列を返してあげます。次に先頭が配列でなかった場合Flatにしようがないのでそれ以降の配列をFllatenDepthにかけます。配列だった場合は、Vの長さが指定のFlat回数Uと一致しているか確認します。Vは再起する際に渡すFlat数です。すでに指定された回数Flat指定ればそれ以降の配列をFlattenDepthにかけます。まだ足りない場合はVを追加して先頭の要素をFlattenDepthにかけます。同時にそれ以降の配列もFlattenDepthにかけます。これによって最終的にU回Flatされた配列ができます。
21日目
【中級】BEM style string
問題の詳細はこちらです。
問題
The Block, Element, Modifier methodology (BEM) is a popular naming convention for classes in CSS.
For example, the block component would be represented as btn, element that depends upon the block would be represented as btn__price, modifier that changes the style of the block would be represented as btn--big or btn__price--warning.
Implement BEM<B, E, M> which generate string union from these three parameters. Where B is a string literal, E and M are string arrays (can be empty).
解答
type Element<E extends string[]> = E extends [] ? '' : `__${E[number]}`;
type Modifier<M extends string[]> = M extends [] ? '' : `--${M[number]}`;
type BEM<B extends string, E extends string[], M extends string[]> = `${B}${Element<E>}${Modifier<M>}`;
解説
BEMはCSSのネーミング法の一種です。ルールを知らなかったので、別の意味でも勉強になりました。
BEMのBはBlockで要素に対する名前、EはElementでブロックに依存した要素、MはModifierでBlockの変数みたいです。BEMについては本質じゃなく、雰囲気理解なのでちゃんと知りたい場合はドキュメントを読んだ方が良いです。とりあえず、blockが先頭にきてそれに従属する形でElementは__
、Modufierは--
で繋げて書きます(btn__price--info
など)。
さて、解答を見ていきます。Bはそのままです。Eは配列が空の時は空文字列を返し、それ以外の時は要素の先頭に__
を足したもののユニオンとなります。それを作る部分は上に切り出しました。同様にMも上に切り出しました。EもMも先頭に加えるものが異なるだけで、操作は同じなので、先頭に加えるものを受け取らせる共通の型を作っても良いかも知れません。
type AddLead<T extends string[], S extends '--' | '__'> = T extends [] ? '' : `${S}${T[number]}`;
【中級】InorderTraversal
問題の詳細はこちらです。
問題
Implement the type version of binary tree inorder traversal.
For example:
const tree1 = {
val: 1,
left: null,
right: {
val: 2,
left: {
val: 3,
left: null,
right: null,
},
right: null,
},
} as const
type A = InorderTraversal<typeof tree1> // [1, 3, 2]
解答
type InorderTraversal<T extends TreeNode | null> = [T] extends [TreeNode]
? [...InorderTraversal<T['left']>, T['val'], ...InorderTraversal<T['right']>]
: [];
解説
Tがnullになるまで、左右に展開していきました。[T] extends [TreeNode]
とした理由はT extends TreeNode
はTがユニオンにされることによって無限ループが発生するからです。
【中級】Flip
問題の詳細はこちらです。
問題
Implement the type of just-flip-object. Examples:
Flip<{ a: "x", b: "y", c: "z" }>; // {x: 'a', y: 'b', z: 'c'}
Flip<{ a: 1, b: 2, c: 3 }>; // {1: 'a', 2: 'b', 3: 'c'}
Flip<{ a: false, b: true }>; // {false: 'a', true: 'b'}
No need to support nested objects and values which cannot be object keys such as arrays
解答
type Flip<T extends Record<any, any>> = { [P in keyof T as `${T[P]}`]: P };
解説
最近あまり見ていなかったので他者の回答を見てみましたが、回答数が20%くらいまで減ってました。解答としてはMapped Typesで逆転させただけです。
22日目
【中級】Fibonacci sequence
問題の詳細はこちらです。
問題
Implement a generic Fibonacci<T> that takes a number T and returns its corresponding Fibonacci number.
The sequence starts: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...
For example
type Result1 = Fibonacci<3> // 2
type Result2 = Fibonacci<8> // 21
解答
type Fibonacci<
T extends number,
IdxArray extends never[] = [never],
Past extends never[] = [],
Current extends never[] = [never],
> = IdxArray['length'] extends T
? Current['length']
: Fibonacci<T, [...IdxArray, never], Current, [...Past, ...Current]>;
解説
数値を扱う問題はプログラミングとして特には低難度なものでも、型として解くとなるとなかなか良い問題になるなと感じました。この問題は再起的に解きました。1個目、2個目と求めていき、T個目の値を得るようにしています。T個目まで求まったことを確認するために最初にIdxArray['length'] extends T
で確認しています。IdxArrayですが、ループごとにneverを詰めていきループ回数を記録しています。初項がないとも止まらないので最初からneverが一つ入っています。他にPastやCurrentがありますが、Pastは前回の値、Currentは今の値を表しています。Pastは次の値は前回の値と今の値の足し算であることから保存しています。そしてPastやCurrentも見てわかるように配列の長さで管理しています。これは型で数値を扱う時の常套手段です。話がそれましたが、最初にT回目かどうか確認して、T回目だったらCurrentの長さを返して回答、T回目でなければFibonacciをまた呼び出して、TとIdxArrayにneverを一つ追加したものとCurrentをPastに追加します。最後にCurrentとしてPastとCurrentを結合したものを追加します。これで完成です。
【中級】文字の組み合わせ
問題の詳細はこちらです。
問題
指定された文字列に含まれる文字をそれぞれ最大で1度だけ使った文字列のすべての組み合わせの型AllCombinationsを実装します。
例えば
type AllCombinations_ABC = AllCombinations<'ABC'>;
// should be '' | 'A' | 'B' | 'C' | 'AB' | 'AC' | 'BA' | 'BC' | 'CA' | 'CB' | 'ABC' | 'ACB' | 'BAC' | 'BCA' | 'CAB' | 'CBA'
解答
type Str2Union<S extends string, T extends string = ''> = S extends `${infer U}${infer V}` ? Str2Union<V, U | T> : T;
type Combination<T extends string, U = T> = U extends T
? U | `${U}${Combination<Exclude<T, U>>}`
: never;
type AllCombinations<S extends string> = Combination<Str2Union<S>>;
解説
久しぶりに日本語の問題が出てきて嬉しいです。問題としては昔やったような気がして、見返しましたがPermutationでした。とても難しかったです。
まず、文字列を一文字ずつのユニオンに分解する型と、Combinationを行う方を用意します。Combination型はユニオン型を受け取って、解答の通りの方を出力するコアとなっています。まず、U extends T
でU(ユニオン型)を単体に分解します。そして、U単体を一つ用意、UとCombinationにTからUを抜いたものを組み合わせたものをユニオン型を取らせます。UとCombinationにTからUを抜いたものはUと何かの組み合わせを新たに作ってくれるので段々文字列の長さが大きいものが作られていきます。これを繰り返していくと最大の長さまでの解答を得られます。
【中級】Greater than
問題の詳細はこちらです。
問題
In This Challenge, You should implement a type GreaterThan<T, U> like T > U
Negative numbers do not need to be considered.
For example
GreaterThan<2, 1> //should be true
GreaterThan<1, 1> //should be false
GreaterThan<10, 100> //should be false
GreaterThan<111, 11> //should be true
Good Luck!
解答
type GreaterThan<T extends number, U extends number, V extends never[] = []> =
T extends V['length']
? false
: U extends V['length']
? true
: GreaterThan<T, U, [...V, never]>;
解説
Vの長さを大きくしていって先にTと同じ数になった時にfalse、Uと同じ数になった時にtrueを返すようにしました。他者の回答に比べて、かなりシンプルに書けて気に入っています。
23日目
【中級】Zip
問題の詳細はこちらです。
問題
In This Challenge, You should implement a type Zip<T, U>, T and U must be Tuple
type exp = Zip<[1, 2], [true, false]> // expected to be [[1, true], [2, false]]
解答
type Zip<T extends any[], U extends any[]> =
T extends [infer V, ...infer W]
? U extends [infer X, ...infer Y]
? [[V, X], ...Zip<W, Y>]
: []
: [];
解説
TとUから一つずつ取ってきて、セットにしたものを配列に詰め込んでいます。どちらかが空になったら終了させています。
【中級】IsTuple
問題の詳細はこちらです。
問題
Implement a type IsTuple, which takes an input type T and returns whether T is tuple type.
For example:
type case1 = IsTuple<[number]> // true
type case2 = IsTuple<readonly [number]> // true
type case3 = IsTuple<number[]> // false
解答
type IsTuple<T> =
T[] extends never[]
? false
: T extends readonly []
? true
: T extends readonly [any, ...any]
? true
: false;
解説
まず、neverを弾きます。次に空配列またはTの最初の値を取れたらtrueを返すようにします。取れなかったらfalseです。
number[]などは[any, ...any]のように分解できないことを生かして解きました。このやり方ではなく、lengthを取得できるか?でやっているものもありました。
【中級】Chunk
問題の詳細はこちらです。
問題
Do you know lodash? Chunk is a very useful function in it, now let's implement it. Chunk<T, N> accepts two required type parameters, the T must be a tuple, and the N must be an integer >=1
type exp1 = Chunk<[1, 2, 3], 2> // expected to be [[1, 2], [3]]
type exp2 = Chunk<[1, 2, 3], 4> // expected to be [[1, 2, 3]]
type exp3 = Chunk<[1, 2, 3], 1> // expected to be [[1], [2], [3]]
解答
type Chunk<
T extends any[],
N extends number,
U extends any[] = [],
V extends any[] = []
> = T extends [infer X, ...infer Rest]
? [...U, X]["length"] extends N
? Chunk<Rest, N, [], [...V, [...U, X]]>
: Chunk<Rest, N, [...U, X], V>
: U extends []
? V
: [...V, U];
解説
引数の説明からです。Tはまだ触れていない値の配列、Nは分離する個数、Uは現在作成中の配列、Vは現段階の答えです。最初に先頭と残りの値を取りつつTが空であるか確認します。Tが空の場合捜索を終了ですので終了条件を書きます。現在作成中の配列が空であればVを空でなければVにUを加えたものを返します。続いて、Tが空でない場合です。Uに先頭のXを加えた配列の長さがNであればそこでUが完成するので、次に渡すUの値を空、Vに渡す値をUにXを加えたものをVに加えたものを渡します。Uに先頭のXを加えた配列がNに満たなければUを成長させるために、UにXを加えたものをUに渡して次の機会を待つようにしました。
24日目
【中級】Fill
問題の詳細はこちらです。
問題
Fill, a common JavaScript function, now let us implement it with types. Fill<T, N, Start?, End?>, as you can see,Fill accepts four types of parameters, of which T and N are required parameters, and Start and End are optional parameters. The requirements for these parameters are: T must be a tuple, N can be any type of value, Start and End must be integers greater than or equal to 0.
type exp = Fill<[1, 2, 3], 0> // expected to be [0, 0, 0]
In order to simulate the real function, the test may contain some boundary conditions, I hope you can enjoy it :)
解答
type Fill<
T extends unknown[],
N,
Start extends number = 0,
End extends number = T['length'],
IndexArray extends 1[] = [],
FillFlag extends boolean = false
> =
T extends [infer F, ...infer R]
? IndexArray['length'] extends Start
? Start extends End
? [F, ...Fill<R, N, Start, End, [1, ...IndexArray]>]
: [N, ...Fill<R, N, Start, End, [1, ...IndexArray], true>]
: IndexArray['length'] extends End
? [F, ...Fill<R, N, Start, End, [1, ...IndexArray], false>]
: [FillFlag extends true ? N : F, ...Fill<R, N, Start, End, [1, ...IndexArray], FillFlag>]
: []
解説
すごい簡単そうなんですけど、久しぶりに解けませんでした。解答には一番シンプルそうなものを貼りました。StartとEndの間どうしようと思ってたんですけど、単純にフラグ持たせれば良いだけでしたね。
【中級】Trim Right
問題の詳細はこちらです。
問題
Implement TrimRight<T> which takes an exact string type and returns a new string with the whitespace ending removed.
For example:
type Trimed = TrimRight<' Hello World '> // expected to be ' Hello World'
解答
type TrimTarget = ' ' | '\n' | '\t'
type TrimRight<S extends string> = S extends `${infer Rest}${TrimTarget}` ? TrimRight<Rest> : S;
解説
前に解いた気がしますが、Trimするものを定義して、右側になくなるまでループし続けます。なくなったらそれを解答として返します。
【中級】Without
問題の詳細はこちらです。
問題
Implement the type version of Lodash.without, Without<T, U> takes an Array T, number or array U and returns an Array without the elements of U.
type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []
解答
type Without<T extends number[], U extends number[] | number, Result extends number[] = []> =
T extends [infer V extends number, ...infer Rest extends number[]]
? U extends number[]
? V extends U[number]
? Without<Rest, U, Result>
: Without<Rest, U, [...Result, V]>
: V extends U
? Without<Rest, U, Result>
: Without<Rest, U, [...Result, V]>
: Result;
解説
Resultを最初に定義して、配列を一つずつ見ていきます。Uに含まれていなければResultに追加、含まれていなければResultに追加しないようにします。Uはnumberとnumber[]の可能性があるので分岐させました。
25
【中級】Trunc
問題の詳細はこちらです。
問題
Implement the type version of Math.trunc, which takes string or number and returns the integer part of a number by removing any fractional digits.
For example:
type A = Trunc<12.34> // 12
解答
type Trunc<T extends string | number> = `${T}` extends `${infer U}.${any}` ? U : `${T}`;
解説
.があったらそれより右を返します。なければstringにして返します。
【中級】indexOf
問題の詳細はこちらです。
問題
Implement the type version of Array.indexOf, indexOf<T, U> takes an Array T, any U and returns the index of the first U in Array T.
type Res = IndexOf<[1, 2, 3], 2>; // expected to be 1
type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // expected to be 2
type Res2 = IndexOf<[0, 0, 0], 2>; // expected to be -1
解答
import type { Equal, Expect } from '@type-challenges/utils'
type IndexOf<T, U, IndexArr extends never[] = []> =
T extends [infer V, ...infer Rest]
? Equal<U, V> extends true
? IndexArr['length']
: IndexOf<Rest, U, [...IndexArr, never]>
: -1;
解説
またEqualを利用してときました(またと言っても最初の方に一度使っただけですが)。配列の先頭を取り続けてUに一致したらIndexArrを返して、取得できるまではIndexArrを追加するようにしました。配列を全部検査し終えたら-1です。
【中級】Join
問題の詳細はこちらです。
問題
Implement the type version of Array.join, Join<T, U> takes an Array T, string or number U and returns the Array T with U stitching up.
type Res = Join<["a", "p", "p", "l", "e"], "-">; // expected to be 'a-p-p-l-e'
type Res1 = Join<["Hello", "World"], " ">; // expected to be 'Hello World'
type Res2 = Join<["2", "2", "2"], 1>; // expected to be '21212'
type Res3 = Join<["o"], "u">; // expected to be 'o'
解答
type Join<T extends string[], U extends string | number, Result extends string = ''> =
T extends [infer F extends string, ...infer Rest extends string[]]
? Join<Rest, U, T['length'] extends 1 ? `${Result}${F}` : `${Result}${F}${U}`>
: Result;
解説
ResultをJoinに保有させておいて、どんどん付け加えていくようにしています。Tが先頭の時だけ、後ろにUを加えないようにしました。
26
【中級】LastIndexOf
問題の詳細はこちらです。
問題
Implement the type version of Array.lastIndexOf, LastIndexOf<T, U> takes an Array T, any U and returns the index of the last U in Array T
For example:
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2> // 3
type Res2 = LastIndexOf<[0, 0, 0], 2> // -1
解答
import type { Equal, Expect } from '@type-challenges/utils'
type ArrayLengthMinusOne<T extends any[]> = T extends [any, ...infer U] ? U['length'] : -1;
type LastIndexOf<T extends any[], U> =
T extends [...infer Rest, infer V]
? Equal<U, V> extends true
? ArrayLengthMinusOne<T>
: LastIndexOf<Rest, U>
: -1;
解説
IndexOfを後ろから検査するようにして、ArrIndexを引数として定義するのではなく、残ったTを元にindexを求めるようにしています。
【中級】Unique
問題の詳細はこちらです。
問題
Implement the type version of Lodash.uniq, Unique takes an Array T, returns the Array T without repeated values.
type Res = Unique<[1, 1, 2, 2, 3, 3]>; // expected to be [1, 2, 3]
type Res1 = Unique<[1, 2, 3, 4, 4, 5, 6, 7]>; // expected to be [1, 2, 3, 4, 5, 6, 7]
type Res2 = Unique<[1, "a", 2, "b", 2, "a"]>; // expected to be [1, "a", 2, "b"]
type Res3 = Unique<[string, number, 1, "a", 1, string, 2, "b", 2, number]>; // expected to be [string, number, 1, "a", 2, "b"]
type Res4 = Unique<[unknown, unknown, any, any, never, never]>; // expected to be [unknown, any, never]
解答
import type { Equal, Expect } from '@type-challenges/utils'
type IsInclude<T extends any[], U> =
T extends [infer V, ...infer Rest]
? Equal<V, U> extends true
? true
: IsInclude<Rest, U>
: false;
type Unique<T, Result extends any[] = []> =
T extends [infer U, ...infer V]
? IsInclude<Result, U> extends true
? Unique<V, Result>
: Unique<V, [...Result, U]>
: Result;
解説
またEqualを利用しました。まず、配列に含まれているかどうかの形を作りました。その次に配列に含まれていれば答えに追加、含まれていなければ答えに追加しないみたいにしています。
【中級】MapTypes
問題の詳細はこちらです。
問題
Implement MapTypes<T, R>
which will transform types in object T to different types defined by type R which has the following structure
type StringToNumber = {
mapFrom: string; // value of key which value is string
mapTo: number; // will be transformed for number
}
Examples:
type StringToNumber = { mapFrom: string; mapTo: number;}
MapTypes<{iWillBeANumberOneDay: string}, StringToNumber> // gives { iWillBeANumberOneDay: number; }
Be aware that user can provide a union of types:
type StringToNumber = { mapFrom: string; mapTo: number;}
type StringToDate = { mapFrom: string; mapTo: Date;}
MapTypes<{iWillBeNumberOrDate: string}, StringToDate | StringToNumber> // gives { iWillBeNumberOrDate: number | Date; }
If the type doesn't exist in our map, leave it as it was:
type StringToNumber = { mapFrom: string; mapTo: number;}
MapTypes<{iWillBeANumberOneDay: string, iWillStayTheSame: Function}, StringToNumber> // // gives { iWillBeANumberOneDay: number, iWillStayTheSame: Function }
解答
import type { Equal, Expect } from '@type-challenges/utils'
type MapTypes<T, R extends Record<'mapFrom' | 'mapTo', any>> = {
[P in keyof T]: T[P] extends R['mapFrom']
? R extends { mapFrom: T[P] }
? R['mapTo']
: never
: T[P]
};
解説
mapFromとT[P]が同じであればmapToを返すようにしました。Rがユニオンの場合mapFromがT[P]のものだけに絞る必要があるのでR extends { mapFrom: T[P] }で絞りました。
27
【中級】Construct Tuple
問題の詳細はこちらです。
問題
Construct a tuple with a given length.
For example
type result = ConstructTuple<2> // expect to be [unknown, unkonwn]
解答
type ConstructTuple<L extends number, Result extends unknown[] = []> =
L extends Result['length']
? Result
: ConstructTuple<L, [unknown, ...Result]>;
解説
LとResultが同じ数になるまでResultにunknownを付け足すように作りました。tsは1000以上のループを無限と判断するため、L=1000は動きません。テストケースでも1000以上はエラーが起きることを確認するケースがあったので今回はこれが想定通りの解答となります。
【中級】Number Range
問題の詳細はこちらです。
問題
Sometimes we want to limit the range of numbers... For examples.
type result = NumberRange<2 , 9> // | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
解答
type NumberRange<L extends number, H extends number, T extends number[] = [], Flag extends boolean = false, Result extends number = L> =
T["length"] extends L
? NumberRange<L, H, [...T, L], true>
: Flag extends true
? T["length"] extends H
? Result | T['length']
: NumberRange<L, H, [...T, never], Flag, Result | T['length']>
: NumberRange<L, H, [...T, never], Flag>
解説
前回Flagを使うことを学んだのでここで使ってみました。TでIndexを管理していて、Lよりも大きくなったときにFlagをtrueにしています。それ以降はHとTの長さが一致するまで詰めていくだけです。
前回の問題を利用して初期値をLの長さの配列にしようと思いましたが、無限ループの恐れがあるのでエラーとなってしまいました。
【中級】Combination
問題の詳細はこちら
)です。
問題
Given an array of strings, do Permutation & Combination. It's also useful for the prop types like video controlsList
// expected to be "foo" | "bar" | "baz" | "foo bar" | "foo bar baz" | "foo baz" | "foo baz bar" | "bar foo" | "bar foo baz" | "bar baz" | "bar baz foo" | "baz foo" | "baz foo bar" | "baz bar" | "baz bar foo"
type Keys = Combination<['foo', 'bar', 'baz']>
解答
type Combination<T extends string[], All = T[number], Item = All>
= Item extends string
? Item | `${Item} ${Combination<[], Exclude<All, Item>>}`
: never;
解説
前に似た問題を解いて味気ないので、面白い解答を引っ張ってきました。
まず引数には、操作する対象のTとそれをunionにしたAllとItemを取ります。Itemは次の行でItem extends stringによってバラバラにされます。その後に、ItemとItemと次のCombinationを組み合わせたものを返します。次のCombinationには空配列のTとAllにAllからItemを省いたものを渡します。TはAllの初期値を決めるためだけにあるので以降は空で良いということです。
28
【中級】Subsequence
問題の詳細はこちらです。
問題
Given an array of unique elements, return all possible subsequences.
A subsequence is a sequence that can be derived from an array by deleting some or no elements without changing the order of the remaining elements.
For example:
type A = Subsequence<[1, 2]> // [] | [1] | [2] | [1, 2]
解答
type Subsequence<T extends any[]> = T extends [infer U, ...infer Rest] ? Subsequence<Rest> | [U, ...Subsequence<Rest>] : [];
解説
Tの先頭Uと残りの部分Restに分けてUを含むもので続きを展開したものとUを含まずに続きを展開したものでユニオン型を取りました。
【中級】GetMiddleElement
問題の詳細はこちらです。
問題
Get the middle element of the array by implementing a GetMiddleElement method, represented by an array
If the length of the array is odd, return the middle element If the length of the array is even, return the middle two elements
For example
type simple1 = GetMiddleElement<[1, 2, 3, 4, 5]>, // expected to be [3]
type simple2 = GetMiddleElement<[1, 2, 3, 4, 5, 6]> // expected to be [3, 4]
解答
type GetMiddleElement<T extends any[], PastResult extends any = []> =
T extends [infer U, ...infer Rest, infer V]
? GetMiddleElement<Rest, [U, V]>
: T extends [infer U, ...infer Rest]
? GetMiddleElement<Rest, [U]>
: PastResult;
解説
Tを先頭と末尾とその間の部分に分けて取得します。分けることが可能だったらPastResultにUとVを組み合わせたもの、TにRestを渡して次に移ります。分けることが不可能だったら、先頭だけ取ってこれるか確認して取ってこれたらPastValueに[U]を渡して次に移ります。それも無理だったら答えを返します。
1週目[1, 2, 3, 4, 5] []
2週目[2, 3, 4] [1, 5]
3週目[3] [2, 4]
4週目[] [3]
【中級】Integer
問題の詳細はこちらです。
問題
Please complete type Integer<T>, type T inherits from number, if T is an integer return it, otherwise return never.
解答
type Integer<T extends number> =
number extends T
? never
: `${T}` extends `${any}.${infer U}`
? U extends 0
? T
: never
: T;
解説
まず純粋なnumberかどうかをみます。numberであればneverを返します。次にTを文字列化して少数かどうか確認します。少数の場合少数部分が0の場合のみTを返してそれ以外はneverを返します。少数でなければTを返します。
29
【中級】ToPrimitive
問題の詳細はこちらです。
問題
Convert a property of type literal (label type) to a primitive type.
For example
type X = {
name: 'Tom',
age: 30,
married: false,
addr: {
home: '123456',
phone: '13111111111'
}
}
type Expected = {
name: string,
age: number,
married: boolean,
addr: {
home: string,
phone: string
}
}
type Todo = ToPrimitive<X> // should be same as `Expected`
解答
type ToPrimitive<T> = {
[P in keyof T]:
T[P] extends string
? string
: T[P] extends number
? number
: T[P] extends boolean
? boolean
: T[P] extends object
? ToPrimitive<T[P]>
: T[P]
};
解説
値をPrimitiveに判別して当てはまればprimitiveを返しています。objectの場合は再度ToPrimitiveを呼ぶようにしています。symbolとかはテストケースになかったので定義しませんでしたが、実際に使うなら定義すべきだと思います。
【中級】DeepMutable
問題の詳細はこちらです。
問題
Implement a generic DeepMutable which make every parameter of an object - and its sub-objects recursively - mutable.
For example
type X = {
readonly a: () => 1
readonly b: string
readonly c: {
readonly d: boolean
readonly e: {
readonly g: {
readonly h: {
readonly i: true
readonly j: "s"
}
readonly k: "hello"
}
}
}
}
type Expected = {
a: () => 1
b: string
c: {
d: boolean
e: {
g: {
h: {
i: true
j: "s"
}
k: "hello"
}
}
}
}
type Todo = DeepMutable<X> // should be same as Expected
You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on do not need to be taken into consideration. However, you can still challenge yourself by covering as many different cases as possible.
解答
type DeepMutable<T extends object> = {
-readonly [P in keyof T]:
T[P] extends object
? T[P] extends Function
? T[P]
: DeepMutable<T[P]>
: T[P]
};
解説
readonlyを外します。バリューがFunction以外のobjectであれば再起的に動作させます。
【上級】Simple Vue
問題の詳細はこちらです。
問題
Implement a simpiled version of a Vue-like typing support.
By providing a function name SimpleVue (similar to Vue.extend or defineComponent), it should properly infer the this type inside computed and methods.
In this challenge, we assume that SimpleVue take an Object with data, computed and methods fields as it's only argument,
data is a simple function that returns an object that exposes the context this, but you won't be accessible to other computed values or methods.
computed is an Object of functions that take the context as this, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions.
methods is an Object of functions that take the context as this as well. Methods can access the fields exposed by data, computed as well as other methods. The different between computed is that methods exposed as functions as-is.
The type of SimpleVue's return value can be arbitrary.
const instance = SimpleVue({
data() {
return {
firstname: 'Type',
lastname: 'Challenges',
amount: 10,
}
},
computed: {
fullname() {
return this.firstname + ' ' + this.lastname
}
},
methods: {
hi() {
alert(this.fullname.toLowerCase())
}
}
})
解答
declare function SimpleVue<Data, Computed, Methods>(options: {
data: (this: void) => Data;
computed: {
[key in keyof Computed]: (this: Data) => Computed[key]
}
methods: Methods & ThisType<Data & Computed & Methods>;
}): any
解説
初の上級問題です。問題文として難しかったような気がして、解答としては単純なものでした。
30日目
【上級】Currying 1
問題の詳細はこちらです。
問題
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
For example:
const add = (a: number, b: number) => a + b
const three = add(1, 2)
const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)
The function passed to Currying may have multiple arguments, you need to correctly type it.
In this challenge, the curried function only accept one argument at a time. Once all the argument is assigned, it should return its result.
解答
type Curry<T> =
T extends (...args: infer U) => infer V
? U extends [infer X, ...infer Rest]
? Rest['length'] extends 0
? (a: X) => V
: (a: X) => Curry<(...args: Rest) => V>
: () => V
: never;
declare function Currying<T>(fn: T): Curry<T>;
解説
カリー化の問題ですね。上級は実用的そうなのが多いの印象を覚えました(実際使わなそうではありますが)Curringでは引数の型を取るだけにして、返り値の方は引数の方を元にカリー化する方を別で用意しました。ここでやっているのは単純なことで、関数の分解、引数の分解。。。です。中級まで解いてきた人には簡単な問題なように感じました。
【上級】Union to Intersection
問題の詳細はこちらです。
問題
Implement the advanced util type UnionToIntersection<U>
For example
type I = Union2Intersection<'foo' | 42 | true> // expected to be 'foo' & 42 & true
解答
type MakeFunction<T = any, U = any> = T extends any ? (args: T) => U : never;
type UnionToIntersection<U> =
MakeFunction<U> extends (args: infer I) => any
? I
: never;
解説
関数の引数の性質を利用して作りました。共変の位置にある型はユニオン型、反変の位置にある型はインターセクション型となります。関数の引数は反変ですのでこのようになります。逆を言えば配列やobjectを利用すればユニオン型で作れると思います。
【上級】Get Required
問題の詳細はこちらです。
問題
必須なフィールドのみを残す高度なユーティリティ型 GetRequired<T> を実装してください。
例えば
type I = GetRequired<{ foo: number, bar?: string }> // expected to be { foo: number }
解答
type RequiredKeys<T> = {
[P in keyof T]-?: T extends Record<P, T[P]> ? P : never
}[keyof T];
type GetRequired<T> = Pick<T, RequiredKeys<T>>;
解説
requiredなkeyを取得して、Pickしました。
31日目
【上級】Get Optional
問題の詳細はこちらです。
問題
オプショナルなフィールドのみを残す高度なユーティリティ型 GetOptional<T> を実装してください。
例えば
type I = GetOptional<{ foo: number, bar?: string }> // expected to be { bar?: string }
解答
type GetOptional<T> = {
[K in keyof T as T[K] extends Required<T>[K] ? never : K]: T[K]
};
解説
keyにRequired<T>[K]があったらneverそうでなければKを返すようにします。前回の問題も同様の手法で解くことが期待されてそうでした(次の問題を見るに)。前回の問題同様Optional Keysを使って解くこともできますが、2問先に控えているためやめておきます。
【上級】Required Key
問題の詳細はこちらです。
問題
必須なキーの Union を抽出する高度なユーティリティ型 RequiredKeys<T> を実装してください。
例えば
type Result = RequiredKeys<{ foo: number; bar?: string }>;
// expected to be “foo”
解答
type RequiredKeys<T> = {
[P in keyof T]-?: T extends Record<P, T[P]> ? P : never
}[keyof T];
解説
先に解いてしまいました。optionalでなければ弾いています。
【上級】Optional Key
問題の詳細はこちらです。
問題
オプショナルなキーの Union を抽出する高度なユーティリティ型 OptionalKeys<T>
を実装してください。
解答
type OptionalKeys<T> = {
[P in keyof T]-?: T extends Record<P, T[P]> ? never : P
}[keyof T];
解説
条件による出力を逆転させるだけです。
32日目
【上級】Capitalize Words
問題の詳細はこちらです。
問題
文字列の各単語の最初の文字を大文字に変換し、残りをそのままにする CapitalizeWords <T> を実装します。
例えば
type capitalized = CapitalizeWords<'hello world, my friends'> // expected to be 'Hello World, My Friends'
解答
type Alphabet = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';
type CapitalizeWords<S extends string, Result extends string = '', IsUpper extends boolean = true> = S extends `${infer T}${infer Rest}`
? T extends Alphabet
? CapitalizeWords<Rest, `${Result}${IsUpper extends true ? Uppercase<T> : T}`, false>
: CapitalizeWords<Rest, `${Result}${T}`, true>
: Result;
解説
まず、アルファベットを定義します。アルファベット以外の文字が来た時にフラグを立てて次の文字を大文字にするためです。あとは分岐させて再起させました。
【上級】Camel Case
問題の詳細はこちらです。
問題
snake_case 文字列を camelCase に変換する CamelCase <T> を実装します。
例えば
type camelCase1 = CamelCase<'hello_world_with_types'> // expected to be 'helloWorldWithTypes'
type camelCase2 = CamelCase<'HELLO_WORLD_WITH_TYPES'> // expected to be same as previous one
解答
type CamelCase<S extends string> = S extends Lowercase<S>
? S extends `${infer T}_${infer U}${infer Rest}`
? `${T}${Uppercase<U>}${CamelCase<Rest>}`
: S
: CamelCase<Lowercase<S>>;
解説
一度全部小文字にして行います。小文字にした後に、_で分離して_の後を大文字にしてそれ以降に再びかけます。
【上級】C-printer Parser
問題の詳細はこちらです。
問題
C 言語にはprintfという関数があり、以下のようにフォーマットして出力してくれます。
printf("The result is %d.", 42);
この課題では、入力値の文字列をパースして%dや%fのようなフォーマットのプレースホルダーを抜き出します。 例えば、もし入力文字列が"The result is %d"であるなら、パースした結果は['dec']というタプルになります。
マッピングは以下となります。
type ControlsMap = {
c: 'char';
s: 'string';
d: 'dec';
o: 'oct';
h: 'hex';
f: 'float';
p: 'pointer';
};
解答
type ParsePrintFormat<S> = S extends `${any}%${infer T}${infer Rest}`
? T extends keyof ControlsMap
? [ControlsMap[T], ...ParsePrintFormat<Rest>]
: ParsePrintFormat<Rest>
: [];
解説
貪欲にときました。%とkeyof ControlsMap
があればそれを加えています。
33日目
【上級】Vue Basic Props
問題の詳細はこちらです。
問題
この問題は 6 - Simple Vue の続きです。 先にその問題を解いた上で、そこで得られるコードを基盤にしてこの問題に取り組んでください。
Simple Vue に加え、我々は 新しく props フィールドをオプションとして利用できます。これは Vue の props オプションを更に簡潔にしたものです。利用するにはいくつかのルールがあります。
props は、 this に挿入された各々のフィールドをキーをとして保持するオブジェクトです。挿入された props は data,computed, methods などを始めとした 全ての環境からアクセス可能になります。
prop は コンストラクタ、または コンストラクタを含んだ type フィールドを持つオブジェクトで定義されます。
例)
props: {
foo: Boolean
}
// or
props: {
foo: { type: Boolean }
}
これらは type Props = { foo: boolean } であると推測されます。
複数の型を渡した場合、Props の型は ユニオン型として推測されます。
props: {
foo: { type: [Boolean, Number, String] }
}
// -->
type Props = { foo: boolean | number | string }
空のオブジェクトが渡された場合、対象のkeyは any 型として推測されます。
より具体的なケースについては, Test Cases セクションを参照してください。
Vue の required, default, そして 配列の props はこの問題において考慮されておりません.
解答
type ToUnion<T> =
T extends Array<infer A>
? ToUnion<A>
: T extends () => infer A
? A
: T extends abstract new (...args: any) => any
? InstanceType<T>
: any
type PropsType<Props> = {
[key in keyof Props]: Props[key] extends { type: infer A } ? ToUnion<A> : ToUnion<Props[key]>
}
declare function VueBasicProps<Props, Data, Computed, Methods>(options: {
props: Props;
data: (this: PropsType<Props>) => Data;
computed: {
[key in keyof Computed]: (this: Data) => Computed[key]
}
methods: Methods & ThisType<PropsType<Props> & Data & Computed & Methods>;
}): any
解説
React好きなのでReactの問題もあったら良いのになと思いました。
前の問題にPropsを必要な箇所に足したものになります。propsはPropsを得るためのものとして、dataなどにはPropsTypeによって取得したTypeを渡しています。PropsTypeではtypeがあればその奥に、なければそのままのバリューを下の変換を行うToUnionに渡しました。
props: {
foo: { type: [Boolean, Number, String] }
}
// -->
type Props = { foo: boolean | number | string }
【上級】IsAny
問題の詳細はこちらです。
問題
any型の値を持っているかを検出することが便利な場合があります。これは、モジュール API でany型の値をエクスポート可能なサードーパーティーの TypeScript モジュールを使用する際に 特に便利です。また、implicitAny チェックを抑制する際にany型について知ることは良いことです。
そこで、型Tを受け取るユーティリティ型IsAny<T>を書いてみましょう。Tがany型であればtrueを返し、そうでなければfalseを返します。
解答
type IsAny<T> = ((args: [any]) => any) extends ((args: T) => T) ? true : false;
解説
Equalの実装をしても解けます。
今回は関数の引数が反変であることと、返り値が共変であることを生かして解きました。引数はnever以外の型を、返り値はneverを判別しています。例えば引数がnumberのものにanyを渡すことはできません。しかし、返り値がnumberのものにanyを渡すことはできます。逆に引数がanyのものにnumberを渡すことはできませんが、返り値がanyのものにnumberを渡すことはできます。この問題ではそれを利用しています。number、string、unknownのような一般的な型が引数の時にanyが引数にはなれないことを、neverが返り値の時にanyを渡せないことを示しています。
【上級】Typed Get
問題の詳細はこちらです。
問題
lodash の get 関数は JavaScript でネストした値にアクセスする際にとても便利です。しかし、TypeScript でこのような関数を使うと型情報が失われてしまいます。 TypeScript4.1 の機能であるTemplate Literal Typesを使うと、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 }
この課題では、配列へのアクセスは必要ありません。
解答
type Get<T, K> = K extends keyof T
? T[K]
: K extends `${infer U}.${infer V}`
? U extends keyof T
? Get<T[U], V>
: never
: never;
解説
可能な限りKを分解してとってきています。分解したものがTに存在しなければneverを返しています。
34日目
【上級】String To Number
問題の詳細はこちらです。
問題
Number.parseIntのように、文字列リテラルを数値に変換する型を実装します。
解答
type Number = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type IsNumber<S extends string> = S extends `${infer T}${infer U}`
? T extends Number
? IsNumber<U>
: false
: true;
type ToNumber<S extends string, T extends any[] = []> = IsNumber<S> extends true
? S extends `${T["length"]}`
? T["length"]
: ToNumber<S, [...T, any]>
: never;
解説
Numberをまず定義します。その次にNumberであることを確認します。その後、Tの長さを文字列化したものがSと同じになるまで更新し続けて、完了したらTの長さを返します。数値が大きいと無限ループ判定になるので注意です。
【上級】Tuple Filter
問題の詳細はこちらです。
問題
タプルTから指定された型FをフィルタリングするFilterOut<T, F>を実装します。
例えば、
type Filtered = FilterOut<[1, 2, null, 3], null>; // [1, 2, 3]
解答
type FilterOut<T extends any[], F, Result extends any[] = []> =
T extends [infer U, ...infer Rest]
? U[] extends F[]
? FilterOut<Rest, F, Result>
: FilterOut<Rest, F, [...Result, U]>
: Result;
解説
Tを一つずつ検査して再度Resultに配列を形成するように作りました。判定はnever対策も兼ねて、U[] extends F[]で行いました。
【上級】Tuple To Enum Object
問題の詳細はこちらです。
問題
enum は TypeScript 独自の文法です(JavaScript にはありません)。そのため、以下の形式にトランスパイルされます。
let OperatingSystem;
(function (OperatingSystem) {
OperatingSystem[(OperatingSystem['MacOS'] = 0)] = 'MacOS';
OperatingSystem[(OperatingSystem['Windows'] = 1)] = 'Windows';
OperatingSystem[(OperatingSystem['Linux'] = 2)] = 'Linux';
})(OperatingSystem || (OperatingSystem = {}));
この問題では、文字列のタプルを enum と同じようなオブジェクトに変換する型を実装します。 さらに、enum のプロパティはパスカルケースであることが好ましいです。
Enum<["macOS", "Windows", "Linux"]>
// -> { readonly MacOS: "macOS", readonly Windows: "Windows", readonly Linux: "Linux" }
第 2 引数にtrueが与えられた場合、値は数値リテラルとなります。
Enum<["macOS", "Windows", "Linux"], true>
// -> { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }
解答
import type { Equal, Expect } from '@type-challenges/utils'
type IndexOf<T, U, IndexArr extends never[] = []> =
T extends readonly [infer V, ...infer Rest]
? Equal<U, V> extends true
? IndexArr['length']
: IndexOf<Rest, U, [...IndexArr, never]>
: -1;
type UpperHead<S extends string> = S extends `${infer T}${infer U}` ? `${Uppercase<T>}${U}` : S;
type Enum<T extends readonly string[], N extends boolean = false> = {
readonly [P in T[number] as UpperHead<P>]: N extends true ? IndexOf<T, P> : P
};
解説
以前作成したIndexOfを利用して作成しました。前回はreadonlyではない配列だったようで、readonlyがついていなかったのでその部分だけ付け加えました。他に先頭を大文字にする型もつくりました。
あとはT[number]を利用してUpperHeadしたものでkeyを作りました。値はNがtrueがどうかによって挙動を変えました。trueの時はIndexOfに代入した値、falseの時はPをそのまま返します。
35日目
【上級】printf
問題の詳細はこちらです。
問題
汎用的なFormat<T extends string>を実装します。
例えば、
type FormatCase1 = Format<'%sabc'>; // FormatCase1 : string => string
type FormatCase2 = Format<'%s%dabc'>; // FormatCase2 : string => number => string
type FormatCase3 = Format<'sdabc'>; // FormatCase3 : string
type FormatCase4 = Format<'sd%abc'>; // FormatCase4 : string
解答
type FormatType = {
s: string;
d: number;
};
type Format<T extends string> =
T extends `${any}%${infer U}${infer Rest}`
? U extends keyof FormatType
? (args: FormatType[U]) => Format<Rest>
: Format<Rest>
: string;
解説
C言語に慣れ親しんでいないので仕様がわからないんですが、Equal<Format<'a%%dbc'>, string>
はフォーマットしないのにEqual<Format<'a%%%dbc'>, (d1: number) => string>
はフォーマットするんですね。この方が型の問題としてはUがFormatTypeでない時の分岐が一つ減るので解きやすくなったので助かります。
解答は単純に%の後の文字列を見て、FormatTypeのキーだったら、それをargsにとって引数でさらに展開していくだけです。
【上級】Deep object to unique
問題の詳細はこちらです。
問題
TypeScript は構造的型システムを持っていますが、場合によっては(公称型システムのように)あらかじめ定義されたユニークなオブジェクトだけを受け入れ、required なフィールドを持つオブジェクトは受け入れないようにしたいこともあるでしょう。
引数にオブジェクトを受け取り、引数に含まれる全てのオブジェクトの文字列と数値のキー、およびこれらのキーの値のプロパティーを保持しながら、引数のオブジェクトとそれに含まれるネストしたオブジェクト全てをユニークにする型を実装します。
元の型と実装した型のアウトプットの型は相互に代入可能でなければなりませんが、同一の型であってはいけません。
例えば、
import { Equal } from '@type-challenges/utils';
type Foo = { foo: 2; bar: { 0: 1 }; baz: { 0: 1 } };
type UniqFoo = DeepObjectToUniq<Foo>;
declare let foo: Foo;
declare let uniqFoo: UniqFoo;
uniqFoo = foo; // ok
foo = uniqFoo; // ok
type T0 = Equal<UniqFoo, Foo>; // false
type T1 = UniqFoo['foo']; // 2
type T2 = Equal<UniqFoo['bar'], UniqFoo['baz']>; // false
type T3 = UniqFoo['bar'][0]; // 1
type T4 = Equal<keyof Foo & string, keyof UniqFoo & string>; // true
解答
const symbol = Symbol()
type DeepObjectToUniq<O extends object, Path extends any[] = [O]> = {
[K in keyof O]: O[K] extends object ? DeepObjectToUniq<O[K], [...Path, K]> : O[K]
} & { [symbol]?: Path }
解説
久しぶりに解けなかったです。symbolを使って固有のObjectにしてコピーしているようです。Pathにはそれまで使用してきたkeyが詰まっています。今後はsymbolを使った問題も解けるようにしたいです。
【上級】Length of String2
問題の詳細はこちらです。
問題
テンプレート文字列の長さを計算するLengthOfString<S>
を実装します。(298 - Length of Stringと同じような型):
type T0 = LengthOfString<'foo'>; // 3
この課題で実装する型は、数百文字の長さの文字列をサポートしなければなりません(通常の再帰的な文字列長の計算は、TS の再帰的な関数呼び出しの深さによって制限されています、つまり、45 文字程度までの文字列をサポートしています)。
解答
type LengthOfString<S extends string , Result extends unknown[] = []> =
S extends `${any}${infer Rest}`
? LengthOfString<Rest, [...Result, unknown]>
: Result["length"];
解説
他の問題を解いて検証したところによるループの最大数は1000だったので、テストケースは一個ずつ取るやり方で解けました。仕様が変わったんですかね?他の解答を見ていると、一度に複数取るようにしているだけなので根本的な解決方法は分かりませんでした。
36日目
【上級】Union to Tuple
問題の詳細はこちらです。
問題
Implement a type, UnionToTuple, that converts a union to a tuple.
As we know, union is an unordered structure, but tuple is an ordered, which implies that we are not supposed to preassume any order will be preserved between terms of one union, when unions are created or transformed.
Hence in this challenge, any permutation of the elements in the output tuple is acceptable.
Your type should resolve to one of the following two types, but NOT a union of them!
UnionToTuple<1> // [1], and correct
`UnionToTuple<'any' | 'a'> // ['any','a']`, and correct
or
UnionToTuple<'any' | 'a'> // ['a','any'], and correct
It shouldn't be a union of all acceptable tuples...
UnionToTuple<'any' | 'a'> // ['a','any'] | ['any','a'], which is incorrect
And a union could collapes, which means some types could absorb (or be absorbed by) others and there is no way to prevent this absorption. See the following examples:
Equal<UnionToTuple<any | 'a'>, UnionToTuple<any>> // will always be a true
Equal<UnionToTuple<unknown | 'a'>, UnionToTuple<unknown>> // will always be a true
Equal<UnionToTuple<never | 'a'>, UnionToTuple<'a'>> // will always be a true
Equal<UnionToTuple<'a' | 'a' | 'a'>, UnionToTuple<'a'>> // will always be a true
解答
type UnionToFnInserction<T> =
(T extends any
? (arg: () => T) => any
: never
) extends (arg: infer U) => any
? U
: never;
type UnionToTuple<T, Result extends any[] = []> =
UnionToFnInserction<T> extends () => infer R
? UnionToTuple<Exclude<T, R>, [...Result, R]>
: Result;
解説
Tを交差型にして一つ一つ詰めていきました。
【上級】String Join
問題の詳細はこちらです。
問題
Create a type-safe string join utility which can be used like so:
const hyphenJoiner = join('-')
const result = hyphenJoiner('a', 'b', 'c'); // = 'a-b-c'
Or alternatively:
join('#')('a', 'b', 'c') // = 'a#b#c'
When we pass an empty delimiter (i.e '') to join, we should concat the strings as they are, i.e:
join('')('a', 'b', 'c') // = 'abc'
When only one item is passed, we should get back the original item (without any delimiter added):
join('-')('a') // = 'a'
解答
type Join<
T extends string,
U extends any[],
Result extends string = ""
> = U extends [infer L, ...infer Rest]
? Join<
T,
Rest,
Result extends "" ? `${L & string}` : `${Result}${T}${L & string}`
>
: Result;
declare function join<T extends string>(delimiter: T): <U extends string[]>(...parts: U) => Join<T, U>;
解説
まず、Joinという型を別で定義します。これは単純な型で、文字列の配列Uを一つずつTで連結して、空になったらResultを返すようなものとなっています。joinでは必要な型を取得してJoinに渡すように調整しています。
【上級】Deep Pick
問題の詳細はこちらです。
問題
Implement a type DeepPick, that extends Utility types Pick. A type takes two arguments.
For example:
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' }}}
解答
type UnionToIntersection<U> = (U extends any ? (arg: U) => any : never) extends ((arg: infer I) => void) ? I : never
type DeepPickInner<T, K> =
K extends keyof T
? [{ [P in K]: T[K] }, T[K]]
: K extends `${infer K1}.${infer K2}`
? [{ [P in K1]: DeepPickInner<DeepPickInner<T, K1>[1], K2>[0] }, DeepPickInner<DeepPickInner<T, K1>[1], K2>[0]]
: [unknown, unknown]
type DeepPick<T, K> = UnionToIntersection<DeepPickInner<T, K>[0]>
解説
解けなかったです。他者の回答を拝借して持ってきたものを貼り付けています。UnitonToIntesectionはユニオンをインターセクション型にするかたです。DeepPickInnerはTを掘って言って目的のobjectを取得しています。
37日目
【上級】Pinia
問題の詳細はこちらです。
問題
Create a type-level function whose types is similar to Pinia library. You don't need to implement function actually, just adding types.
Overview
This function receive only one parameter whose type is an object. The object contains 4 properties:
-
id
- just a string (required) -
state
- a function which will return an object as store's state (required) -
getters
- an object with methods which is similar to Vue's computed values or Vuex's getters, and details are below (optional) -
actions
- an object with methods which can do side effects and mutate state, and details are below (optional)
Getters
When you define a store like this:
const store = defineStore({
// ...other required fields
getters: {
getSomething() {
return 'xxx'
}
}
})
And you should use it like this:
store.getSomething
instead of:
store.getSomething() // error
Additionally, getters can access state and/or other getters via this
, but state is read-only.
Actions
When you define a store like this:
const store = defineStore({
// ...other required fields
actions: {
doSideEffect() {
this.xxx = 'xxx'
return 'ok'
}
}
})
Using it is just to call it:
const returnValue = store.doSideEffect()
Actions can return any value or return nothing, and it can receive any number of parameters with different types.
Parameters types and return type can't be lost, which means type-checking must be available at call side.
State can be accessed and mutated via this
. Getters can be accessed via this
but they're read-only.
解答
declare function defineStore<State, Getters, Actions>(store: {
id: string;
state: () => State;
getters: Getters &
ThisType<
Readonly<State> & {
readonly [P in keyof Getters]: Getters[P] extends (
...args: any
) => infer R
? R
: never;
}
>;
actions: Actions &
ThisType<
State & {
readonly [P in keyof Getters]: Getters[P] extends (
...args: any
) => infer R
? R
: never;
} & Actions
>;
}): Readonly<State> & {
readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R
? R
: never;
} & Actions;
解説
defineStoreが動くようになるまで調整してときました。
【上級】Camelize
問題の詳細はこちらです。
問題
Implement Camelize which converts object from snake_case to to camelCase
Camelize<{
some_prop: string,
prop: { another_prop: string },
array: [{ snake_case: string }]
}>
// expected to be
// {
// someProp: string,
// prop: { anotherProp: string },
// array: [{ snakeCase: string }]
// }
解答
type CamelCase<S extends string> = S extends Lowercase<S>
? S extends `${infer T}_${infer U}${infer Rest}`
? `${T}${Uppercase<U>}${CamelCase<Rest>}`
: S
: CamelCase<Lowercase<S>>;
type Camelize<T> = T extends any[]
? T extends [infer F, ...infer R]
? [Camelize<F>, ...Camelize<R>]
: []
: {
[P in keyof T as P extends string ? CamelCase<P> : P]: T[P] extends object
? T[P] extends Function
? T[P]
: Camelize<T[P]>
: T[P];
};
解説
以前作ったCamelCaseを持ってきて、配列だったら、一つずつにCamelizaをかけていきます。そうでない場合はobjectとして再生成させます。キーはCamelCaseで大きくして、バリューはFunction以外のobjectであれば再度Camelizeに欠けています。
【上級】Drop String
問題の詳細はこちらです。
問題
Drop the specified chars from a string.
For example:
type Butterfly = DropString<'foobar!', 'fb'> // 'ooar!'
解答
type StringToUnion<S extends string> = S extends `${infer T}${infer U}` ? T | StringToUnion<U> : never;
type DropString<S extends string, R extends string, Result extends string = ''> =
S extends `${infer T}${infer U}`
? T extends StringToUnion<R>
? DropString<U, R, Result>
: DropString<U, R, `${Result}${T}`>
: Result;
解説
StringをUnionに変換する型を用意します。DropStringでは一文字ずつ見ていってStringToUnionにRを渡したものに含まれていればResultを更新せずに次に行きます。含まれていなければResultの後ろに一文字を入れて次に行きます。文字列が空になったときのResultが解答になります。
38日目
【上級】Split
問題の詳細はこちらです。
問題
The well known split() method splits a string into an array of substrings by looking for a separator, and returns the new array. The goal of this challenge is to split a string, by using a separator, but in the type system!
For example:
type result = Split<'Hi! How are you?', ' '> // should be ['Hi!', 'How', 'are', 'you?']
解答
type Split<S extends string, SEP extends string, Result extends string[] = []> =
string extends S
? string[]
: SEP extends ''
? S extends `${infer T}${infer U}`
? Split<U, SEP, [...Result, T]>
: [...Result]
: S extends `${infer T}${SEP}${infer U}`
? Split<U, SEP, [...Result, T]>
: [...Result, S];
解説
Sがstringだったときはstring[]を返すようにします。次にSEPが空文字列の時は一文字ずつResultに詰め込んで言って空になったらResultを返すようにします。SEPが空ではないときはSEPの前後をとって前半をResultに詰め込んで後半を再検査します。検査が完了したら、Resultに残りを詰めたものを返します。
【上級】ClassPublicKeys
問題の詳細はこちらです。
問題
Implement the generic ClassPublicKeys<T> which returns all public keys of a class.
For example:
class A {
public str: string
protected num: number
private bool: boolean
getNum() {
return Math.random()
}
}
type publicKyes = ClassPublicKeys<A> // 'str' | 'getNum'
解答
type ClassPublicKeys<T> = keyof T
解説
これでOKでした。
【上級】IsRequiredKey
問題の詳細はこちらです。
問題
Implement a generic IsRequiredKey<T, K> that return whether K are required keys of T .
For example
type A = IsRequiredKey<{ a: number, b?: string },'a'> // true
type B = IsRequiredKey<{ a: number, b?: string },'b'> // false
type C = IsRequiredKey<{ a: number, b?: string },'b' | 'a'> // false
解答
type IsRequiredKey<T, K extends keyof T> = Pick<T, K> extends Pick<Required<T>, K> ? true : false;
解説
PickしたものとRequiredしてPickしたもので比較して一致したらtrueしなかったらfalseを返すようにしました(一致したらというのはextendsを使っているのでもちろん完全にというわけではないです)。
39日目
【上級】ObjectFromEntries
問題の詳細はこちらです。
問題
Implement the type version of Object.fromEntries
For example:
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
解答
type ObjectFromEntries<T extends [string, any]> = {
[E in T as E[0]]: E[1]
}
解説
配列を受け取ってobjectに配置しただけです。
【上級】IsPalindrome
問題の詳細はこちらです。
問題
Implement type IsPalindrome<T> to check whether a string or number is palindrome.
For example:
IsPalindrome<'abc'> // false
IsPalindrome<121> // true
解答
import type { Equal, Expect } from '@type-challenges/utils'
type ReverseString<S extends string> = S extends `${infer First}${infer Rest}` ? `${ReverseString<Rest>}${First}` : ''
type IsPalindrome<T extends string | number> =
`${T}` extends `${infer U}${infer Rest extends string}`
? ReverseString<Rest> extends `${infer V}${infer Middle extends string}`
? Equal<U, V> extends true
? IsPalindrome<Middle>
: false
: true
: true;
解説
以前解いた問題のようにまず文字列を反転させる型を持ってきます。
その後、numberはstringにして、先頭の文字と先頭以降の文字を反転させたものの先頭の文字を取得します。取得できない場合は空文字列か1文字しかないケースなのでtrueを返します。取得できた場合は一致していれば残りを次のIsPlaindromeに、一致しなければfalseを返すようにしています。
【上級】Mutable Keys
問題の詳細はこちらです。
問題
Implement the advanced util type MutableKeys, which picks all the mutable (not readonly) keys into a union.
For example:
type Keys = MutableKeys<{ readonly foo: string; bar: number }>;
// expected to be “bar”
解答
import type { Equal, Expect } from '@type-challenges/utils'
type MutableKeys<T> = keyof {
[P in keyof T as Equal<
Readonly<{ [K in P]: T[K] }>,
{ [K in P]: T[K] }
> extends true
? never
: P]: any;
};
解説
適当なオブジェクトを作ってkeyを作るときに、readonlyだったらneverそうでなければPを返すようにします。それのkeyofをとればmutableなkeyを取得できます。
40日目
【上級】Intersection
問題の詳細はこちらです。
問題
Implement the type version of Lodash.intersection with a little difference. Intersection takes an Array T containing several arrays or any type element including the union type, and returns a new union containing all intersection elements.
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 Intersection<T extends any[]> = T extends [infer U, ...infer Rest]
? (U extends any[] ? U[number] : U) & Intersection<Rest>
: unknown
解説
配列を先頭から見ていき、それが配列だったら中身をユニオンで、配列でなければそのままにしました。残ったものでIntersectionをさらにかけそれらとIntersection(&)を取りました。
【上級】Binary to Decimal
問題の詳細はこちらです。
問題
Implement BinaryToDecimal<S>
which takes an exact string type S consisting 0 and 1 and returns an exact number type corresponding with S when S is regarded as a binary. You can assume that the length of S is equal to or less than 8 and S is not empty.
type Res1 = BinaryToDecimal<'10'>; // expected to be 2
type Res2 = BinaryToDecimal<'0011'>; // expected to be 3
解答
type BinaryToDecimal<S extends string, Result extends never[] = []> =
S extends `${infer T}${infer Rest}`
? T extends '0'
? BinaryToDecimal<Rest, [...Result, ...Result]>
: BinaryToDecimal<Rest, [...Result, ...Result, never]>
: Result['length'];
解説
先頭から文字を取っていき、元々のResultを2倍して、1だった場合はさらに要素を追加して次に行きます。後続の数値分だけ2枚されるのでそれぞれの桁数-1乗分たされることになります。
【上級】Object Key Pahts
問題の詳細はこちらです。
問題
Get all possible paths that could be called by _.get (a lodash function) to get the value of an object
type T1 = ObjectKeyPaths<{ name: string; age: number }>; // expected to be 'name' | 'age'
type T2 = ObjectKeyPaths<{
refCount: number;
person: { name: string; age: number };
}>; // expected to be 'refCount' | 'person' | 'person.name' | 'person.age'
type T3 = ObjectKeyPaths<{ books: [{ name: string; price: number }] }>; // expected to be the superset of 'books' | 'books.
解答
type ObjectKeyPaths<T extends unknown, K extends keyof T = keyof T> = T extends
| Record<any, unknown>
| unknown[]
? K extends keyof T & (string | number)
? `${
// if T is an array, K could be in []
T extends unknown[] ? `[${K}]` | K : K
}${
| ''
| `${
// if T[K] is an array, there could be a empty string between T and T[K]
T[K] extends unknown[] ? '' | '.' : '.'
}${ObjectKeyPaths<T[K]>}`}`
: never
: never
解説
解けなかったので一番上の回答を拝借しました。まず、objectでFunction以外のものに絞って操作を開始します。次に、TのキーであるKをextendsを用いて分解します。Tが配列の場合はKとそれを[]で囲ったものでそうでなければKだけで、それに加えて、空文字列とT[K]がさらに続けば、。。。という風に組み立てていってます。
41日目
【上級】Two Sum
問題の詳細はこちらです。
問題
Given an array of integers nums and an integer target, return true if two numbers such that they add up to target.
解答
type LengthArray<L extends number, Result extends unknown[] = []> =
`${L}` extends `${Result['length']}`
? Result
: LengthArray<L, [...Result, unknown]>;
type Sum<T, U> = T extends number ? U extends number ? [...LengthArray<T>, ...LengthArray<U>]['length'] : never : never;
type TwoSum<T extends number[], U extends number> =
T extends [infer V extends number, ...infer Rest extends number[]]
? U extends Sum<V, Rest[number]>
? true
: TwoSum<Rest, U>
: false
解説
全てのsumを出してUと一致すればtrueを返します。
【上級】ValidDate
問題の詳細はこちらです。
問題
Implement a type ValidDate, which takes an input type T and returns whether T is a valid date.
Leap year is not considered
Good Luck!
ValidDate<'0102'> // true
ValidDate<'0131'> // true
ValidDate<'1231'> // true
ValidDate<'0229'> // false
ValidDate<'0100'> // false
ValidDate<'0132'> // false
ValidDate<'1301'> // false
解答
type Base = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type FillZero<T extends string | number> = `${T}` extends `${Base}` ? `0${T}`: T
type toTuple<T extends number, R extends any[] = []> = R['length'] extends T ? R : toTuple<T, [...R, 1]>
type NumberRange<
L extends number,
H extends number,
R extends any[] = toTuple<L>,
Set extends any = never
> = R['length'] extends H
? Set | FillZero<H>
: NumberRange<L, H, [...R, 1], Set | FillZero<R['length']>>
type Dates<> = {
[K in NumberRange<1, 12>] : `${K}${NumberRange<1, 31>}`
}
type Special = "0229" | "0230" | `${"02" | "04" | "06" | "09" | "11"}${31}`
type ValidDate<T extends string> = T extends Exclude<Dates[keyof Dates], Special>
? true
: false
解説
日付ルールの定義がめんどくさいので解答を拝借しました。全部の日にちを定義して、含むかみるだけです。
【上級】Assign
問題の詳細はこちらです。
問題
You have a target object and a source array of objects. You need to copy property from source to target, if it has the same property as the source, you should always keep the source property, and drop the target property. (Inspired by the Object.assign API)
example
type Target = {
a: 'a'
}
type Origin1 = {
b: 'b'
}
// type Result = Assign<Target, [Origin1]>
type Result = {
a: 'a'
b: 'b'
}
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'
}
}
解答
type Assign<T extends Record<string, unknown>, U extends unknown[]> =
U extends [infer V, ...infer Rest]
? V extends object
? V extends Function
? T
: Assign<{
[P in keyof T | keyof V]: P extends keyof V ? V[P] : P extends keyof T ? T[P] : never;
}, Rest>
: T
: T;
解説
Uを一つずつ取り出して、Tに加えていくようにしました。Functionを除くobjectであればTをすぐ返すようにしました(仕様によっては異なる挙動にする必要がありそう)。
42日目
【上級】Capitalize Nest Object Keys
問題の詳細はこちらです。
問題
Capitalize the key of the object, and if the value is an array, iterate through the objects in the array.
解答
type CapitalizeNestObjectKeys<T> = T extends [infer U, ...infer Rest]
? [CapitalizeNestObjectKeys<U>, ...CapitalizeNestObjectKeys<Rest>]
: T extends Record<any, unknown>
? {
[P in keyof T as P extends string
? Capitalize<P>
: P]: T[P] extends object ? CapitalizeNestObjectKeys<T[P]> : T[P];
}
: T;
解説
配列を処理した後にオブジェクトを処理します。似たような問題が過去にあったので詳細は割愛します。
【上級】Run-length encoding
問題の詳細はこちらです。
問題
Given a string sequence of a letters f.e. AAABCCXXXXXXY. Return run-length encoded string 3AB2C6XY. Also make a decoder for that string.
解答
namespace RLE {
type RepeatChar<N, S extends string, T extends never[] = [never], Result extends string = S> =
N extends ""
? S
: `${T['length']}` extends N
? Result
: RepeatChar<N, S, [...T, never], `${Result}${S}`>;
type MakeEncodedStr<S extends string, C extends never[], D extends string> =
`${S}${C['length'] extends 1
? ""
: C['length']}${D}`;
export type Encode<S extends string, D extends string = '', Result extends string = "", Count extends never[] = [never]> =
S extends `${infer T}${infer Rest}`
? T extends D
? Encode<Rest, T, Result, [...Count, never]>
: Encode<Rest, T, MakeEncodedStr<Result, Count, D>>
: MakeEncodedStr<Result, Count, D>;
export type Decode<S extends string, N = ''> =
S extends `${infer T extends number}${infer Rest}`
? Decode<Rest, `${T}`>
: S extends `${infer T}${infer Rest}`
? `${RepeatChar<N, T>}${Decode<Rest>}`
: '';
}
解説
EncodeはDに連続している文字を格納して、継続が終了すれば、MakeEncodeStrにCount+Dの文字列をResultに足しています。
Decodeは最初の文字列が数値であればNに代入して次の文字列をN回繰り返させます。
【上級】Tree path array
問題の詳細はこちらです。
問題
Create a type Path that represents validates a possible path of a tree under the form of an array.
Related challenges:
Object key path
declare const example: {
foo: {
bar: {
a: string;
};
baz: {
b: number
c: number
}
};
}
/* Possible solutions:
[]
['foo']
['foo', 'bar']
['foo', 'bar', 'a']
['foo', 'baz']
['foo', 'baz', 'b']
['foo', 'baz', 'c']
*/
解答
type Path<T, K extends keyof T = keyof T> = K extends unknown
? T[K] extends Record<any, unknown>
? [K] | [K, ...Path<T[K]>]
: [K]
: never;
解説
これで上級の問題は全て解いたことになります。
K extends unknown
でTのキーを展開して、T[K]
がオブジェクトだったら再起的にみて、そうでなければ[K]を返しています。
43日目
【最上級】Get Readonly Key
問題の詳細はこちらです。
問題
オブジェクトの読み取り専用キーの Union を返す汎用的な GetReadonlyKeys<T> を実装してください。
例えば
interface Todo {
readonly title: string
readonly description: string
completed: boolean
}
type Keys = GetReadonlyKeys<Todo> // expected to be "title" | "description"
解答
type GetReadonlyKeys<T> = keyof {
[P in keyof T as Equal<Pick<T, P>, Readonly<Pick<T, P>>> extends true ? P : never]: T[P]
}
解説
最上級ということで身構えていましたが、みたことあるような問題でよかったです。
Readonlyなキーだったときのみのキーを選択してオブジェクトを作成しました。
【最上級】Query String Parser
問題の詳細はこちらです。
問題
You're required to implement a type-level parser to parse URL query string into a object literal type.
Some detailed requirements:
Value of a key in query string can be ignored but still be parsed to true. For example, 'key' is without value, so the parser result is { key: true }.
Duplicated keys must be merged into one. If there are different values with the same key, values must be merged into a tuple type.
When a key has only one value, that value can't be wrapped into a tuple type.
If values with the same key appear more than once, it must be treated as once. For example, key=value&key=value must be treated as key=value only.
解答
type QueryString<S extends string> = S extends `${infer T extends string}=${infer U}`
? { [P in T]: U }
: { [P in S]: true };
type MergeResult<Result, Target> = {
[P in keyof Result | keyof Target]:
P extends keyof Result
? P extends keyof Target
? Result[P] extends unknown[]
? [...Result[P], Target[P]]
: Result[P] extends Target[P]
? Result[P]
: [Result[P], Target[P]]
: Result[P]
: P extends keyof Target
? Target[P]
: never
};
type ParseQueryString<S extends string, Result = {}> =
S extends ''
? Result
: S extends `${infer T}&${infer U}`
? ParseQueryString<U, MergeResult<Result, QueryString<T>>>
: MergeResult<Result, QueryString<S>>;
解説
大作ができました。内容としては割と簡単です。まず、QueryStringという型で一部分だけ切り分けたものをオブジェクトを作ります。次に既存のResultとQueryStringをMergeするMergeResultを作成します。そして、Sが空文字列だったらResultを返し、&などで分離できなくなっていればResultと文字列のQueryStringでマージしたものを返します。&で分離できる場合はParseQueryStringで後半部分を次に、前半部分はQueryStringにして、ResultをMergeしたものを次に渡します。
【最上級】Slice
問題の詳細はこちらです。
問題
Implement the JavaScript Array.slice function in the type system. Slice<Arr, Start, End> takes the three argument. The output should be a subarray of Arr from index Start to End. Indexes with negative numbers should be counted from reversely.
For example
type Arr = [1, 2, 3, 4, 5]
type Result = Slice<Arr, 2, 4> // expected to be [3, 4]
解答
type Slice<Arr extends number[], Start = 0, End = Arr['length'], IndexArr extends never[] = [], IsFlag extends boolean = false, Result extends number[] = []> =
Start extends End
? []
: [...Arr, 0]['length'] extends IndexArr['length']
? []
:IsFlag extends true
? IndexArr['length'] extends End
? Result
: Slice<Arr, Start, End, [...IndexArr, never], IsFlag, [...Result, Arr[IndexArr['length']]]>
: IndexArr['length'] extends Start
? Slice<Arr, Start, End, [...IndexArr, never], true, [...Result, Arr[IndexArr['length']]]>
: Slice<Arr, Start, End, [...IndexArr, never], IsFlag, Result>;
解説
長くなるのでマイナスの値は省略しました。淡々と条件を書いていくだけです。
44日目
【最上級】Integers Comparator
問題の詳細はこちらです。
問題
Implement a type-level integers comparator. We've provided an enum for indicating the comparison result, like this:
If a is greater than b, type should be Comparison.Greater.
If a and b are equal, type should be Comparison.Equal.
If a is lower than b, type should be Comparison.Lower.
Note that a and b can be positive integers or negative integers or zero, even one is positive while another one is negative.
解答
type ComparePlus<A extends number, B extends number, Result extends never[] = []> =
Result['length'] extends A
? Result['length'] extends B
? Comparison.Equal
: Comparison.Lower
: Result['length'] extends B
? Comparison.Greater
: ComparePlus<A, B, [...Result, never]>
type Comparator<A extends number, B extends number> =
`${A}` extends `-${infer T extends number}` ?
`${B}` extends `-${infer U extends number}` ?
ComparePlus<U, T>
: Comparison.Lower
: `${B}` extends `-${number}`
? Comparison.Greater
: ComparePlus<A, B>;
解説
片方がマイナスの時はマイナスのほうが小さいので定数をそのまま返しています。両方ともマイナスな時は絶対値を取って比較を逆転して判定しています。両方ともプラスな時は普通に比較しています。比較は配列を成長させていって最初にその長さに一致したほうが小さいとして行いました。
【最上級】Curring 2
問題の詳細はこちらです。
問題
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
But in our daily life, currying dynamic arguments is also commonly used, for example, the Function.bind(this, [...params]) API.
const func = (a: number, b: number, c: number) => {
return a + b + c
}
const bindFunc = func(null, 1, 2)
const result = bindFunc(3) // result: 6
Thus, based on Currying 1, we would need to have the dynamic argument version:
const add = (a: number, b: number, c: number) => a + b + c
const three = add(1, 1, 1)
const curriedAdd = DynamicParamsCurrying(add)
const six = curriedAdd(1, 2, 3)
const seven = curriedAdd(1, 2)(4)
const nine = curriedAdd(2)(3)(4)
In this challenge, DynamicParamsCurrying may take a function with zero to multiple arguments, you need to correctly type it. The returned function may accept at least one argument. When all the arguments as satisfied, it should yield the return type of the original function correctly.
解答
type UnionToIntersection<U> = [
U extends unknown ? (arg: U) => any : never
] extends [(arg: infer I) => any]
? I
: never
type SplitParameters<T extends unknown[], U extends unknown[] = []> =
| [U, T]
| (T extends [infer A, ...infer B] ? SplitParameters<B, [...U, A]> : never)
type CurriedFn<
T extends unknown[],
U,
SP extends [unknown[], unknown[]] = SplitParameters<T>
> = T['length'] extends 0
? U
: UnionToIntersection<
SP extends unknown ? (...args: SP[0]) => CurriedFn<SP[1], U> : never
>
declare function DynamicParamsCurrying<T extends unknown[], U>(
fn: (...args: T) => U
): CurriedFn<T, U>
解説
引数ごとに関数を作って、インターセクションで取るようにしました。Intersectionにする部分が私はできなくて、理解が足りていないと思いました。
【最上級】Sum
問題の詳細はこちらです。
問題
Implement a type Sum<A, B> that summing two non-negative integers and returns the sum as a string. Numbers can be specified as a string, number, or bigint.
For example,
type T0 = Sum<2, 3> // '5'
type T1 = Sum<'13', '21'> // '34'
type T2 = Sum<'328', 7> // '335'
type T3 = Sum<1_000_000_000_000n, '123'> // '1000000000123'
解答
type Or<left extends boolean, right extends boolean> =
left extends true ? true : right extends true ? true : false;
type CoalesceToString<n extends string | number | bigint> = n extends string ? n : `${n}`;
type Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type SubOneFromDigit<digit extends Digit> =
digit extends "1" ? "0"
: digit extends "2" ? "1"
: digit extends "3" ? "2"
: digit extends "4" ? "3"
: digit extends "5" ? "4"
: digit extends "6" ? "5"
: digit extends "7" ? "6"
: digit extends "8" ? "7"
: digit extends "9" ? "8"
: digit extends "10" ? "9"
: never;
type AddOneToDigit<digit extends Digit> =
digit extends "0" ? { result: "1"; carry: false}
: digit extends "1" ? { result: "2"; carry: false }
: digit extends "2" ? { result: "3"; carry: false }
: digit extends "3" ? { result: "4"; carry: false }
: digit extends "4" ? { result: "5"; carry: false }
: digit extends "5" ? { result: "6"; carry: false }
: digit extends "6" ? { result: "7"; carry: false }
: digit extends "7" ? { result: "8"; carry: false }
: digit extends "8" ? { result: "9"; carry: false }
: digit extends "9" ? { result: "0"; carry: true }
: never;
type SingleDigitSumResult<result extends Digit, carry extends boolean> = { result: result; carry: carry };
type SumSingleDigits<left extends Digit, right extends Digit, carryIn extends boolean = false, carryOut extends boolean = false> =
carryIn extends true
? AddOneToDigit<left> extends SingleDigitSumResult<infer leftIncremented, infer carryOutFromIncrement>
? SumSingleDigits<leftIncremented, right, false, carryOutFromIncrement>
: never
: right extends "0"
? { result: left; carry: carryOut }
: AddOneToDigit<left> extends SingleDigitSumResult<infer leftIncremented, infer carryOutFromIncrement>
? SumSingleDigits<leftIncremented, SubOneFromDigit<right>, false, Or<carryOut, carryOutFromIncrement>>
: never;
type RightMostDigitResult<rest extends string, digit extends Digit> = { rest: rest; digit: digit }
type RightMostDigit<s extends string> =
s extends `${infer rest}${Digit}`
? s extends `${rest}${infer digit}`
? { rest: rest; digit: digit }
: never
: never;
type SumStrings<left extends string, right extends string, accumulatedResultDigits extends string = '', carry extends boolean = false, > =
'' extends left
// Left is empty
? '' extends right
// Right is empty
? carry extends true ? `1${accumulatedResultDigits}` : accumulatedResultDigits
// Right has value
: RightMostDigit<right> extends RightMostDigitResult<infer remainingRight, infer rightDigit>
? SumSingleDigits<'0', rightDigit, carry> extends SingleDigitSumResult<infer resultDigit, infer resultCarry>
? SumStrings<'', remainingRight, `${resultDigit}${accumulatedResultDigits}`, resultCarry>
: never
: never
// Left has value
: '' extends right
// Right has no value
? RightMostDigit<left> extends RightMostDigitResult<infer remainingLeft, infer leftDigit>
? SumSingleDigits<'0', leftDigit, carry> extends SingleDigitSumResult<infer resultDigit, infer resultCarry>
? SumStrings<remainingLeft, '', `${resultDigit}${accumulatedResultDigits}`, resultCarry>
: never
: never
// Right has value
: RightMostDigit<left> extends RightMostDigitResult<infer remainingLeft, infer leftDigit>
? RightMostDigit<right> extends RightMostDigitResult<infer remainingRight, infer rightDigit>
? SumSingleDigits<leftDigit, rightDigit, carry> extends SingleDigitSumResult<infer resultDigit, infer resultCarry>
? SumStrings<remainingLeft, remainingRight, `${resultDigit}${accumulatedResultDigits}`, resultCarry>
: never
: never
: never;
type Sum<left extends string | number | bigint, right extends string | number | bigint> =
SumStrings<CoalesceToString<left>, CoalesceToString<right>>;
解説
大きな数値も含むSumを実装することはできませんんでした。解答は他者のものを持ってきています。Stringに変換して足し算を行なっています。足し算は足し引を定義して、繰り下がり繰り上げを文字列で定義して行なっています。
45日目
【最上級】Multiply
問題の詳細はこちらです。
問題
This challenge continues from 476 - Sum, it is recommended that you finish that one first, and modify your code based on it to start this challenge.
Implement a type Multiply<A, B> that multiplies two non-negative integers and returns their product as a string. Numbers can be specified as string, number, or bigint.
For example,
type T0 = Multiply<2, 3> // '6'
type T1 = Multiply<3, '5'> // '15'
type T2 = Multiply<'4', 10> // '40'
type T3 = Multiply<0, 16> // '0'
type T4 = Multiply<'13', '21'> // '273'
type T5 = Multiply<'43423', 321543n> // '13962361689'
解答
type NumberWithinTen<
T extends string | number,
R extends unknown[] = []
> = `${R['length']}` extends `${T}` ? R : NumberWithinTen<T, [unknown, ...R]>
type StringToCharArray<T extends string> = T extends `${infer A}${infer B}`
? [A, ...StringToCharArray<B>]
: []
type StringToNumberArray<T extends string> = T extends `${infer A}${infer B}`
? [NumberWithinTen<A>, ...StringToNumberArray<B>]
: []
type Reverse<T extends unknown[]> = T extends [infer A, ...infer B]
? [...Reverse<B>, A]
: []
type Shift<T extends unknown[]> = T extends [unknown, ...infer A] ? A : T
type RemoveRightZero<T extends string> = T extends `0${infer A}`
? A extends ''
? '0'
: RemoveRightZero<A>
: T
type SumUnit<
A extends unknown[] | undefined,
B extends unknown[] | undefined,
C extends boolean = false
> = A extends unknown[]
? B extends unknown[]
? C extends true
? [...A, ...B, unknown]
: [...A, ...B]
: C extends true
? [...A, unknown]
: A
: B extends unknown[]
? C extends true
? [...B, unknown]
: B
: C extends true
? [unknown]
: undefined
type BasicSum<
A extends string | number | bigint,
B extends string | number | bigint,
Carry extends boolean = false,
AInNumberArray extends unknown[][] = Reverse<StringToNumberArray<`${A}`>>,
BInNumberArray extends unknown[][] = Reverse<StringToNumberArray<`${B}`>>,
UnitSum extends string = SumUnit<
AInNumberArray[0],
BInNumberArray[0],
Carry
> extends unknown[]
? `${SumUnit<AInNumberArray[0], BInNumberArray[0], Carry>['length']}`
: ''
> = UnitSum extends `${infer SU1}${infer SU2}`
? `${BasicSum<
A,
B,
SU2 extends '' ? false : true,
Shift<AInNumberArray>,
Shift<BInNumberArray>
>}${SU2 extends '' ? SU1 : SU2}`
: UnitSum
type Sum<
A extends string | number | bigint,
B extends string | number | bigint
> =
// @ts-ignore
RemoveRightZero<BasicSum<A, B>>
type MultiplyWithinTen<
A extends unknown[],
B extends unknown[]
> = B['length'] extends 0 ? [] : [...A, ...MultiplyWithinTen<A, Shift<B>>]
type MultiplyUnit<
A extends unknown[] | undefined,
B extends unknown[] | undefined,
Carry extends unknown[] = []
> = A extends unknown[]
? B extends unknown[]
? [...Carry, ...MultiplyWithinTen<A, B>]
: Carry['length'] extends 0
? unknown
: Carry
: Carry['length'] extends 0
? unknown
: Carry
type BasicMultiply<
A extends string | number | bigint,
B extends string | number | bigint,
C extends unknown[] = [],
AInNumberArray extends unknown[][] = Reverse<StringToNumberArray<`${A}`>>,
BInNumber extends unknown[] = NumberWithinTen<`${B}`>,
UnitResult extends string = MultiplyUnit<
AInNumberArray[0],
BInNumber,
C
> extends unknown[]
? // @ts-ignore
`${MultiplyUnit<AInNumberArray[0], BInNumber, C>['length']}`
: ''
> = UnitResult extends `${infer MU1}${infer MU2}`
? `${BasicMultiply<
A,
B,
MU2 extends '' ? [] : NumberWithinTen<MU1>,
Shift<AInNumberArray>,
BInNumber
>}${MU2 extends '' ? MU1 : MU2}`
: UnitResult
type SumProduct<
Product extends unknown[],
Result extends string = '0',
LeftZero extends string = ''
> = Product extends [infer A, ...infer B]
? // @ts-ignore
SumProduct<B, Sum<Result, `${A}${LeftZero}`>, `${LeftZero}0`>
: Result
type Multiply<
A extends string | number | bigint,
B extends string | number | bigint,
BInCharArray extends string[] = Reverse<StringToCharArray<`${B}`>>,
Product extends string[] = []
> = BInCharArray['length'] extends 0
? SumProduct<Product>
: Multiply<
A,
B,
Shift<BInCharArray>,
[...Product, BasicMultiply<A, BInCharArray[0]>]
>
解説
難しくてわかりませんでした。
【最上級】Tag
問題の詳細はこちらです。
問題
Despite the structural typing system in TypeScript, it is sometimes convenient to mark some types with tags, and so that these tags do not interfere with the ability to assign values of these types to each other.
For example, using tags, you can check that some value passes through the calls of the required functions, and in the correct order:
const doA = <T extends string>(x: T) => {
const result = x
return result as Tag<typeof result, 'A'>
}
const doB = <T extends string>(x: T) => {
const result = x
return result as Tag<typeof result, 'B'>
};
const a = doA('foo')
const b = doB(a)
type Check0 = IsTrue<HasTags<typeof b, ['A', 'B']>>
Write a function Tag<B, T extends string>
that takes a type B
other than null
and undefined
and returns a type labeled with the string literal type T
.
The labeled types must be mutually assignable with the corresponding original types:
declare let x: string
declare let y: Tag<string, 'A'>
x = y = x
When tagging a type already marked with a tag, a new tag must be added to the end of the list of all tags of the type:
type T0 = Tag<{ foo: string }, 'A'>
type T1 = Tag<T0, 'B'>
type Check1 = IsTrue<HasExactTags<T1, ['A', 'B']>>
Add some functions to check for type tags.
GetTags<B>
retrieves a list of all tags of a type B
:
type T2 = Tag<number, 'C'>
type Check2 = IsTrue<Equal<GetTags<T2>, ['C']>>
HasTag<B, T extends string>
checks if type B
is tagged with tag T
(and returns true
or false
):
type T3 = Tag<0 | 1, 'D'>
type Check3 = IsTrue<HasTag<T3, 'D'>>
HasTags<B, T extends readonly string[]>
checks if type B
is tagged in succession with tags from tuple T
:
type T4 = Tag<Tag<Tag<{}, 'A'>, 'B'>, 'C'>
type Check4 = IsTrue<HasTags<T4, ['B', 'C']>>
HasExactTags<B, T extends readonly string[]>
checks if the list of all tags of type B
is exactly equal to the T
tuple:
type T5 = Tag<Tag<unknown, 'A'>, 'B'>
type Check5 = IsTrue<HasExactTags<T5, ['A', 'B']>>
Finally, add type UnTag<B>
, which removes all tags from type B
:
type T6 = Tag<{ bar: number }, 'A'>
type T7 = UnTag<T6>
type Check6 = IsFalse<HasTag<T7, 'A'>>
解答
declare const KEY: unique symbol;
type Tuple = readonly string[];
interface Tagged {
readonly [KEY]?: unknown;
}
/**
* Shift<["foo", "bar", "baz"]> = ["bar", "baz"].
*/
type Shift<T extends Tuple> = T extends [infer Head, ...infer Tail] ? Tail : [];
/**
* StartWith<[], ["foo"]> = true.
* StartWith<["foo"], ["foo", "bar"]> = true.
* StartWith<["foo", "baz"], ["foo", "bar"]> = false.
* StartWith<["foo", "bar"], ["foo", "bar", "qux"]> = true.
*/
type StartWith<S extends Tuple, T extends Tuple> = S extends []
? true
: Equal<S[0], T[0]> extends false
? false
: StartWith<Shift<S>, Shift<T>>;
/**
* Includes<["foo", "bar"], ["quux", "foo", "bar", "qux"]> = true.
* Includes<["foo"], ["bar", "qux"]> = false.
*/
type Includes<S extends Tuple, T extends Tuple> = S extends []
? true
: T extends []
? false
: Equal<S[0], T[0]> extends true
? StartWith<S, T>
: Includes<S, Shift<T>>;
/**
* GetStringProps<{ 0: 0; x?: 3 }> = 3.
*/
type GetStringProps<T> = Exclude<
{
[K in keyof T & string]: T[K];
}[keyof T & string],
undefined
>;
/**
* GetStringKeys<{ 0: 0; x?: 3 }> = "x".
*/
type GetStringKeys<T> = Exclude<
{
[K in keyof T & string]: K;
}[keyof T & string],
undefined
>;
/**
* GetTagsKey<null> = "".
* GetTagsKey<Tag<string, "foo">> = "0foo".
* GetTagsKey<Tag<Tag<string, "foo">, "bar">> = "0foo1bar"
*/
type GetTagsKey<
V,
TagsOrUndefined = [V] extends [Tagged] ? V[typeof KEY] : undefined,
TagsKeyOrNever = GetStringKeys<Exclude<TagsOrUndefined, undefined>>
> = Equal<TagsKeyOrNever, never> extends true
? ""
: Equal<TagsKeyOrNever, string> extends true
? ""
: TagsKeyOrNever;
/**
* GetTags<null> = [].
* GetTags<any> = [].
* GetTags<Tag<string, "foo">> = ["foo"].
* GetTags<Tag<Tag<string, "foo">, "bar">> = ["foo", "bar"].
* GetTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">> = ["foo", "bar", "baz"].
*/
export type GetTags<
V,
TagsOrUndefined = [V] extends [Tagged] ? V[typeof KEY] : undefined,
TagsOrNever = GetStringProps<Exclude<TagsOrUndefined, undefined>>
> = Equal<V, any> extends true
? []
: Equal<TagsOrNever, never> extends true
? []
: TagsOrNever extends Tuple
? TagsOrNever
: [];
/**
* Tag<number, "foo"> = number with tag "foo".
* Tag<{ x: 0 }, "foo"> = { x: 0 } with tag "foo".
* Tag<Tag<V, "foo">, "bar"> = V with tags "foo" and "bar".
*/
export type Tag<
V,
T extends string,
Tags extends Tuple = GetTags<V>,
TagsKey extends string = GetTagsKey<V>
> = Equal<V, null> extends true
? null
: Equal<V, undefined> extends true
? undefined
: (typeof KEY extends keyof V ? Omit<V, typeof KEY> : V) & {
readonly [KEY]?: { 0: 0 } & {
[K in `${TagsKey}${Tags["length"]}${T}`]?: [...Tags, T];
};
};
/**
* UnTag<null> = null.
* UnTag<undefined> = undefined.
* UnTag<Tag<{}, "foo">> = {}.
* UnTag<Tag<Tag<{ x: 0 }, "foo">, "bar">> = { x: 0 }.
*/
export type UnTag<V> = typeof KEY extends keyof V ? Omit<V, typeof KEY> : V;
/**
* HasTag<null, "foo"> = false.
* HasTag<Tag<{}, "foo">, "foo"> = true.
* HasTag<Tag<any, "foo">, "foo"> = true.
* HasTag<Tag<Tag<{}, "foo">, "bar">, "foo"> = true.
* HasTag<Tag<Tag<symbol, "bar">, "foo">, "foo"> = true.
* HasTag<Tag<Tag<{}, "bar">, "baz">, "foo"> = false.
*/
export type HasTag<V, T extends string> = Includes<[T], GetTags<V>>;
/**
* HasTags<null, ["foo"]> = false.
* HasTags<Tag<{}, "bar">, ["foo"]> = false.
* HasTags<Tag<any, "bar">, ["foo"]> = false.
* HasTags<Tag<{}, "foo">, ["foo"]> = true.
* HasTags<Tag<any, "foo">, ["foo"]> = true.
* HasTags<Tag<Tag<string, "foo">, "bar">, ["foo", "bar"]> = true.
* HasTags<Tag<Tag<{}, "bar">, "foo">, ["foo", "bar"]> = false.
* HasTags<Tag<Tag<Tag<{}, "baz">, "foo">, "bar">, ["foo", "bar"]> = true.
* HasTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">, ["foo", "bar"]> = true.
* HasTags<Tag<Tag<Tag<{}, "foo">, "baz">, "bar">, ["foo", "bar"]> = false.
*/
export type HasTags<V, T extends Tuple> = Includes<T, GetTags<V>>;
/**
* HasExactTags<0, []> = true.
* HasExactTags<Tag<number, "foo">, ["foo"]> = true.
* HasExactTags<Tag<{}, "foo">, ["bar"]> = false.
* HasExactTags<Tag<Tag<any, "foo">, "bar">, ["foo", "bar"]> = true.
* HasExactTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">, ["foo", "bar"]> = false.
* HasExactTags<Tag<Tag<Tag<{}, "foo">, "bar">, "baz">, ["foo", "bar", "baz"]> = true.
*/
export type HasExactTags<V, T extends Tuple> = Equal<GetTags<V>, T>;
【最上級】Inclusive Range
問題の詳細はこちらです。
問題
Recursion depth in type system is one of the limitations of TypeScript, the number is around 45.
We need to go deeper. And we could go deeper.
In this challenge, you are given one lower boundary and one higher boundary, by which a range of natural numbers is inclusively sliced. You should develop a technique that enables you to do recursion deeper than the limitation, since both boundary vary from 0 to 200.
Note that when Lower > Higher, output an empty tuple.
解答
type Compare<A extends number, B extends number, Idx extends never[] = []> =
Idx['length'] extends A
? Idx['length'] extends B
? 0
: 1
: Idx['length'] extends B
? -1
: Compare<A, B, [...Idx, never]>;
type NumberLengthArray<N extends number, Result extends never[] = []> = Result['length'] extends N ? Result : NumberLengthArray<N, [...Result, never]>;
type Range<LowerArray extends never[], HigherArray extends never[], Result extends number[] = []> =
LowerArray['length'] extends HigherArray['length']
? [...Result, LowerArray['length']]
: Range<[...LowerArray, never], HigherArray, [...Result, LowerArray['length']]>;
type InclusiveRange<Lower extends number, Higher extends number, CompareAB = Compare<Lower, Higher>> =
CompareAB extends -1
? []
: CompareAB extends 0
? [Lower]
: Range<NumberLengthArray<Lower>, NumberLengthArray<Higher>>;
解説
やっと解ける問題が来ました。まず、CompareでLowerがHeightよりも大きい時と等しい時を弾き、適切な値を返します。次に、Rangeを用いて結果を返します。RangeではLowerを成長させてHigherと一致するまで大きくなるまで配列を更新し続け一致したらそれを返すようにしました。
と上記のように思いましたが、問題文をちゃんと読むと45までの再起数だった時の問題みたいでした。この回答では再起に引っかかるのでダメです。
type CompareLen<A extends string, B extends string> = `${A}|${B}` extends `${string}${infer A}|${string}${infer B}` ? A extends "" ? B extends "" ? 0 : -1 : B extends "" ? 1 : CompareLen<A, B> : never;
type CompareDigit<A extends string, B extends string> = A extends B ? 0 : '0123456789' extends `${string}${A}${string}${B}${string}` ? -1 : 1;
type Compare<A extends string, B extends string, Len = CompareLen<A, B>> =
Len extends 0
? `${A}|${B}` extends `${infer AF}${infer AR}|${infer BF}${infer BR}`
? CompareDigit<AF, BF> extends infer N
? N extends 0
? `${AR}${BR}` extends "" ? 0 : Compare<AR, BR>
: N
: never
: never
: Len;
type UpToN<N extends number, C extends 0[] = []> = C['length'] extends N ? C : UpToN<N, [...C, 0]>;
type InclusiveRange<
Lower extends number,
Higher extends number,
E = Compare<`${Lower}`,`${Higher}`>,
C extends 0[] = UpToN<Lower>,
Res extends number[] = []> =
E extends 1 ? [] : E extends 0 ? [Lower] :
C['length'] extends Higher
? [...Res, C['length']]
: InclusiveRange<Lower, Higher, E, [...C, 0], [...Res, C['length']]>;
type test = InclusiveRange<1, 1000>;
Compareの部分は足し算の時のように一桁ずつ行うようになっています。後半は制限に引っかかっていますが、どのコードも引っかかっていたので回答が分かりませんでした。
46日目
【最上級】Sort
問題の詳細はこちらです。
問題
In this challenge, you are required to sort natural number arrays in either ascend order or descent order.
Ascend order examples:
Sort<[]> // []
Sort<[1]> // [1]
Sort<[2, 4, 7, 6, 6, 6, 5, 8, 9]> // [2, 4, 5, 6, 6, 6, 7, 8, 9]
The Sort type should also accept a boolean type. When it is true, the sorted result should be in descent order. Some examples:
Sort<[3, 2, 1], true> // [3, 2, 1]
Sort<[3, 2, 0, 1, 0, 0, 0], true> // [3, 2, 1, 0, 0, 0, 0]
Extra challenges:
Support natural numbers with 15+ digits.
Support float numbers.
解答
type GreaterThan<T extends number, U extends number, Idx extends never[] = []> =
Idx['length'] extends T
? false
: Idx['length'] extends U
? true
: GreaterThan<T, U, [...Idx, never]>;
type GetBiggest<Arr extends number[], Result extends number = 0> =
Arr extends [infer N extends number, ...infer Rest extends number[]]
? GreaterThan<N, Result> extends true
? GetBiggest<Rest, N>
: GetBiggest<Rest, Result>
: Result;
type RemoveNumber<Arr extends number[], Number extends number> =
Arr extends [infer N extends number, ...infer Rest extends number[]]
? N extends Number
? Rest
: [N, ...RemoveNumber<Rest, Number>]
: [];
type Reverse<T extends any[]> = T extends [infer F, ...infer R] ? [...Reverse<R>, F] : []
type Sort<
Arr extends number[],
IsReverse extends boolean = false,
Result extends any[] = [],
InitArrLen extends number = Arr['length'],
Biggest extends number = GetBiggest<Arr>
> = Arr['length'] extends 0
? IsReverse extends true
? Reverse<Result>
: Result
: Result['length'] extends InitArrLen
? never
: Sort<RemoveNumber<Arr, Biggest>, IsReverse, [Biggest, ...Result], InitArrLen>;
解説
反転は最後に行うようにしました。ソートアルゴリズムは選択ソートで行い、一番大きい数値を詰めていきました。
【最上級】Distributeunions
問題の詳細はこちらです。
問題
Implement a type Distribute Unions, that turns a type of data structure containing union types into a union of all possible types of permitted data structures that don't contain any union. The data structure can be any combination of objects and tuples on any level of nesting.
For example:
type T1 = DistributeUnions<[1 | 2, 'a' | 'b']>
// => [1, 'a'] | [2, 'a'] | [1, 'b'] | [2, 'b']
type T2 = DistributeUnions<{ type: 'a', value: number | string } | { type: 'b', value: boolean }>
// => | { type 'a', value: number }
// | { type 'a', value: string }
// | { type 'b', value: boolean }
type T3 = DistributeUnions<[{ value: 'a' | 'b' }, { x: { y: 2 | 3 } }] | 17>
// => | [{ value: 'a' }, { x: { y: 2 } }]
// | [{ value: 'a' }, { x: { y: 3 } }]
// | [{ value: 'b' }, { x: { y: 2 } }]
// | [{ value: 'b' }, { x: { y: 3 } }]
// | 17
For context, this type can be very useful if you want to exclude a case on deep data structures:
type ExcludeDeep<A, B> = Exclude<DistributeUnions<A>, B>
type T0 = ExcludeDeep<[{ value: 'a' | 'b' }, { x: { y: 2 | 3 } }] | 17, [{ value: 'a' }, any]>
// => | [{ value: 'b' }, { x: { y: 2 } }]
// | [{ value: 'b' }, { x: { y: 3 } }]
// | 17
解答
type DistributeUnions<T>
= T extends unknown[] ? DistributeArray<T>
: T extends object ? Merge<DistributeObject<T>>
: T
type DistributeArray<A extends unknown[]>
= A extends [infer H, ...infer T]
? ArrHelper<DistributeUnions<H>, T>
: []
type ArrHelper<H, T extends unknown[]> = H extends H ? [H, ...DistributeArray<T>] : never
type DistributeObject<O extends object, K extends keyof O = keyof O>
= [K] extends [never] ? {}
: K extends K ? ObjHelper<K, DistributeUnions<O[K]>> & DistributeObject<Omit<O, K>>
: never
type ObjHelper<K, V> = V extends V ? { [k in K & string]: V } : never
type Merge<O> = { [K in keyof O]: O[K] }
解説
オブジェクトとarrayで分けて定義して、それぞれをユニオンを分岐させてから再度組み合わせています。
【最上級】
問題の詳細はこちらです。
問題
Sometimes we want to use the good old for
-loop with an index to traverse the array, but in this case TypeScript does not check in any way that we are accessing the elements of the array at its real index (not exceeding the length of the array), and that we are not using an arbitrary number as an index, or index from another array (for nested loops, for traversing matrices or graphs):
const matrix = [
[3, 4],
[5, 6],
[7, 8],
];
// This example contains no type errors when the noUncheckedIndexedAccess option is off.
for (let i = 0; i < matrix.length; i += 1) {
const columns: number[] = matrix[i];
for (let j = 0; j < columns.length; j += 1) {
const current: number = columns[i]; // oops! i instead of j
console.log(
current.toFixed(), // TypeError: Cannot read property 'toFixed' of undefined
);
}
}
You can enable the noUncheckedIndexedAccess option (in tsconfig.json
), but then each time you access an array element, you will need to check that this element exists, which is somewhat verbose and inconvenient, especially since in the case of such a for
-traversal, we are sure that the index does not exceed the length of the array:
const numbers = [5, 7];
for (let i = 0; i < numbers.length; i += 1) {
const current = numbers[i];
if (current !== undefined) {
console.log(current.toFixed());
}
}
Write an assert
-function assertArrayIndex(array, key)
that can be applied to any array
(with an arbitrary unique string key
, which is needed to distinguish arrays at the type level) to allow access to the elements of this array only by the index obtained from array by the special generic type Index<typeof array>
(this functionality requires enabling the noUncheckedIndexedAccess option in tsconfig.json
):
const numbers = [5, 7];
assertArrayIndex(numbers, 'numbers');
for (let i = 0 as Index<typeof numbers>; i < numbers.length; i += 1) {
console.log(numbers[i].toFixed());
}
When accessing by such an index, it must be guaranteed that an element in the array exists, and when accessing an array by any other indices, there is no such guarantee (the element may not exist):
const matrix = [
[3, 4],
[5, 6],
[7, 8],
];
assertArrayIndex(matrix, 'rows');
let sum = 0;
for (let i = 0 as Index<typeof matrix>; i < matrix.length; i += 1) {
const columns: number[] = matrix[i];
// @ts-expect-error: number | undefined in not assignable to number
const x: number[] = matrix[0];
assertArrayIndex(columns, 'columns');
for (let j = 0 as Index<typeof columns>; j < columns.length; j += 1) {
sum += columns[j];
// @ts-expect-error: number | undefined in not assignable to number
const y: number = columns[i];
// @ts-expect-error: number | undefined in not assignable to number
const z: number = columns[0];
// @ts-expect-error: number[] | undefined in not assignable to number[]
const u: number[] = matrix[j];
}
}
The assertArrayIndex
function cannot be called on tuples (since the accessing the elements is already well typed in them):
const tuple = [5, 7] as const;
// @ts-expect-error
assertArrayIndex(tuple, 'tuple');
解答
type Num = ReadonlyArray<0>;
type N0 = readonly [];
type N1 = readonly [0];
type N2 = readonly [0, 0];
type N3 = readonly [0, 0, 0];
type N4 = readonly [0, 0, 0, 0];
type N5 = readonly [0, 0, 0, 0, 0];
type N6 = readonly [0, 0, 0, 0, 0, 0];
type N7 = readonly [0, 0, 0, 0, 0, 0, 0];
type N8 = readonly [0, 0, 0, 0, 0, 0, 0, 0];
type N9 = readonly [0, 0, 0, 0, 0, 0, 0, 0, 0];
/**
* Sum<N3, N4> = N7.
*/
type Sum<N extends Num, M extends Num> = readonly [...N, ...M];
type NA = Sum<N9, N7>;
type NI = Sum<NA, Sum<N9, N5>>;
type NP = Sum<NI, Sum<N9, N8>>;
type NX = Sum<NP, Sum<N9, N4>>;
type Codes = {
' ': N7;
a: Sum<NA, N1>;
b: Sum<NA, N2>;
c: Sum<NA, N3>;
d: Sum<NA, N4>;
e: Sum<NA, N5>;
f: Sum<NA, N6>;
g: Sum<NA, N7>;
h: Sum<NA, N8>;
i: Sum<NI, N1>;
j: Sum<NI, N2>;
k: Sum<NI, N3>;
l: Sum<NI, N4>;
m: Sum<NI, N5>;
n: Sum<NI, N6>;
o: Sum<NI, N7>;
p: Sum<NP, N1>;
q: Sum<NP, N2>;
r: Sum<NP, N3>;
s: Sum<NP, N4>;
t: Sum<NP, N5>;
u: Sum<NP, N6>;
v: Sum<NP, N7>;
w: Sum<NP, N9>;
x: Sum<NX, N1>;
y: Sum<NX, N2>;
z: Sum<NX, N3>;
};
/**
* KeyToNum<'ab'> = N74.
*/
type KeyToNum<Key extends string> = Key extends ''
? N0
: Key extends `${infer L}${infer Rest}`
? L extends keyof Codes
? Sum<Codes[L], KeyToNum<Rest>>
: never
: never;
/**
* IsArray<[0]> = false, IsArray<string[]> = true.
*/
type IsArray<A extends readonly unknown[]> = number extends A['length'] ? true : false;
/**
* IsKey<'ab x'> = true, IsKey<'key!'> = false.
*/
type IsKey<Key extends string> = Key extends ''
? false
: KeyToNum<Key> extends never
? false
: true;
declare const KEY: unique symbol;
declare const CODE: unique symbol;
/**
* WithIndex<string, 'foo'> = object with key index 'foo'.
*/
type WithIndex<
Element,
Key extends string,
KeyCode extends number = KeyToNum<Key>['length']
> = KeyCode extends never
? never
: {
readonly [KEY]: Key;
readonly [CODE]: KeyCode;
} & {
readonly [K in KeyCode]: Element;
};
/**
* Index<typeof indexedArray> = index of indexedArray.
*/
type Index<A extends { readonly [CODE]: number }> = A[typeof CODE];
/**
* assertArrayIndex(arr, 'foo') assert that arr is array with key index 'foo'.
*/
function assertArrayIndex<A extends readonly unknown[], Key extends string>(
array: IsKey<Key> extends true ? (IsArray<A> extends true ? A : never) : never,
key: Key,
): asserts array is IsKey<Key> extends true
? IsArray<A> extends true
? A & WithIndex<A[number], Key>
: never
: never {}
解説
解けませんでした。
47日目
【最上級】Json Parser
問題の詳細はこちらです。
問題
You're required to implement a type-level partly parser to parse JSON string into a object literal type.
Requirements:
Numbers and Unicode escape (\uxxxx) in JSON can be ignored. You needn't to parse them.
解答
//My answer is only for the test cases
type Parse<T extends string> = Eval<T> extends [infer V, infer U] ? V : never
type Eval<T>
= T extends `${' '|'\n'}${infer U}` ? Eval<U>
: T extends `true${infer U}` ? [true, U]
: T extends `false${infer U}` ? [false, U]
: T extends `null${infer U}` ? [null, U]
: T extends `"${infer U}` ? EvalString<U>
: T extends `${'['}${infer U}` ? EvalArray<U>
: T extends `${'{'}${infer U}` ? EvalObject<U>
: false
type Escapes = {r:'\r', n:'\n', b:'\b', f:'\f'}
type EvalString<T, S extends string = ''>
= T extends `"${infer U}` ? [S, U]
: (T extends `\\${infer C}${infer U}` ? C extends keyof Escapes ? [C, U] : false : false) extends [infer C, infer U]
? EvalString<U, `${S}${C extends keyof Escapes ? Escapes[C] : never}`>
: T extends `${infer C}${infer U}` ? EvalString<U, `${S}${C}`>
: false
type EvalArray<T, A extends any[] = []>
= T extends `${' '|'\n'}${infer U}` ? EvalArray<U, A>
: T extends `]${infer U}` ? [A, U]
: T extends `,${infer U}` ? EvalArray<U, A>
: Eval<T> extends [infer V, infer U] ? EvalArray<U, [...A, V]>
: false
type EvalObject<T, K extends string = '', O = {}>
= T extends `${' '|'\n'}${infer U}` ? EvalObject<U, K, O>
: T extends `}${infer U}` ? [O, U]
: T extends `,${infer U}` ? EvalObject<U, K, O>
: T extends `"${infer U}` ? Eval<`"${U}`> extends [`${infer KK}`, infer UU] ? EvalObject<UU, KK, O> : false
: T extends `:${infer U}` ? Eval<U> extends [infer V, infer UU] ? EvalObject<UU, '', Merge<{[P in K]: V} & O>> : false
: false
type Merge<T> = {[P in keyof T]: T[P]}
解説
JSONの形ごとに定義して、地道にParseしていってます。
【最上級】Subtract
問題の詳細はこちらです。
問題
Implement the type Subtraction that is - in Javascript by using BuildTuple.
If the minuend is less than the subtrahend, it should be never.
It's a simple version.
For example
Subtract<2, 1> // expect to be 1
Subtract<1, 2> // expect to be never
解答
type Subtract<M extends number, S extends number, ResultArray extends never[] = [], IdxArray extends never[] = [], IsStart extends boolean = false> =
IdxArray['length'] extends M
? IdxArray['length'] extends S
? 0
: IsStart extends true
? ResultArray['length']
: never
: IsStart extends false
? IdxArray['length'] extends S
? Subtract<M, S, [...ResultArray, never], [...IdxArray, never], true>
: Subtract<M, S, ResultArray, [...IdxArray, never]>
: Subtract<M, S, [...ResultArray, never], [...IdxArray, never], true>;
解説
超上級はほとんど解けなかったので最後の最後で解けてよかったです。Sから解答の数値をカウントし始めてMと一致したら解答するようにしました。Mと一致したときにSも一致していれば0を返すようにしました。さらに、カウントが開始していなければ大小関係がおかしいということでneverを返すようにしました。
最後の方は解けない問題が複数出て苦労しましたが、当初の目標通り毎日3問ずつ解くことができました。これ以上書く気はないのでCloseします。