Open82

type-challenges をやる

azukiazusaazukiazusa

Pick

Implement the built-in Pick<T, K> generic without using it.
Constructs a type by picking the set of properties K from T
For example

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

https://github.com/type-challenges/type-challenges/blob/master/questions/4-easy-pick/README.md

ヒント

この課題をパスするためには、以下の型を知る必要があります。

解説

keyof

keyof はあるオブジェクトからそのキーを string または number のユニオンとして取得します。
例えば、例題にある Todo インターフェイスに keyof を適用すると以下の結果が得られます。

type K = keyof Todo // 'title' | 'description' | 'completed'

回答例ではジェネリクスの2つ目の引数は K extends keyof T であり、つまり MyPick の2つ目の型引数は1つ目の型引数のキーのみを受け取るよう制約を設けています。

Mapped Types

Mapped Types の基本形は { [P in K]: T }であり、それぞれの型変数は以下の通りです。

  • P パラメーター型
  • K 制約型
  • T 付与される型

まず [P in K] という部分はオブジェクトの取りうるキーを反復処理します。in というキーワードはfor...in で使われている in と同じような意味であると考えるとわかりやすいと思います。

このとき Kstring or number or symbol のユニオン型であり、そのユニオンの取りうる数だけオブジェクトのキーが生成されます。

回答例で K として使われている型変数はジェネリクスの1つ目の型引数のキーのユニオンとなっているのでした。このユニオンで指定した数だけキーが反復処理されるので Pick の2つ目の型引数で指定キーだけからなるオブジェクトを生成するという目的を達成できていることがわかるかと思います。

T はそのままオブジェクトのプロパティとして付与される型を表現しています。以下の例を確認してみましょう。

type Foo<T, K extends string> = {
  [P in K]: T
}

type Bar = Foo<string, 'a' | 'b' | 'c'> 

// type Bar = {
//    a: string;
//    b: string;
//    c: string;
// }

Pick は元のキーが保有していた値の型を割り当てる必要があるのですが、その型情報はどこから取得すればよいのでしょうか?

今回の回答例では T の部分は単純な型変数ではなく、T[P] のようにまるでオブジェクトのプロパティにアクセスするような形の型を使用しています。この型を見てみましょう。

Indexed Access Types

Indexed Access Types を使うと通常の JavaScript でオブジェクトのプロパティにアクセスするように、オブジェクトの特定の型を取得できます。

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"]; // number

この課題の回答例では T[P] という形でアクセスされていますが、 P は2つ目の形引数 K の取りうる型を反復処理したものですので、これによりそれぞれのキーに対応する適切な型を指定することができます。

for...in でオブジェクトの個々のプロパティの要素にアクセスする処理を考えてみると、理解しやすいのではないでしょうか?

const todo = {
  title: 'my-title',
  description: 'lorem ipsum',
  completed: false
}
for ( const k in todo) {
  console.log(`key: ${k} value: ${todo[k]}`)
}
# console
key: title value: my-title
key: description value: lorem ipsum
key: completed value: false

回答例

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P] 
}
azukiazusaazukiazusa

Readonly

Implement the built-in Readonly<T> generic without using it.

Constructs a type with all properties of T set to readonly, meaning
the properties of the constructed type cannot be reassigned.

For example

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 

https://github.com/type-challenges/type-challenges/blob/master/questions/7-easy-readonly/README.md

回答例

type MyReadonly<T> = { 
  readonly [K in keyof T]: T[K] 
}

解説

Pick では私達は以下の使い方を学びました。そのことを覚えていれば、簡単に回答できるはずです。

Mapped Types により、型引数で受け取ったすべてのプロパティを反復処理し readonly 修飾子を付与するだけです。

azukiazusaazukiazusa

Tuple to Object

Give an array, transform into an object type and the key/value must in the given array.
For example

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

https://github.com/type-challenges/type-challenges/blob/master/questions/11-easy-tuple-to-object/README.md

解説

PickReadonlyと同じように以下を利用します。

Mapped Types を使用して反復処理をするのはお決まりのパターンです。ただし、今回は渡される型引数がオブジェクトではなくタプルなので Kkeyof Tのパターンは使用できません。

代わりに Indexed Access Types を利用して T[number] という型制約を利用します。配列に対して T[number] とアクセスすると配列から型を取得できます。

const array = ['apple', 'banana', 'strawberry'] as const

type Arr = typeof array // ['apple', 'banana', 'strawberry']

type Fruits = Arr[number] // "apple" | "banana" | "strawberry"

得られた配列の取りうる値をキーとして反復処理を行えばのぞみのオブジェクトを生成することができます。

回答例

type TupleToObject<T extends readonly string[]> = {
  [P in T[number]]: P
}
azukiazusaazukiazusa

First of Array

Implement a generic First<T> that takes an Array T and returns it's first element's type.

For example

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

https://github.com/type-challenges/type-challenges/blob/master/questions/14-easy-first/README.md#first-of-array--

ヒント

この課題をパスするためには以下の機能を知る必要があります。

解説

今までの課題で Indexed Access Type に十分に触れてきたので、配列の先頭の型にアクセスするにはなにをすべきかもうお分かりでしょう。

JavaScript で配列の先頭にアクセスするためには Arr[0] と添字に 0 を用いればよいのでした。型システム上でもそれは変わりありません。

type First<T extends any[]> = T[0]

しかし、現状の実装では型引数として空の配列を渡された時に対応できません。空の配列は 0 というプロパティを持っていないからですね。

このケースに対応するため配列が空かどうかチェックし空の配列なら never を返し、そうでないなら配列の先頭の型を返すという条件分岐が必要です。型システム上で条件分岐を実装するには Conditional Types を使用します。空の配列かどうかの条件部には T extends [] と記述します。

回答例

type First<T extends any[]> = T extends [] ? never : T[0]
azukiazusaazukiazusa

Length of Tuple

For given a tuple, you need create a generic Length, pick the length of the tuple

For example

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

https://github.com/type-challenges/type-challenges/blob/master/questions/18-easy-tuple-length/README.md

ヒント

この課題をパスするためには以下の機能を知る必要があります。

解説

Indexed Access Types のことをすでに知っているのであれば、この課題は驚くほど簡単です。普段配列の要素の数を取得するために Array.length プロパティにアクセスしています。同じように、型変数に対して length プロパティにアクセスすれば タプルの要素数を取得することができます。

type Length<T extends any> = T['length']

しかし、これだけだと T が本当に length プロパティを持っているかどうかわからないので、以下のようなエラーが出力されてしまします。

Type '"length"' cannot be used to index type 'T'.

Tlength プロパティを持っていることを伝えるために extends { length: number } のように制約を持たせることも可能ですが、この指定方法ですとタプルだけでなく string のような型も引数として渡せてしますので、適切ではありません。これを踏まえた回答例は以下になります。

回答例

type Length<T extends readonly any[]> = T['length']
azukiazusaazukiazusa

if

Implement a utils If which accepts condition C, a truthy return type T, and a falsy return type F. C is expected to be either true or false while T and F can be any type.

For example:

type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

https://github.com/type-challenges/type-challenges/blob/master/questions/268-easy-if/README.md

ヒント

この課題をパスするためには、以下の機能を知る必要があります。

解説

型引数 C,T,F を受け取り Ctrue なら Tを、Cfalse なら F を返す If 型を作成します。

まず、問題文から Ctrue もしくは false である必要があるのでまずはここから埋めてしまいましょう。

type If<C extends boolean, T, F> = any

型システム上で if のような条件分岐を実装するためには Conditional Types と呼ばれる機能を使います。構文は以下の通りで、三項演算子と同様の演算子なので直感的に理解しやすいと思います。

SomeType extends OtherType ? TrueType : FalseType;

条件部は SomeTypeOtherType を拡張しているかを定義します。条件を満たす場合には TrueType を返し、そうでないなら FalseTyep を返します。上記の構文を課題の例に当てはめると以下の回答例になります。

回答例

type If<C extends boolean, T, F> = C extends true ? T : F
azukiazusaazukiazusa

Exclude

mplement the built-in Exclude<T, U>

Exclude from T those types that are assignable to U

https://github.com/type-challenges/type-challenges/blob/master/questions/43-easy-exclude/README.md

ヒント

この課題をパスするためには以下の機能を知る必要があります。

解説

