TypeScriptで配列の直積を計算する関数を、型込みで作成する(ついでに内積も)
配列の直積が欲しかったのですが、めぼしいものがなかったので自作しました。
型定義を作りこんでいるので、以下のようにタプル型の型推論が利きます。
// list => [boolean, string, number][].
const list = outerProd([true, false], ['a', 'b', 'c'], [1, 2, 3, 4]);
for (let [a, b, c] of list) {
a; // boolean
b; // string
c; // number
}
配列の直積
次の自分のコードをそのまま再掲します。https://github.com/charon1212/util-charon1212/blob/develop-v1.0/src/main/array.ts
type OuterProd<T extends (readonly unknown[])[]> = number extends T['length'] ? T : OuterProdTuple<T>[];
type OuterProdTuple<T extends (readonly unknown[])[]> = T extends [infer P extends (readonly unknown[]), ...infer Q extends (readonly unknown[])[]] ? [P[number], ...OuterProdTuple<Q>] : [];
/**
* Create array's outer product.
* e.g. outerProd(['a','b'], [1,2]) returns [['a',1],['a',2],['b',1],['b',2]].
* @param arr Source array of outer product.
* @returns Outer product result.
*/
export const outerProd = <T extends (readonly unknown[])[]>(...arr: T): OuterProd<T> => {
if (arr.length === 0) return [] as any;
if (arr.length === 1) return arr[0].map((a) => [a]) as any;
const first = arr.shift()!;
const rest = outerProd(...arr);
const result = [] as any;
first.forEach((f) => rest.forEach((r) => {
result.push([f, ...r]);
}));
return result;
};
Q&A
- あれ?タイトルで型込みという割にanyというのが見えますが、これは…?
- 外部仕様はちゃんとしてるので、許してください。。。
(こういう処理を書いていると、何でもかんでもanyは許さないという姿勢より、anyを外に持ち出さない姿勢のほうが効率的じゃないかな~と思いました。)
- 外部仕様はちゃんとしてるので、許してください。。。
-
OuterProdTuple<T>
って何してるの?- 基本はタプル型を扱うときのパターン
OuterProdTuple<T> = T extends [infer P, ...inferQ] ? HOGE<P,Q> : FUGA
を使ってます。
Tは、[A[],B[],C[],...,N[]]
のようなタプル型を想定しています。
上記のパターンで1個ずつ取り出すと、infer P=A[]
のようになります。
あとは、P[number]
が配列の中身の型を表すので、[P[number],...OuterProdTuple<Q>]
で再帰的にタプル型を形成しています。
結果として、[A,B,C,...,N]
のように配列を取り除いた型が得られます。
- 基本はタプル型を扱うときのパターン
-
OuterProd<T>
にあるnumber extends T['length']
って何?- タプル型と配列型を区別しています。
true branchだと、T
は配列の配列になります。(number[][]
等)
false branchだと、T
は配列のタプルになります。([string[], number[]]
等)
配列の配列の場合は直積取った結果も配列の配列と解釈するのが良いと考えて、こうしてます。
これいれないと、OuterProd<string[][]>
が[][]
に推論されてしまうので。。。途中で気付いてテコ入れしました。
- タプル型と配列型を区別しています。
-
extends (readonly unknown[])[]
の制約は何?- 型Tは配列の配列を受け取る想定なので、最初は
extends unknown[][]
で作り始めました。
その後、outerProd([1, 2], ['a', 'b'] as const)
みたいにしたときにエラーになってしまうので、readonly化しました。
- 型Tは配列の配列を受け取る想定なので、最初は
配列の内積
大体同じなので、内積も作りました:https://github.com/charon1212/util-charon1212/blob/develop-v1.0/src/main/array.ts
type InnerProd<T extends (readonly unknown[])[]> = number extends T['length'] ? T : InnerProdTuple<T>[];
type InnerProdTuple<T extends (readonly unknown[])[]> = T extends [infer P extends (readonly unknown[]), ...infer Q extends (readonly unknown[])[]] ? [P[number], ...InnerProdTuple<Q>] : [];
/**
* Create array's inner product.
* e.g. innerProd(['a','b'], [1,2], [true,false]) returns [['a',1,true],['b',2,false]].
*
* This method DO NOT checks that all array's length are same each other. Return array's length is maximum size of input array.
* @param arr Source array of inner product.
* @returns Inner product result.
*/
export const innerProd = <T extends (readonly unknown[])[]>(...arr: T): InnerProd<T> => {
if (arr.length === 0) return [] as any;
const n = Math.max(...arr.map((a) => a.length));
const result = [] as any;
for (let i = 0; i < n; i++) result.push(arr.map((a) => a[i]));
return result;
};