LoginSignup
7
1

More than 5 years have passed since last update.

rubyで多重配列の総当たり(直積/デカルト積)をワンライナーで作成する

Last updated at Posted at 2018-04-26

やりたいこと

[
  ['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の存在を知って悔しかったのでこの記事を書いた

7
1
1

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
7
1