任意の階層だけ繰り返しをネストする多重 map 的メソッド(Ruby)
Array#nest_loop で、Integer#times の多重ネスト版です。配列にループ回数を入れて呼び出します。わかり切った多重ループを書くのが面倒なときに役立ちます。ブロックが与えられなければ Enumertor を返します。
[4, 2, 3].nest_loop do |i, j, k| print "#{[i, j, k]} " end #=> [0, 0, 0] [0, 0, 1] [0, 0, 2] [0, 1, 0] [0, 1, 1] [0, 1, 2] [1, 0, 0] [1, 0, 1] [1, 0, 2] [1, 1, 0] [1, 1, 1] [1, 1, 2] [2, 0, 0] [2, 0, 1] [2, 0, 2] [2, 1, 0] [2, 1, 1] [2, 1, 2] [3, 0, 0] [3, 0, 1] [3, 0, 2] [3, 1, 0] [3, 1, 1] [3, 1, 2]
for in 文を使って実装しているので、self の配列の中には範囲演算子や配列、ハッシュなどを使うことができます。また、ブロックのそれぞれの返り値が配列に入って返ります。なので、多重ループ版の map のように使えます。
p [2..6, 2, ["a", "b"]].nest_loop(&:itself) #=>[[2, 0, "a"], [2, 0, "b"], [2, 1, "a"], [2, 1, "b"], [3, 0, "a"], [3, 0, "b"], # [3, 1, "a"], [3, 1, "b"], [4, 0, "a"], [4, 0, "b"], [4, 1, "a"], [4, 1, "b"], # [5, 0, "a"], [5, 0, "b"], [5, 1, "a"], [5, 1, "b"], [6, 0, "a"], [6, 0, "b"], # [6, 1, "a"], [6, 1, "b"]] a = [:a, :b, :c] p [a, a].nest_loop {|i, j| (j == :b) ? [i, "b"] : [i, j]} #=>[[:a, :a], [:a, "b"], [:a, :c], [:b, :a], [:b, "b"], # [:b, :c], [:c, :a], [:c, "b"], [:c, :c]]
本体のコードです。(※注:2018/2/7 に修正しました。)
nest_loop.rb
class Array def nest_loop check = lambda do |obj| return 0...obj if obj.class.ancestors.include?(Integer) obj end e = Enumerator.new do |y| ns = lambda do |ax, args| a, ax = ax.first, ax.drop(1) for i in check.call(a) nxt = args + [i] if ax.empty? y << nxt else ns.call(ax, nxt) end end end ns.call(self, []) end block_given? ? e.map {|i| yield(i)} : e end end
Gem 化
自家製の utility Gem 'kaki-utils' に同梱しました。
kaki-utils | RubyGems.org | your community gem host
インストールは $ gem install kaki-utils で。使い方は
require 'kaki/utils/nest_loop' a = [1, 2, 3] p [a, a].nest_loop {|i, j| i * j} #=>[1, 2, 3, 2, 4, 6, 3, 6, 9]
という感じ。(2018/2/6)