Exclude<T, U>U に割り当て可能な型をT から除外する型です。 Union Types から特定の型を取り除く際に使われます。

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"

type T2 = Exclude<string | number | (() => void), Function>; // string | number

この課題を解くにあたり重要になるポイントは Conditional Types分配法則(Distributive) に従うと
いう点です。Conditional Types の条件部において T extends UT が ユニオン型である場合 T に対して反復処理を行い各要素に条件を適用します。

type ToArray<Type> = Type extends any ? Type[] : never;
 
type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]

https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types

そのため、回答例としては T の各要素を反復し TU を拡張可能であれば never を返しそうでないなら T を返すようにすればよいです。

回答例

type MyExclude<T, U> = T extends U ? never : T
azukiazusaazukiazusa

Awaited

If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type? For example if we have Promise<ExampleType> how to get ExampleType?

https://github.com/type-challenges/type-challenges/blob/master/questions/189-easy-awaited/README.md

ヒント

この課題をパスするためには以下の機能を知る必要があります。

解説

この課題でやるべきは Promise<T> から T を取り出すことです。このようにある型から内側の型を取り出すことを Unwrap と呼びます。

はじめのステップとして Promise<string> から string を取り出す例を考えてみましょう。これは型引数 TPromise<string> を拡張可能である場合 string を返すような記述すればよいです。

type Awaited<T extends Promise<any>> = T extends Promise<string> ? string : never

他にも numberboolean の例も出してみましょう。

type Awaited<T extends Promise<any>> = T extends Promise<number> ? number : never
type Awaited<T extends Promise<any>> = T extends Promise<boolean> ? boolean : never

この型を特定の型だけでなく一般性を持たせるためには TPromise<U> を拡張可能であるならば U を返すという記述をすればよさそうです。しかし、U という型変数はどこから取得すればよいのでしょうか? Promise<any> という型を受け取ったうえで、実際に条件が評価されるタイミングになったらその具体的な型を代入したいということをしたいのです。

このような場合には infer キーワードが使えます。inferconditional type のみで使用することができます。infer は「推論」を意味する単語であり、その型になにかわかった時点で型変数にその値を代入します。

回答例

type Awaited<T extends Promise<any>> = T extends Promise<infer U> ? U : never
azukiazusaazukiazusa

Concat

Implement the JavaScript Array.concat function in the type system. A type takes the two arguments. The output should be a new array that includes inputs in ltr order

For example

type Result = Concat<[1], [2]> // expected to be [1, 2]

https://github.com/type-challenges/type-challenges/blob/master/questions/533-easy-concat/README.md#concat--

ヒント

この課題をパスするためには、以下の機能を知る必要があります。

解説

Array.concat と同じことを型システムとして実装します。

ところで、皆さんは普段配列の連結はどのような方法で行っていますか? Array.concat を使う以外の方法として以下のように スプレット構文 をよく使うと思います。

const arr1 = [1,2,3]
const arr2 = [4,5,6]

const result = [...arr1, ...arr2] // [1, 2, 3, 4, 5, 6] 

実は上記の例と同じことが型システムでも可能です。つまりタプル型のなかで ...T と書くことができるのです。 これが Variadic Tuple Types と呼ばれる機能です。

型システムの中でスプレット構文が使えるとあれば、すでに答えは見えてきているのではないでしょうか?

...T の書き方ができる型システムは extends any[] という条件を満たす必要があります。このことを踏まえた回答例は以下の通りです。

回答例

type Concat<T extends any[], U extends any[]> = [...T, ...U]
azukiazusaazukiazusa

Includes

Implement the JavaScript Array.includes function in the type system. A type takes the two arguments. The output should be a boolean true or false.

For example

type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

https://github.com/type-challenges/type-challenges/blob/master/questions/898-easy-includes/README.md

ヒント

解説

はじめに、単純の回答例を考えると以下のようになるのではないでしょうか?

type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;

UTの配列の要素の型に代入可能であるなら true をそうでないなら false を返します。しかし、この回答例ですと以下のケースに対応できません。

回答例

type Includes<T extends readonly any[], U> = T extends [infer L, ...infer R]
  ? [U, L] extends [L, U]
    ? true
    : Includes<R, U>
  : false

これ本当に easy レベルか?

azukiazusaazukiazusa

Push

Implement the generic version of Array.push

For example

type Result = Push<[1, 2], '3'> // [1, 2, '3']

ヒント

この課題をパスするためには、以下の機能を知る必要があります。

解説

Concat と同じ考え方で解くことができます。

JavaScript で Array.push を使わずに配列の末尾に要素を追加するときはどのようにしていますか?

回答例

type Push<T extends any[], U> = [...T, U] 
azukiazusaazukiazusa

Parameters

Implement the built-in Parameters<T> generic without using it.

https://github.com/type-challenges/type-challenges/blob/master/questions/3312-easy-parameters/README.md

ヒント

この課題をパスするためには以下の機能を知る必要があります。

解説

Parameters はある関数型 T 引数の型をタプルとして取得する組み込み型です。

const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: {a: 'A'}): void => {}
const baz = (): void => {}

type T0 = Parameters<typeof foo> // [string, number]
type T1 = Parameters<typeof bar> // [boolean, {a: 'A'}]
type T2 = Parameters<typeof baz> // []

やるべきことは (...args: any[]) => any という型から (...args: any[]) の部分の具体的な型を取得することです。このように実際に条件が評価されるタイミングになってからその具体的な型を取得するには infer キーワードを使用します。

回答例

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

Get Return Type

Implement the built-in ReturnType<T> generic without using it.

For example

const fn = (v: boolean) => {
  if (v)
    return 1
  else
    return 2
}

type a = MyReturnType<typeof fn> // should be "1 | 2"

https://github.com/type-challenges/type-challenges/blob/master/questions/2-medium-return-type/README.md

ヒント

この課題をパスするためには以下の機能を知る必要があります。

解説

基本形は Paramerters と同じですね。型を推論するべき箇所が引数から返り値に変わっています。

回答例

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

Omit

Implement the built-in Omit<T, K> generic without using it.

Constructs a type by picking all properties from T and then removing K

For example

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = MyOmit<Todo, 'description' | 'title'>

const todo: TodoPreview = {
  completed: false,
}

https://github.com/type-challenges/type-challenges/blob/master/questions/3-medium-omit/README.md

ヒント

この課題をパスするためには、以下の型を知る必要があります。

解説

やりたいことは Pick と同じで Mapped Types を使って新しいオブジェクトを作成すればよいわけですが、第2引数で渡されたキーを除外しなければいけないので、単純な Mapped Type を使うだけでは回答できません。

とりあえず現時点でわかるところだけを埋めておきましょう。

type MyOmit<T, K extends keyof T> = any

ここでやりたいことは keyof T を反復した上で反復時の型 PP extends K を満たさないときだけオブジェクトのプロパティに追加することです。

条件分岐が出てきたのでなんとなく Conditional Types を使えばよいことは想像できますが、どうすれば反復処理の中で条件分岐を使うことができるのでしょうか?

Mapped Types 元のプロパティから新しいプロパティを生成したり、あるプロパティを除外するためには as 句を使用します。

as 句は2通りの使い方があります。1つ目は以下の例の通り template literal types を用いてプロパティ名をリネームすることができます。

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
 
interface Person {
    name: string;
    age: number;
    location: string;
}
 
type LazyPerson = Getters<Person>;
         
type LazyPerson = {
    getName: () => string;
    getAge: () => number;
    getLocation: () => string;
}

2つ目の使い方として、as 句の中で never を返した場合にはそのプロパティを除外することができます。今回の課題の場合には as 句の中で PK に対して拡張可能であるか検査しそうであるなら never を返せばよいわけです。

回答例

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

Readonly 2

Implement a generic MyReadonly2<T, K> which takes two type argument T and K.

K specify the set of properties of T that should set to Readonly. When K is not provided, it should make all properties > readonly just like the normal Readonly<T>.

For example

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

https://github.com/type-challenges/type-challenges/blob/master/questions/8-medium-readonly-2/README.md

ヒント

解説

まずは、途中までは Readonly と同じなのでそこまで書いてしまいましょう。

type MyReadonly2<T, K> = {
  readonly [P in keyof T]: T[P]
}

通常の Readonly と異なる点は第2引数で受け取る型のみを readonly とする点です。 Mapped Types の反復処理させる集合を K に変更しましょう。また KT のプロパティ型のみを受け取るように制約を設けます。

