mosya
mosya Business はこちら

mosya<TC> - ネストされた配列をフラットにする型を作ろう

この記事はmosya<TC>の問題の一つであるFlatten型の解説になります。

問題

配列 T を受け取り、その最後の要素の型を返す汎用的な Flatten<S, T> を実装してください。

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

前提知識

この問題を解くにあたって型についての以下の知識を理解しておく必要があります。

  1. Conditional Typesを理解する
  2. inferを理解する
  3. スプレッド演算子を理解する

Conditional Typesを理解する

Conditional Typesは、条件によって型を変更することができる機能です。

例えば、以下のような型が考えられます。

type Foo<T> = T extends string
  ? string
  : number;

この型は、Tstring型を継承している場合はstring型を、そうでない場合はnumber型を返します。

このように、extendsを使って条件を指定することで、型を変更することができます。

inferを理解する

inferは、型を推論することができる機能です。

例えば、以下のような型が考えられます。

type ArrayItem<T> =
  T extends (infer R)[] ? R : never;

この型は、Tが配列の場合は、配列の中の型を返します。

以下のように使うことができます。

type Foo = ArrayItem<string[]>; // string

この場合、infer Rにはstring[]型が当てはまるので、Rstring型に推論され、Rを返すのでstring型が返されます。
このように推論される型を取得するのにinferは役立ちます。

スプレッド演算子を理解する

スプレッド演算子...は、配列やオブジェクトの中身を展開する演算子です。
これを使うと配列やオブジェクトの中身を展開して、新しい配列やオブジェクトを生成することができます。
実際の値だけでなく型にもこの演算子を使うことができます。

例えば、以下のような型が考えられます。

type Foo = [1, 2, 3];

type Bar = [...Foo, 4]; // [1, 2, 3, 4]

解答例

type Flatten<
  S extends any[],
  T extends any[] = []
> = S extends [infer X, ...infer Y]
  ? X extends any[]
    ? Flatten<[...X, ...Y], T>
    : Flatten<[...Y], [...T, X]>
  : T;

まず、今回、Flattenに代入される方は配列が前提なので、S extends any[]という条件を指定します。

type Flatten<S extends any[], T extends any[] = []> = ...

次に、Sが配列型の場合は、その配列の最初の要素を取得してXに代入し、残りの要素をYに代入します。

type Flatten<S extends any[], T extends any[] = []> = S extends [infer X, ...infer Y] ? ...

そして、Xが配列型の場合は、Xを展開してYと結合し、Xが配列型でない場合は、Yだけを展開して、Xを展開したTと結合し、再びFlattenに渡します。

このように徐々に展開済みの配列を第2引数のTに格納していき、最終的にSが空になったらTを返すことで、配列を展開することができます。

type Flatten<
  S extends any[],
  T extends any[] = []
> = S extends [infer X, ...infer Y]
  ? X extends any[]
    ? Flatten<[...X, ...Y], T>
    : Flatten<[...Y], [...T, X]>
  : T;

例えば、配列、[1, 2, [3, 4], [[[5]]]]Flattenに渡すと、以下のように段階的に展開されます。

Flatten<[1, 2, [3, 4], [[[5]]]]>;

// 1回目
Flatten<[[2, [3, 4], [[[5]]]]], [1]>;

// 2回目
Flatten<[[3, 4], [[[5]]]], [1, 2]>;

// 3回目
Flatten<[[4], [[[5]]]], [1, 2, 3]>;

// 4回目
Flatten<[[[[5]]]], [1, 2, 3, 4]>;

// 5回目
Flatten<[], [1, 2, 3, 4, 5]>[
  // 6回目
  (1, 2, 3, 4, 5)
];

Authored by

筆者の写真

Godai@steelydylan

Webサービスを作るのが好きなWebエンジニア。子供が産まれたことをきっかけに独立し法人化。サービス開発が大好き。
好きな言語はTypeScript。

ReactやTypeScriptなどの周辺技術が学べる
オンライン学習サービスを作りました!

詳しくはこちら
mosya

mosyaはオンラインでHTML,CSS,JavaScriptを基本から学習できるサービスです。現役エンジニアが作成した豊富なカリキュラムに沿って学習を進めましょう。

© 2023 - mosya. All rights reserved.