前回の記事の続きです。
https://qiita.com/theanine/items/9462b79b83c1aba412bd
前回の記事で多次元の配列を簡単に作るmacroを作りました。
多次元配列を作ると、各次元ごとにループをかけていくので、深いネストのループになりがちです。
前回取り上げたAtCoderの問題では10重ループとなりました。
(0..n1).each do |i1|
  (0..n2).each do |i2|
    (0..n3).each do |i3|
      (0..n4).each do |i4|
        (0..n5).each do |i5|
          (0..n6).each do |i6|
            (0..n7).each do |i7|
              (0..n8).each do |i8|
                (0..n9).each do |i9|
                  (0..n10).each do |i10|
                    何かの処理
                  end
                end
              end
            end
          end
        end
      end
    end
  end
end
どうせ、コピペで書いていくので、書くのはそこまで大変ではないですが、変更とかあるとしんどいですね。
そこで、前回に引き続きmacroで楽をしましょう。
macroのブロック
Crystalのmacroは高機能で、ブロックを受け取ることもできます。
args, bodyでブロックの引数や中身などにも分けて取得できて色々できそうですね。これを使って、多重ループマクロを作ってみます
多重ループマクロ
ということで以下のようなものを作ってみました。
macro rep(*a, &block)
  {% for i in 0...a.size %}
    ({{a[i]}}).each do |{{block.args[i]}}|
  {% end %} 
  {{ block.body }}
  {% for i in 0...a.size %}
    end
  {% end %}
end
使い方は以下のようにします。eachメソッドを持つオブジェクトを引数で渡せば良いです。範囲や配列、タプルなどを渡すことができます。
rep(1..2, {3,4,5}, 0..1) do |i, j, k|
  puts "#{i} #{j} #{k}"
end
これを実行すると以下のようにできます。
1 3 0
1 3 1
1 4 0
1 4 1
1 5 0
1 5 1
2 3 0
2 3 1
2 4 0
2 4 1
2 5 0
2 5 1
これで深いループもブロックのネストを深くせずにかけます。
冒頭で取り上げた10重ループは
rep(0..n1, 0..n2, 0..n3, 0..n4, 0..n5, 0..n6, 0..n7, 0..n8, 0..n9, 0..n10) do |i1, i2, i3, i4, i5, i6, i7, i8, i9, i10|
  何らかの処理
end
となります。