type MyReadonly2<T, K extends keyof T> = {
  readonly [P in keyof K]: T[P]
}

一方で第2引数で指定されなかった型はどのように表現するのか考えてみましょう。 readonly を付与しない、ということはなにもしないでそのまま返せばよいのです。

type MyReadonly2<T, K> = T

これで K で指定されたプロパティと指定されなかったプロパティどちらも表すことができました。最終的にこれらの型を結合して返したいのですから、交差型(Intersection Types) を使いましょう。交差型は同じプロパティ名を持つ時後ろの型が優先されるので順番が重要です。

type MyReadonly2<T, K extends keyof T> = T & {
  readonly [P in K]: T[P]
}

しかし、この形ではまだ漏れがあります。 K が渡されなかった場合にはすべてのプロパティを readonly にする必要がありますがその要件を満たせていません。

このエラーを解消するために K に対してデフォルト引数を渡します。これは JavaScriptのデフォルト引数と同様の構文です。デフォルト値として T のプロパティの取りうるすべての値を渡せばすべてのプロパティに対して反復処理が行われるため要件を満たすことができます。

回答例

type MyReadonly2<T, K extends keyof T = keyof T> = T & {
  readonly [P in K]: T[P]
}
R-AzR-Az

こちらの記事ヒントと解説がしっかりされており、とても勉強になります!
素敵な記事をありがとうございます!!

Readonly2で筆者様の回答がテストに引っかかっていたので(ts4.6.4の場合)、僭越ながらご報告させていただきます。
下記が解答を見てまわった感じ一番スッキリしてました。

type MyReadonly2<T, K extends keyof T = keyof T> = {
  readonly [P in K]: T[P]
} & Omit<T, K>

詳しくはこちらご覧いただけますと幸いです。

azukiazusaazukiazusa

報告ありがとうございます👍

この問題の回答当時は ts4.4.4 だったのですが、その後のバージョンアップで挙動が変更になったようですね。

バージョンの記載がなく混乱を招く形になってしまいました🙇‍♂️

R-AzR-Az

なるほど、バージョンの差による物だったんですね、ありがとうございます!

azukiazusaazukiazusa

Deep Readonly

Implement a generic DeepReadonly<T> which make every parameter of an object - and its sub-objects recursively - readonly.

You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on are no need to take into consideration. However, you can still challenge your self by covering different cases as many as possbile.

For example

type X = { 
  x: { 
    a: 1
    b: 'hi'
  }
  y: 'hey'
}

type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'hi'
  }
  readonly y: 'hey' 
}

const todo: DeepReadonly<X> // should be same as `Expected`

https://github.com/type-challenges/type-challenges/blob/master/questions/9-medium-deep-readonly/README.md

ヒント

解説

途中までは Readonly と同じです。

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

ここで DeepReadonly にするための条件を見直してみましょう。この課題ではすべてのパラメーターとそのサブオブジェクトを再帰的に readonly とする必要があると書いてあります。つまり T[P] がオブジェクトならさらにサブオブジェクトまで readonly とし、それ以外ならそのまま T[P] を返せばよいわけです。「T[P] がオブジェクトなら〜」という条件が出てきましたので、ここは Conditional Types の出番です。

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends Record<string, unknown> ? /** T[P]がオブジェクトだったときの処理 */ : T[P]
}

T[P] がオブジェクトかどうかの判定のために組み込み型である Record<Keys, Type>を使用しています。Record<Keys, Type> はプロパティが Keys 型あり、値が Type 型であるオブジェクト型を生成します。

最後に T[P] がオブジェクトだったときの処理を埋めましょう。問題文がヒントとなっているように conditional types おいては再帰的な型を定義することができます。

回答例

type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends Record<string, unknown> ? DeepReadonly<T[P]> : T[P]
}
azukiazusaazukiazusa

Tuple to Union

Implement a generic TupleToUnion<T> which covers the values of a tuple to its values union.

For example

type Arr = ['1', '2', '3']

const a: TupleToUnion<Arr> // expected to be '1' | '2' | '3'

ヒント

解説

配列型にnumber でアクセスすると配列の要素の型を取得できます。

const array = ['apple', 'banana', 'strawberry'] as const

type Arr = typeof array // ['apple', 'banana', 'strawberry']

type Fruits = Arr[number] // "apple" | "banana" | "strawberry"

回答例

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

Tuple to Object が easy なのになんでこちらは medium なのだろうか・・・

azukiazusaazukiazusa

Last of Array

TypeScript 4.0 is recommended in this challenge

Implement a generic Last<T> that takes an Array T and returns it's last element's type.

For example

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

ヒント

解説

配列から最後の要素を取り出す方法をいくつか考えてみましょう。真っ先に思いつくのが arr[arr.length - 1] のように「配列の長さ - 1の添字でアクセスする」という方法ですが、型システム上で四則演算はできません。

type Last<T extends any[]> = T[T['length'] - 1] // ']' expected.

その他の方法を考えてみましょう。単純に配列の先頭の要素から1つずつ取得していって最後に残った要素は配列の最後の要素になります。これを型システム上で表現するには Variadic Tuple Types を使います。JavaScript では構文エラーになる書き方なので、ちょっと気が付きにくいかもしれないですね。

[...any, L]

この形から L を取得できればよさそうです。最後の要素の型を推測するためには infer が使えます、

回答例

type Last<T extends any[]> = T extends [...any, ...infer L] ? L : never
azukiazusaazukiazusa

Pop

TypeScript 4.0 is recommended in this challenge

Implement a generic Pop<T> that takes an Array T and returns an Array without it's last element.

For example

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]

Extra: Similarly, can you implement Shift, Push and Unshift as well?

https://github.com/type-challenges/type-challenges/blob/master/questions/16-medium-pop/README.md

ヒント

解説

Last of Array では配列の最後の要素だけを取得しました。Pop は配列の最後の要素だけを取り除きます。

回答例

type Pop<T extends any[]> = T extends [...infer P, any] ? P : never
azukiazusaazukiazusa

Promise.all

Type the function PromiseAll that accepts an array of PromiseLike objects, the returning value should be Promise<T> where T is the resolved result array.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const)

https://github.com/type-challenges/type-challenges/blob/master/questions/20-medium-promise-all/README.md

ヒント

解説

PromiseAll はある配列の型を受け取りそれを Promise でラップしたものを返します。
はじめのステップとしてまずはそこから記述しましょう。

declare function PromiseAll<T extends any[]>(values: [...T]): Promise<T>

この段階で argument of type 'readonly [1, 2, 3]' is not assignable to parameter of type '[1, 2, 3]'. というエラーが表示されています。引数の型に readonly 修飾子を付与して修正しましょう。

declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<T>

この時点で1つ目のテストケースは成功しますが、残りはエラーとなっています。これは Promise.all は渡された型の配列に Promise でラップされている型が含まれている場合それをアンラップする必要があるためです。1つ目のテストケースには Promise が含まれていないので成功しているわけです。

それではこのエラーを修正しましょう。配列の要素を1つづつ検査し、その要素の型が Promise であった場合 Awaited でやったように Promise<T> から T を取り出せばよいわけです。

回答例

declare function PromiseAll<T extends any[]>(values: readonly [...T]): Promise<{
  [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P]
}>
azukiazusaazukiazusa

TrimLeft

Implement TrimLeft<T> which takes an exact string type and returns a new string with the whitespace beginning removed.

For example

type trimed = TrimLeft<'  Hello World  '> // expected to be 'Hello World  '

https://github.com/type-challenges/type-challenges/blob/master/questions/106-medium-trimleft/README.md

ヒント

解説

やりたいことは、文字列の先頭がスペースかどうか判定してそうであるなら残りの文字で再帰的に TrimLeft を呼び出しそうでないなら文字列をそのまま返せばできそうです。

type TrimLeft<S extends string> = /** 先頭文字スペースか? */ ? TrimLeft<L> : S

問題は先頭文字がスペースか判定し、残りも文字列を取得する条件部をどのように記述するかです。対象の型が配列であったのなら [any, ...infer L] のような形で取得できたのでしょうが今回の対象は文字列なのでそうはいきません。

このような文字列を型として操作したい場合には Template Literal Types の出番です。

以下のように infer と組み合わせて使えば「先頭がスペースある文字列」にマッチさせることができます。

