3
1

More than 1 year has passed since last update.

TypeScriptで配列の直積を計算する関数を、型込みで作成する(ついでに内積も)

Posted at

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化しました。

配列の内積

大体同じなので、内積も作りました: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;
};
3
1
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
3
1