組み合わせ
Rubyで組み合わせを求める機会があった。
Rubyには便利なメソッドがあって、以下のように求めることができる。
arr = [1, 2, 3, 4, 5]
p arr.combination(2).to_a
# OUTPUT
[[1, 2], [1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 4], [3, 5], [4, 5]]
しかし、これを実際に自分で実装しようとすると意外と難しいなと感じたのでまとめてみる。
実装
考え方
直感的な考え方は、自分で樹形図を書くプロセスである。
例えば、[1,2,3,4]
から3つの要素を取る組み合わせを考える。
まず、1
を取る。残った[2,3,4]
から2つを取ってきて1
に加える。
[1,2,3][1,2,4][1,3,4]
ができる。
次に、2
を取る、残った[3,4]
から2つ取ってきて2
に加える。
[2,3,4]
ができる。
合わせて、4つの組み合わせができる。
これは再帰的に実装をすることができる。
再帰終了条件
[1,2,3]
から3つ取ってくるようなときは[[1,2,3]]
を返す。
[1,2,3]
から1つ取ってくるようなときは[[1],[2],[3]]
を返す。
Rubyでは、インスタンスメソッド中でレシーバを省略するとself
が暗黙的にレシーバになる。
### コード
class Array
# EXAMPLE: [1.2.3.4].combination_array(3) => [[1,2,3], [1,2,4], [1,3,4], [2,3,4]]
def combination_array(comb_size)
return [] if comb_size > size # 4C5のようなものは空を返す
return [] if comb_size == 0 # 4C0のようなものは空を返す
return [self] if comb_size == size
return map { |e| [e] } if comb_size == 1
ret = []
(0..size - comb_size).each do |i|
picked = self[i]
rest = self[i + 1..-1]
rest.combination_array(comb_size - 1).each do |c|
ret << [picked] + c
end
end
ret
end
end
if $PROGRAM_NAME == __FILE__
p %i[asahi kirin sapporo suntory].combination_array(2)
# 重複がない時(既存メソッドとの比較)
p [1, 2, 3, 4, 5, 6, 7, 8, 9].combination_array(3) == [1, 2, 3, 4, 5, 6, 7, 8, 9].combination(3).to_a
# 重複がある時(既存メソッドとの比較)
p [1, 2, 3, 4, 5, 6, 1, 8, 3].combination_array(4) == [1, 2, 3, 4, 5, 6, 1, 8, 3].combination(4).to_a
end
## OUTPUT
[[:asahi, :kirin], [:asahi, :sapporo], [:asahi, :suntory], [:kirin, :sapporo], [:kirin, :suntory], [:sapporo, :suntory]]
true
true
参考