type TrimLeft<S> = S extends ` ${infer L}` ? TrimLeft<L> : S;

しかし、この回答だと最後のテストをパスしません。\n\t も取り除く必要があります。

条件部を「先頭文字が または \n または \t」のように OR 条件で判定する必要がありそうです。嬉しいことに、 Template Literal Types の補完(${}) にはユニオン型を使うこともできます。

以下の例のように補完にユニオン型が使われた場合にはユニオンによって取りうるすべての文字列のユニオン型として表現されます。

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
 
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
//  "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"

課題に戻りましょう。同様に \n\t のユニオン型を使用すれば先頭文字がいずれかの場合もマッチさせることができます。

回答例

type space = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${space}${infer L}` ? TrimLeft<L> : S
azukiazusaazukiazusa

Trim

Implement Trim<T> which takes an exact string type and returns a new string with the whitespace from both ends removed.

For example

type trimed = Trim<'  Hello World  '> // expected to be 'Hello World'

https://github.com/type-challenges/type-challenges/blob/master/questions/108-medium-trim/README.md

ヒント

解説

TrimLeft を拡張して両側からスペースを削除できるようにします。TrimLeft のコードを再掲します。ここから始めましょう。

type space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${space}${infer L}` ? Trim<L> : S

ここでは初めに左側にスペースがあるか再帰的に検査して取り除きます。これ以上左側にスペースが存在しない状態まで進めたら Conditional Typesfalse 句へ入ります。そうしたら今度は右側にスペースがあるパターンでまた同じことをおこなえばよいです。回答例のように Conditional Types はネストして使用できます。

回答例

type space = ' ' | '\n' | '\t'
type Trim<S extends string> = S extends `${space}${infer R}` ? Trim<R> : S extends `${infer L}${space}` ? Trim<L> : S
azukiazusaazukiazusa

Capitalize

Implement Capitalize<T> which converts the first letter of a string to uppercase and leave the rest as-is.

For example

type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'

https://github.com/type-challenges/type-challenges/blob/master/questions/110-medium-capitalize/README.md

ヒント

解説

問題の趣旨とは異なるかもしれないですが、回答例として上げときましょう(

intrinsic キーワードについて以下記事を参考に・・・

https://zenn.dev/uhyo/articles/typescript-intrinsic

回答例

type Capitalize<S extends string> = intrinsic
azukiazusaazukiazusa

ビルドインを使わない回答例として以下のような形式がありますね。

type CharMap = { "a": "A", "b": "B", "c": "C", "d": "D", "e": "E", "f": "F", "g": "G", "h": "H", "i": "I", "j": "J", "k": "K", "l": "L", "m": "M", "n": "N", "o": "O", "p": "P", "q": "Q", "r": "R", "s": "S", "t": "T", "u": "U", "v": "V", "w": "W", "x": "X", "y": "Y", "z": "Z" }
type Capitalize<S extends string> =
    S extends `${infer First}${infer U}` ?
    (First extends keyof CharMap ? `${CharMap[First]}${U}` : S)
    : S;

https://github.com/type-challenges/type-challenges/issues/1356[

小文字と大文字の辞書を定義しておいて Template Literal Types により最初の文字ををキーに辞書から大文字にしたものを取得してから再度文字を結合してるようです。

azukiazusaazukiazusa

Replace

Implement Replace<S, From, To> which replace the string From with To once in the given string S

For example

type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'

https://github.com/type-challenges/type-challenges/blob/master/questions/116-medium-replace/README.md

ヒント

解説

Replace を実装するには、まずは From でマッチする文字列をサーチする必要があります。

Template Literal Types を使えば特定の文字列にマッチさせることは造作もないです。

type Replace<S extends string, From extends string, To extends string> = S extends `${infer L}${From}${infer R}`

あとは文字列にマッチしたなら FromTo にそのまま置き換えるだけでよさそうです。文字列にマッチしなかったら元の文字列をそのまま返します。

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

しかし、まだ1つのテストに失敗します。どうやら From に 空文字 '' が渡されると具合が悪いようです。ここは早期リターンのように From が 空文字 '' だった場合には早々に元の文字列を返してしまいましょう。

回答例

type Replace<S extends string, From extends string, To extends string> = From extends '' 
  ? S
  : S extends `${infer L}${From}${infer R}`  ? `${L}${To}${R}` : S
azukiazusaazukiazusa

ReplaceAll

Implement ReplaceAll<S, From, To> which replace the all the substring From with To in the given string S

For example

type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'

https://github.com/type-challenges/type-challenges/blob/master/questions/119-medium-replaceall/README.md

ヒント

解説

Replace を元に考えてみましょう。Replace は1度文字列にマッチしたらその場で打ち切っていましたが ReplaceAll はすべての対象の文字列を置換する必要があります。

勘のいい方ならもうお分かりかもしれないですが、このような場合は再帰が使えます。

回答例

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

Append Argument

For given function type Fn, and any type A (any in this context means we don't restrict the type, and I don't have in > mind any type 😉) create a generic type which will take Fn as the first argument, A as the second, and will produce > function type G which will be the same as Fn but with appended argument A as a last one.

For example,

type Fn = (a: number, b: string) => number

type Result = AppendArgument<Fn, boolean> 
// expected be (a: number, b: string, x: boolean) => number

This question is ported from the original article > by @maciejsikora

https://github.com/type-challenges/type-challenges/blob/master/questions/191-medium-append-argument/README.md

回答例

type AppendArgument<Fn, A> = Fn extends (...args: infer Args) => infer R ? (...args: [...Args, A]) => R : never
azukiazusaazukiazusa

Length of String

Compute the length of a string literal, which behaves like String#length.

https://github.com/type-challenges/type-challenges/blob/master/questions/298-medium-length-of-string/README.md

ヒント

解説

Length of Tuple と似たような課題に見えますが、一筋縄にはいきません。S['length']number を返します。

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

LengthOfString<'kumiko'> //  number

どうにかして文字数を数える方法はないでしょうか?

考えられる手段として文字列を先頭から1つづつ取り出し再帰的に LengthOfString を呼び出し再帰が行われた回数を数えることができればよさそうです。

type LengthOfString<S extends string> = S extends `${infer F}${infer L}` ? LengthOfString<L> : S

問題はどのように再帰した回数を数えるかです。 型パラメーターにもう一つ number 型の形変数を加えてみるのはどうでしょう?初めはデフォルト引数として 0 を渡しておき、再帰として LengthOfString を呼び出すときには引数とで渡された値 + 1して渡すと再帰した回数を数えられそうです。文字列の最後に達して再帰が終了したときには回数をカウントしていた型変数を返します。

type LengthOfString<S extends string, Count extends number = 0> = S extends `${infer F}${infer L}` 
  ? LengthOfString<L, Count + 1> 
  : Count

良い方法に思えたのですが、これではうまくいきません。型システム上では演算をすることはできないので Count + 1 の部分が不正になります。

他にカウントすることができる方法はないでしょうか?そういえば Length of Tuple では配列の要素の数だけ T['length'] が値を返すことを知ったのでした。これを使えばうまくいきそうです。つまり、再帰があるたびに配列の要素を1つづつ追加していき、文字列の最後に達したなら T['length'] を返せばよいのです。

回答例

type LengthOfString<S extends string, T extends readonly any[] = []> = S extends `${infer F}${infer L}` 
  ? LengthOfString<L, [...T, F]> 
  : T['length']
azukiazusaazukiazusa

響け!ユーフォニアムだ・・・

/* _____________ Test Cases _____________ */
import { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<LengthOfString<''>, 0>>,
  Expect<Equal<LengthOfString<'kumiko'>, 6>>,
  Expect<Equal<LengthOfString<'reina'>, 5>>,
  Expect<Equal<LengthOfString<'Sound! Euphonium'>, 16>>,
]
azukiazusaazukiazusa

Flatten

In this challenge, you would need to write a type that takes an array and emitted the flatten array type.

For example:

type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]

https://github.com/type-challenges/type-challenges/blob/master/questions/459-medium-flatten/README.md

ヒント

解説

再帰処理を用いて、配列の要素を順に先頭から取り出していき処理を行います。まずはそこから記述しましょう。配列の先頭の要素と残りの要素を取得するには [infer F, ...infer L] と書けばよいです。

