やりたいこと
[
['1', '2'],
['a', 'b'],
['x', 'y']
]
のような配列から
[
['1', 'a', 'x'],
['1', 'a', 'y'],
['1', 'b', 'x'],
['1', 'b', 'y'],
['2', 'a', 'x'],
['2', 'a', 'y'],
['2', 'b', 'x'],
['2', 'b', 'y'],
]
といった総当たりの組み合わせ(デカルト積/直積)を求めたい
実装コード
※要ruby1.8.7以降
arr = [
['1', '2'],
['a', 'b'],
['x', 'y']
]
arr.inject(:product).map(&:flatten)
もしくは
arr.inject(&:product).map(&:flatten)
arr.reduce(:product).map(&:flatten)
arr.reduce(&:product).map(&:flatten)
のいずれか
以下解説
今回の要になるのは「inject」「product」「&:」の3要素です
enum.inject
繰り返しの計算を行うメソッドになります。
前回の返り値と、次の配列の要素のペアでブロック内を計算していきます。
イメージとしては下記のような感じです
((((要素1 + 要素2) + 要素3) + 要素4 ) + 要素5 ) + ...
実際に使用する際は下記のように記載します。
arr = [1, 2, 3, 4]
p arr.inject{|sum, n| sum + n } #=> 10
p arr.inject{|mul, n| mul * n } #=> 24
inject(reduce)のすごいところはsymbolでメソッド名を渡すことで、そのメソッドをブロック内にて演算してくれます。
その為上記はこのようにも書けます
arr = [1, 2, 3, 4]
p arr.inject(:+) #=> 10
p arr.inject(:*) #=> 24
ものすごくスマートになりましたね
array.product
今回の最重要メソッドです。これがいないと何も始まらない
元となる配列と引数の配列の要素の組み合わせの多重配列を作成して返してくれます。
総組み合わせを作りたい配列達が今回のように一つの配列に入ってなければでなければ以下のようにして求められます
arr1 = ['1', '2']
arr2 = ['a', 'b']
arr3 = ['x', 'y']
arr1.product(arr2, arr3) #=> [['1', 'a', 'x'],['1', 'a', 'y'], ...]
しかし今回は連想配列の為、上記方法がそのまま使用できません。
injectをでproductを呼んでやってみると下記のようになってしまいます
arr = [
['1', '2'],
['a', 'b'],
['x', 'y']
]
arr.inject(:product) #=> [[['1', 'a'], 'x'],[['1', 'a'], 'y'], ...]
ただ、一つの配列の中にまとまってくれているのでflattenしてあげれば綺麗になってくれそうです。
mapでflattenしてあげましょう
arr = [
['1', '2'],
['a', 'b'],
['x', 'y']
]
p arr.inject(:product).map{|e| e.flatten} #=> [['1', 'a', 'x'],['1', 'a', 'y'], ...]
これでひとますは完成です。
ちなみにproductだけでやろうとすると下記のような感じでできます。
arr = [
['1', '2'],
['a', 'b'],
['x', 'y']
]
first_arr = arr.shift()
first_arr.product(*arr)
下記のように記載すると簡単に書けるのではないかと思うかもしれませんが、これはNGです。
productは空配列があると返り値も空配列になってしまう為、全てが無に帰してしまいます。
p [].product(*arr) #=> []
&:
知ってて損はない便利記法
簡単に言うと
{|e| e.hoge }
をこういうふうに書けますよっていう記法です。
(&:hoge)
そのためにmap{|e| e.flatten}
は下記のように書き直せます。
map(&:flatten)
詳しく知りたい人は下記の記事などを読むといいかもしれない
余談
Array#productを知らないときにノリと勢いだけで書いたクソコードの供養
arr = [[1,2],['a','b','c'],['x','y']]
x = arr.map{|a| a.length}.inject(:*)
y = x
ans = arr.map do |a|
x /= a.length
tmp = a.map{|b| Array.new(x) { b }}.flatten
Array.new(y/tmp.length){ tmp }.flatten
end
p ans.transpose
完成させた後にproductの存在を知って悔しかったのでこの記事を書いた