0
0

無限に時間がかかるんじゃないかって思うぐらい苦戦した

多次元配列の定義

export type MultiDimensional<T> = T | MultiDimensional<T>[];

厳密には配列ではないですが(T自体を含むため)、これを定義とします。

何がしたいか

例えば、以下のような型があるとします。

type STRINGS = [string,string,[[string],string],[string,string],string];

これを以下に変換したいのです。

type NUMBERS = [number,number,[[number],number],[number,number],number];

やりたいことは伝わったと思います。

多次元配列の定義そのままではだダメなのか

ダメです。せっかくTypeScriptやってるのに、多次元配列の構造を定義しないのであれば私には生きている価値がありません。

結論

以下でできます。

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
> = M extends unknown[]
  ? N["length"] extends M["length"]
    ? []
    : M[N["length"]] extends unknown[]
      ? [SameStructure<T, M[N["length"]]>, ...SameStructure<T, M, [...N, 0]>]
      : [T, ...SameStructure<T, M, [...N, 0]>]
  : T;

解説

行ごとに解説していきます。

引数

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
>
  • T: 変換先の型
  • M: 変換元の構造(型は問わない)
  • N: カウンタ用配列

カウンタ用配列とは

TypeScriptの型では、数値の演算が基本的にできません。しかし、配列の長さで数値を表すと、インクリメントが簡単に表現できます。

~1行目

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
> = M extends unknown[]
  ? ...
  : T;

まず、Mが配列であるかどうかを見ます。配列であれば、次の処理に進み、そうでないなら、型Tをそのまま返します。

~2行目

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
> = M extends unknown[]
  ? N["length"] extends M["length"]
    ? []
    : ...
  : T;

次に、再帰が終わったかどうかを判定します。
ここで重要なのが、N["length"] extends M["length"]であることです。これは、暗黙的にMlengthが数値リテラル型であることを強制します。
逆に、M["length"] extends N["length"]だと、M["length"]が数値リテラル型でない、つまりnumber型のときに、無限ループに陥ってしまいます。
再帰が終了したら、空配列を返すようにしています。

~3行目

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
> = M extends unknown[]
  ? N["length"] extends M["length"]
    ? []
    : M[N["length"]] extends unknown[]
      ? ...
      : ...
  : T;

3行目では、今見ているMの要素が配列であるかどうかを判別しています。それだけです。

~4行目

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
> = M extends unknown[]
  ? N["length"] extends M["length"]
    ? []
    : M[N["length"]] extends unknown[]
      ? [SameStructure<T, M[N["length"]]>, ...SameStructure<T, M, [...N, 0]>]
      : ...
  : T;

4行目では、今見ているMの要素が配列であったときに、再帰的にこの型を適用しています。
1つ目の再帰で、スタックを遡り、2つ目の再帰で、次の要素から見ていくようになっています。

~最後

type SameStructure<
  T,
  M extends MultiDimensional<unknown>,
  N extends 0[] = [],
> = M extends unknown[]
  ? N["length"] extends M["length"]
    ? []
    : M[N["length"]] extends unknown[]
      ? [SameStructure<T, M[N["length"]]>, ...SameStructure<T, M, [...N, 0]>]
      : [T, ...SameStructure<T, M, [...N, 0]>]
  : T;

最後に、今見ているMの要素が配列でないときに、Tとその次以降の要素を列挙して終わりです。

簡単でしょ?

1行毎に見ていけば全然難しくないです。が、これを組むのは意外と大変です。
試しにChatGPTで生成させてみようとしましたが、固定次元の多次元配列についてしか考えてくれませんでした。
論理的かつ再帰的な思考が必要なタスクはまだ難しいようです。

以上!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0