type Flatten<T extends any[]> = T extends [infer F, ...infer L]
  ? [F, ...Flatten<L>]
  : []

再帰処理の終了時には空の配列を返します。配列を平坦化するためには、配列のある要素が配列であった場合、その要素が配列でなくなるまで Flatten を再帰的に良い出せばよいです。配列の要素が配列かどうかは F extends any[] で判定します。

回答例

type Flatten<T extends any[]> = T extends [infer F, ...infer L]
 ? F extends any[] ? [...Flatten<F>, ...Flatten<L>] : [F, ...Flatten<L>]
 : []
azukiazusaazukiazusa

Append to object

Implement a type that adds a new field to the interface. The type takes the three arguments. The output should be an object with the new field

For example

type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }

https://github.com/type-challenges/type-challenges/blob/master/questions/527-medium-append-to-object/README.md

ヒント

解説

オブジェクトにプロパティを追加する方法として真っ先に思いつくのは交差型を使うことでしょうか?

type AppendToObject<T extends Record<string, unknown>, U extends string, V> = T & { [P in U]: V }

しかし、この回答はテストをパスしません。返される型を確認してみると、交差型として返されているいます。この課題では交差型を使わないでプロパティを追加する必要がありそうです。

type Result = AppendToObject<test1, 'home', boolean>

test1 & {
    home: boolean;
}

あるオブジェクト型から新しいオブジェクト型を生成するためには Mapped Types を使いましょう。まず第1引数のオブジェクト型をそのまま返すには以下のように記述します。

type AppendToObject<T extends Record<string, unknown>, U extends string, V> = {
  [P in keyof T]: T[P]
}

Mapped Types はオブジェクト型のプロパティを反復処理して型を生成します。オブジェクト型に新たなプロパティを追加するには反復処理するプロパティに第2引数の U を追加すればよいでしょう。ユニオン型を使用し Mapped Types の取りうるプロパティに追加します。

type AppendToObject<T extends Record<string, unknown>, U extends string, V> = {
  [P in keyof T | U]: T[P]
}

さらに、ここでは反復処理中の PU 型だった場合にはオブジェクトの値の型として V を渡す必要があります。それ以外の場合なら T のオブジェクトのプロパティなのでそのまま T[P] を返します。

回答例

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

Absolute

Implement the Absolute type. A type that take string, number or bigint. The output should be a positive number string

For example

type Test = -100;
type Result = Absolute<Test>; // expected to be "100"

https://github.com/type-challenges/type-challenges/blob/master/questions/529-medium-absolute/README.md

ヒント

解説

まずは、符号の有無は考えず型引数の numberstring に変換するところを考えてみましょう。これは Template Literal Types を使えば簡単です。

type Absolute<T extends number | string | bigint> = `${T}`

これで <Absolute<10>, '10'>Absolute<9_999n>, '9999'> などの - 符号のついていないテストケースはパスします。

- 符号を取り除くためには string に変換した T を先頭の文字と残りの文字に分解し先頭の文字が - であるなら残りの文字を返しそれ以外の場合なら string に変換した T を返せばよいです。

回答例

type Absolute<T extends number | string | bigint> = `${T}` extends `${infer F}${infer R}`
 ? F extends '-' ? R : `${T}`
 : never
azukiazusaazukiazusa

もっと簡潔な回答例もあります。

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

- 符号を取り除くためにはまず string に変換した T が 先頭に - がついているある文字列にマッチするかどうかを検査します。Template Literal Types を使えば -${infer R} という形式で検査をすることができます。

条件に当てはまった場合には - を除いた残りの文字列である R を返しそうでないなら string に変換した T を返します。

azukiazusaazukiazusa

String to Union

Implement the String to Union type. Type take string argument. The output should be a union of input letters

For example

type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"

https://github.com/type-challenges/type-challenges/blob/master/questions/531-medium-string-to-union/README.md

ヒント

解説

文字列を先頭から一つづつ取り出して処理します。まずは再帰処理の下地を記述しましょう。

type StringToUnion<T extends string> = T extends `${infer L}${infer R}`
  ? StringToUnion<R> : /** 文字列を最後まで処理したら最終結果を返す */

文字列を最後まで処理した時にユニオン型を返す必要がありますが、どこかで取得した文字を保持する必要があります。Tuple to Union でタプル型はユニオン型に変換できることはわかっているので、タプルとして文字を保持しておけば良さそうです。

型システム上でなにか保持しておきたいときのパターンとして初めに空の配列をデフォルト引数を渡しておいて、再帰処理で呼び出すたびに要素を追加するという方法が使えます。

回答例

type StringToUnion<T extends string, U extends any[] = []> = T extends `${infer L}${infer R}`
  ? StringToUnion<R, [...U, L]> : U[number]
azukiazusaazukiazusa

Merge

Merge two types into a new type. Keys of the second type overrides keys of the first type.

https://github.com/type-challenges/type-challenges/blob/master/questions/599-medium-merge/README.md

ヒント

解説

Append to Object と同じ処理を行いましょう。

F のプロパティと S のプロパティを反復処理します。

type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
  [P in keyof F | keyof S]: /** TODO */
};

反復処理の中で PT のプロパティなら F[P] を そうでないなら S[P] を返すようにします。

type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
  [P in keyof F | keyof S]: P extends keyof F ? F[P] : S[P] // Type 'P' cannot be used to index type 'S'.
};

しかしこれではコンパイルが通りません。さらに PS のプロパティであるか検査する必要があります。

type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
  [P in keyof F | keyof S]: P extends keyof F ? F[P] : P extends keyof S ? S[P] : never
};

これでうまくいっているように見えますが、テストケースは失敗します。どこが悪いのか生成された型を確認してみましょう。

type result = Merge<Foo, Bar>
type result = {
    a: number;
    b: string;
    c: boolean;
}

よく見るとプロパティ bFooBar どちらにも存在します。プロパティが重複する場合には2つ目の型のプロパティで上書きする必要があるので b の型は number でなければいけません。

2つ目の型のプロパティで上書きするようにするには、条件部で先に S が持つプロパティかどうかを検査する必要があります。

回答例

type Merge<F extends Record<string, unknown>, S extends Record<string, unknown>> = {
  [P in keyof F | keyof S]: P extends keyof S ? S[P] : P extends keyof F ? F[P]: never
};
azukiazusaazukiazusa

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.

https://github.com/type-challenges/type-challenges/blob/master/questions/949-medium-anyof/README.md

ヒント

解説

回答例

type Falsy = 0 | false | '' | [] | { [P in any]: never }

type AnyOf<T extends readonly any[]> = T extends [infer L, ...infer R]
  ? L extends Falsy ? AnyOf<R> : true
  : false
azukiazusaazukiazusa

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

https://github.com/type-challenges/type-challenges/blob/master/questions/1042-medium-isnever/README.md

ヒント

解説

中級レベルにしては簡単な課題に思いましたか?思ったよりも一筋縄にはいかない問題です。

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

しかし、これではテストに失敗します。IsNever<never> がなぜだか false を返してしまいます。

never 型はあらゆる型のサブタイプになることができますが、あらゆる型は never 型のサブタイプになることができません。つまり、never 型には never 型自身のみしか代入できないのです。

それではどのように Tnever 型に代入可能が検査すればよいのでしょうか?

裏ワザのような方法ですが never[] 型は never[] を拡張することができます。これは Conditional Types の分配法則に関係します。T extends U ? A : BT がユニオン型の場合 T のそれぞれの要素に対して条件型が展開されます。しかし、never は存在しない型として扱われるため T | never は必ず T として扱われます。

type T1 = number | never  // number

つまりは、Conditional Types による型の分配が行われると never は消え去ってしまうのです。

この動作を防ぐためには、Conditional Types による型の分配をさせないようにしなければなりません。実はConditional Types による型の分配は型が型変数である場合のみ起こるものです。型変数でなくするためにはなにか別の型で囲めばよいわけですが、配列の [] で囲うのが一番手軽なのでこの方法が使われるわけです。

回答例

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

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

https://github.com/type-challenges/type-challenges/blob/master/questions/1130-medium-replacekeys/README.md

ヒント

解説

回答例

type ReplaceKeys<U, T, Y> = {
  [P in keyof U]: P extends T 
    ? P extends keyof Y ? Y[P] : never
    : U[P]
}
azukiazusaazukiazusa

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 }

https://github.com/type-challenges/type-challenges/blob/master/questions/1367-medium-remove-index-signature/README.md

