概要
- 複数の配列の組み合わせを作成。
- ex. 要素数3の配列2つを組み合わせる場合、3×3=9通りの組み合わせが入った配列が作られる。
- 渡す配列の数は任意。
- 配列の要素数も任意。
基本
- forEach(またはfor文)の入れ子で作成する。
- 組み合わせに利用する配列の数で入れ子の数が変わってしまうため、コードの汎用性がない。
- 配列をいくつ渡しても入れ子が増えない形で作りたい。
const arr1 = [1, 2, 3],
arr2 = ['a', 'b', 'c'],
arr3 = ['s', 't', 'u'];
const result = [];
arr1.forEach(v1 => {
arr2.forEach(v2 => {
arr3.forEach(v3 => {
result.push([v1, v2, v3]);
});
});
});
console.log(result);
/* [
[1,"a","s"],[1,"a","t"],[1,"a","u"],[1,"b","s"],[1,"b","t"],[1,"b","u"],[1,"c","s"],[1,"c","t"],[1,"c","u"],
[2,"a","s"],[2,"a","t"],[2,"a","u"],[2,"b","s"],[2,"b","t"],[2,"b","u"],[2,"c","s"],[2,"c","t"],[2,"c","u"],
[3,"a","s"],[3,"a","t"],[3,"a","u"],[3,"b","s"],[3,"b","t"],[3,"b","u"],[3,"c","s"],[3,"c","t"],[3,"c","u"]
] */
このままだと
組み合わせたい配列が4つの場合。
const result = [];
arr1.forEach(v1 => {
arr2.forEach(v2 => {
arr3.forEach(v3 => {
arr4.forEach(v4 => {
result.push([v1, v2, v3, v4]);
});
});
});
});
組み合わせたい配列が5つの場合。
const result = [];
arr1.forEach(v1 => {
arr2.forEach(v2 => {
arr3.forEach(v3 => {
arr4.forEach(v4 => {
arr5.forEach(v5 => {
result.push([v1, v2, v3, v4, v5]);
});
});
});
});
});
終わらない地獄が待っている。
しかも渡す配列が少ない場合に適切に対応できない。
考え方
- 2つの配列の組み合わせを作る関数を1つ作成する。
- この関数を上手く使いまわせれば、繰り返し処理で出来るのではないか?
const make = (arr1, arr2) => {
const result = [];
arr1.forEach((v1) => {
arr2.forEach((v2) => {
result.push([v1, v2]);
});
});
return result;
};
サンプルコード
// 引数を可変にする
const makeComb = function(...array) {
// 2つの配列の組み合わせを作る関数
const make = (arr1, arr2) => {
// 組み合わせ作成時の例外処理
if (arr1.length === 0) {
return arr2;
}
// 組み合わせの作成
return arr1.reduce((arr, v1) => {
arr2.forEach(v2 => {
// concatで結合
const group = [].concat(v1, v2);
arr.push(group);
});
return arr;
}, []);
};
// 繰り返し処理
return array.reduce(make, []);
};
const arr1 = ['僕は','俺は', '私は'],
arr2 = ['今日、', 'いつか、', '明日、'],
arr3 = ['遊ぶ', '読書する', 'スタバでMacする'],
arr4 = ['。', 'かもしれない。', 'ぞ!'];
const arr = makeComb(arr1, arr2, arr3, arr4);
arr.forEach(s => {
console.log(s.join(''));
});
/*
僕は今日、遊ぶ。
僕は今日、遊ぶかもしれない。
...
私は明日、スタバでMacするぞ!
*/
解説
引数を可変にする
- Rest parametersを利用して、引数を1つの配列としてまとめる。
- このまとめた配列を(配列処理用のメソッドを利用して)処理の大元にすることで、引数がいくつあってもすべての値を利用できるようになる。
// 引数に[1, 2]と[3, 4]を渡した場合
const makeComb = (...array) => {
console.log(array); // [[1, 2], [3, 4]]
// まとめた配列を元に処理
array.forEach(...);
}
引数部分に「...」を付けない場合、意味が変わるので注意。
const test = (array) => {
// 第一引数を展開したものを空の配列に配置
const arrays = [...array]; // [1, 2]
};
test([1,2], [3, 4]);
組み合わせの作成
- 外側をforEach→reduceにした。
- forEachのままでも良かったが、第二引数に初期値
[]
を設定でき、結果をそのままreturnで返せる。
arr1.reduce((arr, v1) => {
arr2.forEach(v2 => {
const group = [].concat(v1, v2);
arr.push(group);
});
return arr;
}, []);
値の格納方法を[v1, v2]
とするのではなく、空の配列に値をconcat
で結合する形にした。
const group = [].concat(v1, v2);
arr.push(group);
これにより、配列と値を同階層に結合できる。もちろん、配列同士や値同士でも問題ない。
具体例
v1 = ['a', 'b'];
v2 = 3;
[v1, v2] // [['a', 'b'], 3]
[].concat(v1, v2) // ['a', 'b', '3']
組み合わせ作成時の例外処理
1つ目の配列の要素数が0の場合、2つ目の配列をそのまま返す。
こうすることで繰り返し処理に分岐を入れることがなくなり、簡単になった。
make = (arr1, arr2) => {
if (arr1.length === 0) {
return arr2;
}
...
繰り返し実行のイメージ
let result = [];
// 1回目 arr1を結合
result = make(result, arr1);
// 2回目 arr2を結合して組み合わせを作成
result = make(result, arr2);
// 3回目 先程の結果にarr3を結合して組み合わせを作成
result = make(result, arr3);
繰り返し処理
- 基本の考え方だとネストで頑張っていた部分。
- 配列がいくつあっても、単純な繰り返し処理に置き換わった。
単純な繰り返し処理
let result = [];
array.forEach(arr => {
result = make(result, arr);
});
さらにreduce
を使ってまとめると下記の形になった。
reduceを使用
return array.reduce((result, arr) => make(result, arr), []);
// さらに短縮化。渡ってきた値をそのまま使うため仮引数を使う必要がなく、reduceの第一引数に使いたい関数、第二引数に初期値を設定すればよい。
return array.reduce(make, []);
まとめ
- ネストが1つのコードを関数化して使いまわそう。
- 汎用性のないコールバック地獄は避けよう。
- 単純な繰り返し処理になるようにコードをまとめる。