ヒント

解説

オブジェクトのプロパティのある条件に当てはまるときに除外するので Key Remapping in Mapped Types を使う方向で考えます。プロパティによりフィルタリングするには as 句の中で never を返せばよいのでした。ベースとなる形から記述しましょう。

type RemoveIndexSignature<T> = {
  [P in keyof T as /** プロパティが index signature のとき */ ? never : P]: T[P]
}

オブジェクトプロパティが index signature のときには除外するため never を返し、それ以外の場合にはプロパティをそのまま返します。

プロパティが index signature かどうか判定する処理を考えてみましょう。あるプロパティが通常のプロパティの場合 foobar のように具体的な型で表現されます。一方で index signature の場合には stringnumber のように広い型で返されます。

一旦 number 型のことは無視して考えるとつまりある型がリテラル型か文字列型か判定できれば良さそうです。

これを判定するためにはリテラル型は文字列型から拡張できますが、文字列型からリテラル型へは拡張できないという性質を利用します。

string extends 'foo' // false
'foo' extends string // true
string extends string // true
type RemoveIndexSignature<T> = {
  [P in keyof T as string extends P ? never : P]: T[P]
}

これで1つ目のテストと3つ目のテストはパスしているはずです。2つ目のテストをパスするためにプロパティが number 型であるパターンを追加しましょう。string extends Pfalse 句に入ったときはまだ Pnumber である可能性が残されているのでここに追加します。

回答例

type RemoveIndexSignature<T> = {
  [P in keyof T as  string extends P ? never : number extends P ? never : P]: T[P]
}
azukiazusaazukiazusa

Percentage Parser

Implement PercentageParser<T extends string>.
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", ""]

https://github.com/type-challenges/type-challenges/blob/master/questions/1978-medium-percentage-parser/README.md

ヒント

解説

文字列をいじくり回すので、お察しの通り Template Literal Types の出番です。

// TODO

回答例

type PercentageParser<A extends string> = A extends `${infer B}${infer C}${'%'}`
  ? B extends '+' | '-' ? [B, C, '%'] : ['', `${B}${C}`, '%']
  : A extends `${infer B}${infer C}` ? B extends '+' | '-' ? [B, C, ''] : ['', `${B}${C}`, '']
  : ['', '', '']
azukiazusaazukiazusa

Drop Char

https://github.com/type-challenges/type-challenges/blob/master/questions/2070-medium-drop-char/README.md

Drop a specified char from a string.

For example:

type Butterfly = DropChar<' b u t t e r f l y ! ', ' '> // 'butterfly!'

ヒント

解説

Replace All とほぼ同じ。マッチした文字を置換する代わりに削除します。

回答例

type DropChar<S extends string, C extends string> = S extends `${infer L}${C}${infer R}`  
  ? `${DropChar<L, C>}${DropChar<R, C>}` : S
azukiazusaazukiazusa

MinusOne

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 MinusOne<T extends number> = T - 1

ここは Length of String でやったことを思い出しましょう。型システムで数を数えるには再帰とタプルを使うのがパターンです。

タプルの lengthT に代入可能 = 同じ数字になるまでタプルに要素を追加していき、最後にタプルの先頭以外の要素を取り出せば計算できます。

type MinusOne<T extends number, U extends readonly any[] = []> = T extends U['length'] 
? U extends [any, ...infer R] ? R['length'] : never 
: MinusOne<T, [...,U any]>

一見良さそうな回答なのですが、いくつかのテストケースに失敗してしまいます。実は TypeScript の再帰は50回が上限です。そのため、50を超える数を型変数 T に渡しているケースでは再帰条件に達してしまい予期する結果が得られないわけです。ですので、なんとかして再帰上限を突破しなければなりません。

再帰上限を突破するには、再帰の再帰を利用します。再帰上限の50は一つのスタックに対して制限ですので、再帰上限を達する前にさらに再帰でスタックをもう一つ増やせばよいわけです。

詳しくは以下の記事を参照してください。

https://qiita.com/kazatsuyu/items/44c1b012d66aae1dc2c0

今回の例では数字の桁ごとに再帰処理を行うようにします。例えば渡された型変数の値が 56 の場合には初めに 6 を取り出して6つの要素を持つタプルを作成してから次に 5 を取り出して5つの要素を持つタプルを作成します。

数字を桁ごとに取り出すためには文字列に変換した後に Template Literal Types を使用して順に取り出します。

type GenerateTuple<T extends string, U extends readonly = any[] = []> = `T extends ${infer L}${infer R}`
  ? GenerateTuple<R, /** Lの要素の数を持つタプルを作る処理 */ > : U
type MinusOne<T extends number> = GenerateTuple<`${T}`> extends [any, ...infer R] ? R['length'] : 0

作成した2つのタプルをマージする際に5の要素を持つタプルは十の位の数でありますから、これを50の要素を持つ配列に変換しておく必要があります。

この変換をするには、以下のように10回同じ配列を展開すればよいわけです。

type Arr10<T extends readonly any[]> = [...T,...T,...T,...T,...T,...T,...T,...T,...T,...T]

もともとあったある数字からその数だけ要素を持つタプルを作成する処理を type として作成しておきましょう。

type DigitToArr<T extends number, U extends readonly any[] = []> = T  extends U['length'] ? U : DigitToArr<T, [...U, any]>

ただし、今回の変更で文字列化した数字を受け取る必要があります。U['length] に拡張可能化調べるのに今度は文字列から数値に変換する必要があります。そのために 09 の要素を持つタプル型を作成しておきます。LenArr['1']1 を返すので、これを使えば数値方に変化できるわけです。

type LenArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type StrDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type DigitToArr<T extends StrDigit, U extends readonly any[] = []> = LenArr[T] extends U['length'] ? U : DigitToArr<T, [...U, any]>

作成した DigitToArrGenerateTuple の中で使用しましょう。DigitToArr の型引数は StrDigit 型である必要があるのでこれを検査する必要があります。

type GenerateTuple<T extends string, U extends readonly any[] = []> = T extends `${infer L}${infer R}` 
  ? L extends StrDigit ? GenerateTuple<R, [...Arr10<U>, ...DigitToArr<L>]> : never
  : U

回答例

type LenArr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
type StrDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Arr10<T extends readonly any[]> = [...T,...T,...T,...T,...T,...T,...T,...T,...T,...T]
type DigitToArr<T extends StrDigit, U extends readonly any[] = []> = LenArr[T] extends U['length'] ? U : DigitToArr<T, [...U, any]>
type GenerateTuple<T extends string, U extends readonly any[] = []> = T extends `${infer L}${infer R}` 
  ? L extends StrDigit ? GenerateTuple<R, [...Arr10<U>, ...DigitToArr<L>]> : never
  : U
type MinusOne<T extends number> = GenerateTuple<`${T}`> extends [any, ...infer R] ? R['length'] : 0
azukiazusaazukiazusa

PickByUnion

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; }

https://github.com/type-challenges/type-challenges/blob/master/questions/2595-medium-pickbytype/README.md

ヒント

解説

オブジェクトから特定の条件のプロパティを取り出すときたら Key Remapping in Mapped Types の出番です。T[P]U に代入可能であればそのまま P を返しそうでないなら never を返してプロパティを除外します。

回答例

type PickByType<T, U> = {
  [P in keyof T as T[P] extends U ? P: never]: T[P]
}
azukiazusaazukiazusa

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

https://github.com/type-challenges/type-challenges/blob/master/questions/2688-medium-startswith/README.md

ヒント

解説

逆詐称気味。Template Literal Types を使えばある文字列が特定の文字にマッチするか判定するのは容易です。

回答例

type StartsWith<T extends string, U extends string> = T extends `${U}${any}` ? true : false
azukiazusaazukiazusa

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 }

https://github.com/type-challenges/type-challenges/blob/master/questions/2757-medium-partialbykeys/README.md

ヒント

解説

一瞬 Key Remapping in Mapped Types が有効に働きそうに思えますが、条件によってプロパティを取り除くことはできても readonly? などの修飾子を付与するかどうかの判定は行えません。

回答の考え方は以下のとおりです。

  • K で指定したプロパティのみを反復処理してすべてオプショナルを付与したオブジェクト型を作成する
  • T から K を取り除いたオブジェクト型を作成してマージする
type PartialByKeys<T, K extends PropertyKey = keyof T> = {
  [P in keyof T]?: T[P]
} & Omit<T, K>

この状態ですとインターセクション型として表現されてしまうため、インターセクション型をフラットなオブジェクトに変換する型を定義してこれを利用します。

type IntersectionToObj<T> = {
  [P in keyof T]: T[P]
}

回答例

type IntersectionToObj<T> = {
  [P in keyof T]: T[P]
}

type PartialByKeys<T, K extends PropertyKey = keyof T> = IntersectionToObj<{
  [P in keyof T]?: T[P]
} & Omit<T, K>
>
azukiazusaazukiazusa

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 UserPartialName = RequiredByKeys<User, 'name'> // { name: string; age?: number; address?: string }

https://github.com/type-challenges/type-challenges/blob/master/questions/2759-medium-requiredbykeys/README.md

ヒント

解説

PartialByKeysとほぼ同じ。オプショナル修飾子(?)の前に - を付与するとその修飾子を取り除けます。

回答例

type IntersectionToObj<T> = {
  [P in keyof T]: T[P]
}

type RequiredByKeys<T, K extends PropertyKey = keyof T> = IntersectionToObj<{
  [P in keyof T]-?: T[P]
} & Omit<T, K>
>
azukiazusaazukiazusa

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<T> // { title: string; description: string; >completed: boolean; }

https://github.com/type-challenges/type-challenges/blob/master/questions/2793-medium-mutable/README.md

ヒント

解説

readonly? などの修飾子に - を付与するとその修飾子を取り除くことができます。

回答例


type Mutable<T> = {
  -readonly[P in keyof T]: T[P]
}
azukiazusaazukiazusa

Object Entries

type ObjectEntries<T, U extends keyof T = keyof T> = U extends infer R ? [R, Exclude<T[U], undefined>] : never
azukiazusaazukiazusa

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

https://github.com/type-challenges/type-challenges/blob/master/questions/3188-medium-tuple-to-nested-object/README.md

回答例

type TupleToNestedObject<T extends readonly any[], U> = T extends [infer L, ...infer R]
  ? L extends string 
  ? { [P in L]: TupleToNestedObject<R, U> }
  : never
  : U
azukiazusaazukiazusa

FlipArguments

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

https://github.com/type-challenges/type-challenges/blob/master/questions/3196-medium-flip-arguments/README.md

回答例

type FlipArguments<T, U extends readonly any[] = []> = T extends (...args: infer Args) => infer R
  ? Args extends [...infer F, infer L]
    ? FlipArguments<(...args: F) => R, [...U, L]>
    : (...args: U) => R
  : never
azukiazusaazukiazusa

Flatten Depth

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.

https://github.com/type-challenges/type-challenges/blob/master/questions/3243-medium-flattendepth/README.md

ヒント

解説

Flatten を拡張して平坦化の回数に制限を加えたものです。Flatten の回答を前提に始めましょう。

type Flatten<T extends any[]> = T extends [infer F, ...infer L]
 ? F extends any[] ? [...Flatten<F>, ...Flatten<L>] : [F, ...Flatten<L>]
 : []

追加の要素として第2引数に平坦化する回数を受け取ります。デフォルトは 1 です。そして再帰のたびに平坦化した回数をカウントして 第2引数の値と一致したときには再帰を終了するようにします。数を数えるにはお決まりのパターンとしてデフォルトが空のタプルを渡します。

type Flatten<T extends any[], N extends number = 1, A extends readonly any[] = []> = T extends [infer F, ...infer L]
 ? F extends any[] ? [...Flatten<F>, ...Flatten<L>] : [F, ...Flatten<L>]
 : []

後は再帰のたびにタプルの要素を増やしていく処理を追加しておけばよいです。注意点として再帰の全体の数の制限ではなく、ある配列出会ったときその深さに対して制限を適用するので先頭以外の要素に対して再帰呼び出しをするときにはタプルの要素を増やしません。

回答例

type FlattenDepth<T extends any[], N extends number = 1, A extends readonly any[] = []> = A['length'] extends N ? T
  : T extends [infer F, ...infer L] 
  ? F extends any[] ? [...FlattenDepth<F, N, [any, ...A]>, ...FlattenDepth<L, N>] : [F, ...FlattenDepth<L, N, A>]
 : []
azukiazusaazukiazusa

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

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

ヒント

解説

オブジェクトを変換するときには Mapped Types の出番です。
プロパティも変更する必要があるので Key Remapping in Mapped Types を使用して プロパティにもともと値であったものを割り当てます。

type Flip<T> = {
  [P in keyof T as T[P]]: P
}

この段階では 3 つ目のテストに失敗してしまいます。オブジェクトのプロパティには string number symbol しか使えないのですが boolean を割り当てようとしているためです。

回答例としてプロパティとして割り当てようとしている値が string number symbol ではなかったときには Template Literal Types を使い無理やり string に変換してやればよいです。

回答例

type Flip<T> = {
  [P in keyof T as T[P] extends PropertyKey ? T[P] : `${T[P]}`]: P
}
azukiazusaazukiazusa

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

Good Luck!

https://github.com/type-challenges/type-challenges/blob/master/questions/4425-medium-greater-than/README.md

解説

accumulator として いつものタプルを型引数に追加します。

type GreaterThan<T extends number, U extends number, A extends any[] = []>

再帰の度にタプルの要素を一つづつ増やしていき先に T が タプルの要素と同じとなったら T のほうが小さいと判定でき、先に U がタプルの要素と同じになるなら U のほうが小さいと判定できます。

回答例

type GreaterThan<T extends number, U extends number, A extends any[] = []> = A['length'] extends T
  ? false
  : A['length'] extends U
  ? true
  : GreaterThan<T, U, [any, ...A]>;
azukiazusaazukiazusa

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).

https://github.com/type-challenges/type-challenges/blob/master/questions/3326-medium-bem-style-string/README.md

ヒント

解説

文字列を操作するので Template Literal Types の出番です。深く考えずに B__E--M のように結合してあげればよいわけです。

ここでは EMstring ではなく string[] 型として渡されることとなっているので素直に結合はできません。EM は ユニオン型とに変換されることが期待されているので Tupple To Union でやったように E[number] として変換します

type BEM<B extends string, E extends string[], M extends string[]> = `${B}__${E[number]}--${M[number]}`

この段階ではいくつかのテストに失敗します。EM は空の配列として渡される可能性があるのでその場合には結果から除外する必要があります。

空の配列の判定は E['length'] extends 0 のように配列の長さが 0 であれば調べればよいです。

回答例

type BEM<B extends string, E extends string[], M extends string[]> = E['length'] extends 0 ? `${B}--${M[number]}`
  : M['length'] extends 0 ? `${B}__${E[number]}`
  : `${B}__${E[number]}--${M[number]}`
azukiazusaazukiazusa

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]]

https://github.com/type-challenges/type-challenges/blob/master/questions/4471-medium-zip/README.md

ヒント

解説

この課題でやりたいのは以下のようなことです。

type Zip<T extends readonly any[], U extends readonly any[]> = [[T[0], U[0]], [T[1], U[1]]

for ループのように配列の要素を一つづつ処理したい場合には Variadic Tuple Typesinfer で先頭の要素と残りの要素に分けて再帰を使用します。

回答例

type Zip<T extends readonly any[], U extends readonly any[], A extends readonly any[] = []> = 
  T extends [infer RF, ...infer RL] ?
  U extends [infer UF, ...infer UL] ?
  Zip<RL, UL, [...A, [RF, UF]]>
  : A : A
azukiazusaazukiazusa

TrimRight

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'

https://github.com/type-challenges/type-challenges/blob/master/questions/4803-medium-trim-right/README.md

ヒント

解説

TrimLeft と同じ

回答例

type space = ' ' | '\n' | '\t'

type TrimRight<S extends string> = S extends `${infer L}${space}` ? TrimRight<L> : S
azukiazusaazukiazusa

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

https://github.com/type-challenges/type-challenges/blob/master/questions/4484-medium-istuple/README.md

解説

まずはタプルを満たす条件についておさらいしておきましょう。

  1. readonly である
  2. 配列を拡張した型である
  3. 配列の長さは固定である

ひとまず上記2つを満たしているかどうか調べることでいくつかのテストはパスします。

type IsTuple<T> = T extends readonly any[] ? true : false

しかし、この状態以下のテストのみ失敗してしまいます。

Expect<Equal<IsTuple<number[]>, false>>,

number[] は タプル型ではありませんが 1. と 2. の条件を満たします。3. の配列の長さの条件を調べることによってこの形は除外することができます。

通常の配列型の場合には長さが未確定のため T['length'] にアクセスした場合には number 型を返しますが、タプル型の場合に T['length'] にアクセスした場合にはより具体的な型('2', '3' など)を返します。

つまり numberT['length'] を拡張可能か追加で調べればよいわけです。

回答例

type IsTuple<T> = T extends readonly any[] ? 
  number extends T['length'] ? false : true : false
azukiazusaazukiazusa

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 :)

https://github.com/type-challenges/type-challenges/blob/master/questions/4518-medium-fill/README.md

ヒント

解説

配列の要素を1つづつ取り出してまずはいつもの infer で先頭の要素だけ取り出します。ついでに再帰処理御用達の accumulator も型引数に追加

type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?

配列の要素を N で置き換えるのは現在の配列のインデックスが Start 以上かつ End 以下の場合のみなのでその条件を追加します。

type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
      A['length'] extends Start ?
        Start extends End ?

現在の配列のインデックスが End 以上の場合にはこれ以上置き換える必要がないことがわかっているので accumulator と現在の配列の要素を返して終了します。

type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
      A['length'] extends Start ?
        Start extends End ?
          [...A, ...T] 

Start 以上 End 以下の時には accumulator に 置き換え対象の N を渡して再帰します。この時 次の再帰で A['length'] extends Start を満たすようにするために Start の値をインクリメントします。

Start 以下の場合には accumulator に現在の要素を追加して再帰をします。

回答例

type Fill<
  T extends unknown[],
  N,
  Start extends number = 0,
  End extends number = T['length'],
  A extends readonly any[] = []
> = T extends [infer L, ...infer R] ?
      A['length'] extends Start ?
        Start extends End ?
          [...A, ...T] 
        : Fill<R, N, [...A, any]['length'] & number, End, [...A, N]>
      : Fill<R, N, Start, End, [...A, L]>
    : A
azukiazusaazukiazusa

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>; // excepted to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // excepted to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // excepted to be []

https://github.com/type-challenges/type-challenges/blob/master/questions/5117-medium-without/README.md

ヒント

回答例

いつものように再帰で配列の要素を一つづつ取り出して処理を行います。
やることは簡単で再帰処理の中で要素が U を拡張可能であるなら accumulator に追加せず、そうでないなら accumulator に追加すれば U の要素を取り除いた配列が出来上がります。

type Without<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]
  ? L extends U ? Without<R, U, A> : Without<R, U, [...A, L]>
  : A

ただ一つ厄介なのは Unumbernumber[] どちらかも受け付ける点です。Unumber[] 型で来る場合には一度ユニオン型に変換してから判定を行う必要があります。

タプルをユニオン型に変換するには U[number] です。

回答例

type Without<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R] 
  ? U extends any[]
    ? L extends U[number] ? Without<R, U, A> : Without<R, U, [...A, L]>
    : L extends U ? Without<R, U, A> : Without<R, U, [...A, L]>
  : A
azukiazusaazukiazusa

Trunk

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

https://github.com/type-challenges/type-challenges/blob/master/questions/5140-medium-trunc/README.md

ヒント

解説

期待結果が string なことがもはやヒントのようなものですが、Template Literal Types を使います。

小数点 . で前後を切り分けて . より前の部分を取り出せばよいです。

回答例

type Trunc<T extends string | number> = `${T}` extends `${infer A}.${infer B}` ? A : `${T}`
azukiazusaazukiazusa

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>; // excepted to be 1
type Res1 = IndexOf<[2,6, 3,8,4,1,7, 3,9], 3>; // excepted to be 2
type Res2 = IndexOf<[0, 0, 0], 2>; // excepted to be -1

https://github.com/type-challenges/type-challenges/blob/master/questions/5153-medium-indexof/README.md

ヒント

解説

いつもの再帰セット。

type IndexOf<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]

LU を拡張可能ならその時点での accumulator の要素数を返します。
要素が見つからなかったときには -1 を返します。

回答例

type IndexOf<T extends readonly any[], U, A extends readonly any[] = []> = T extends [infer L, ...infer R]
  ? L extends U ? A['length'] : IndexOf<R, U, [...A, any]>
  : -1  
azukiazusaazukiazusa

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'

https://github.com/type-challenges/type-challenges/blob/master/questions/5310-medium-join/README.md

回答例

type Join<T extends readonly any[], U, A extends string = ''> = T extends [infer L, ...infer R]
  ? A extends ''
   ? Join<R, U, `${L}`>
   : Join<R, U, `${A}${U}${L}`> 
  : A
azukiazusaazukiazusa

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

https://github.com/type-challenges/type-challenges/blob/master/questions/5317-medium-lastindexof/README.md

回答例

type LastIndexOf<T extends any[], U, A extends any[] = [], C extends any[] = []> = T extends [infer L, ...infer R]
  ? L extends U ? LastIndexOf<R, U, [...A, any], A> : LastIndexOf<R, U, [...A, any], C>
  : C['length'] extends 0 ? -1 : C['length']
azukiazusaazukiazusa

Unique

Implement the type version of Lodash.uniq, Unique<T> 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"]

https://github.com/type-challenges/type-challenges/blob/master/questions/5360-medium-unique/README.md

ヒント

解説

いつもの再帰セットで配列をループし accumulator にすでに追加済みであれば何もせずに、そうでないなら accumulator に追加します。

accumulator に追加済みかどうかは accumulator をユニオン型に変換して判定を行います。

回答例

type Unique<T extends any[], A extends any[] = []> = T extends [infer L, ... infer R]
  ? L extends A[number] ? Unique<R, A> : Unique<R, [...A, L]>
  : A
azukiazusaazukiazusa

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())
    }
  }
})

https://github.com/type-challenges/type-challenges/blob/master/questions/6-hard-simple-vue/README.md

ヒント

解説

data,computed,methodsのみのもつ簡易的な Vue ライクな型を作成します。

以下の目的を達成する必要があります。

  • computed 内の this からは data,computed にアクセスできる
  • methods 内の this からは data,computed,methods にアクセスできる
  • computed はメソッドではなくオブジェクトのプロパティとしてアクセスできる
  • data 内の this からは他のプロパティにアクセスできない

まずやるべきは、data,computed,methdos のそれぞれの型を取得することです。これらは動的に変化するので、ジェネリクスによる型変数を利用して型を取得できます。

declare function SimpleVue<D, C, M>(options: {
  data: () => D,
  computed: C
  methods: M
}): any

続いて this に型を付けていきましょう。プロパティが data のような関数の場合には関数の1番目の引数を this にすることで、this の型を指定できます。datathis からは他のどのプロパティについてもアクセスできないので空のオブジェクト {}this として渡します。

declare function SimpleVue<D, C, M>(options: {
  data: (this: {}) => D,
  computed: C
  methods: M
}): any

computed,methods のようなオブジェクトのプロパティに対して this の型を付けるために ThisType と呼ばれる Utility Type を使います。

declare function SimpleVue<D, C, M>(options: {
  data: (this: {}) => D,
  computed: C & ThisType<D & C>
  methods: M & ThisType<D & M & C>
}): any

ここまででほとんどのエラーは取り除かれましたが methods 内で computed プロパティにアクセスするときに適切な型が得られていません。

computed の元々の型自体はメソッドとして定義していますが、this から computed にアクセスするときには通常のオブジェクトのプロパティとしてアクセスできる必要があるためです。

そのため C はプロパティはそのままに値の型をを関数の返り値の型に変換する必要があります。
関数の返り値の型を得るためには ReturnType を使用します。

回答例

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

declare function SimpleVue<D, C, M>(options: {
  data: (this: {}) => D,
  computed: C & ThisType<D & C>
  methods: M & ThisType<D & M & GetComputed<C>>
}): any
azukiazusaazukiazusa

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.

https://github.com/type-challenges/type-challenges/blob/master/questions/17-hard-currying-1/README.md

type Curry<T, U> = T extends [infer L, ...infer R] 
  ? (a: L) => C<R, U> : U

declare function Currying<F>(fn: F): F extends (...args: infer T) => infer U ? Curry<T,U>: never