RubyのArray#productでスロット
RubyのArray#product
を知らなかったのでメモ。
Slackのカスタムレスポンスを使ってスロットみたいなことができる。
そのためにはカスタムレスポンスに出目をバーっと設定する必要がある。
例えばスロットの図柄が:neko:
, :kuma:
, :inu:
(カスタム絵文字)の3種類なら、
:neko: :neko: :neko:
:neko: :neko: :kuma:
:neko: :neko: :inu:
:neko: :kuma: :neko:
:neko: :kuma: :kuma:
:neko: :kuma: :inu:
:neko: :inu: :neko:
:neko: :inu: :kuma:
:neko: :inu: :inu:
:kuma: :neko: :neko:
:kuma: :neko: :kuma:
:kuma: :neko: :inu:
:kuma: :kuma: :neko:
:kuma: :kuma: :kuma:
:kuma: :kuma: :inu:
:kuma: :inu: :neko:
:kuma: :inu: :kuma:
:kuma: :inu: :inu:
:inu: :neko: :neko:
:inu: :neko: :kuma:
:inu: :neko: :inu:
:inu: :kuma: :neko:
:inu: :kuma: :kuma:
:inu: :kuma: :inu:
:inu: :inu: :neko:
:inu: :inu: :kuma:
:inu: :inu: :inu:
こんな感じで設定する。
図柄が3つ揃った時は何らかの当たった演出を加えたりするといい感じになる。
図柄を変えたり増えたり減らしたり自由に設定したいので、そのためにはこれを簡単に吐き出せる必要がある。
スロットには3つのリールがあるので、図柄が$$N$$種類あるとき$$N ^ 3$$個の組み合わせを全列挙したい。
単純に考えればえいやっと3重ループで書ける。
members = ["neko", "kuma", "inu"]
comb = []
members.each do |left|
members.each do |center|
members.each do |right|
comb << ":#{left}: :#{center}: :#{right}:"
end
end
end
puts comb
うーんいい感じ、以上です。
〜Fin〜
〜再開〜
もうちょい違う書き方できないかな〜ってことでArray#product
て便利なもののがあるのを知った。
こう書ける。
members = ["neko", "kuma", "inu"]
comb = members.product(members, members).map do |left, center, right|
":#{left}: :#{center}: :#{right}:"
end
puts comb
3重ループがなくなった。便利。ただ元の3重ループと比べてわかりやすいかは謎。
今回こんな感じで列挙したものはデカルト積
とか直積集合
とか呼ぶらしい。
なるほどproduct
。
自分でも単純な実装を書いてみた。
def product(*arrays)
ret = []
arrays.each do |arr|
memo = []
if ret.empty?
ret = arr
next
end
ret.each do |r|
arr.each do |e|
memo << [*r, e]
end
end
ret = memo
end
ret
end
なんかif ret.empty?
で初回ループの特別処理してるところがあんまセンスない感じだけど一応同じようにデカルト積を求められる。
色々みて回ってこういう実装を見つけた:aaw/cartesian-product
これふつうに感動的で、
特にこの部分:https://github.com/aaw/cartesian-product/blob/master/lib/cartesian-product.rb#L21-L27
def index_to_item(index)
@arrays.map do |array|
element = array[index % array.length]
index /= array.length
element
end.reverse
end
このインデックスの操作がテクい感じがする。
でもよくわからないから、
arr1 = [1, 2, 3]
arr2 = ['a', 'b', 'c']
prod = CartesianProduct.new(arr1, arr2)
prod.each { |element| p element }
こういう感じで要素数3の配列を2つ与えて動かしたときのarr1
とarr2
のインデックスの遷移を追ってみると、
arr1: [0, 0, 0, 1, 1, 1, 2, 2, 2]
arr2: [0, 1, 2, 0, 1, 2, 0, 1, 2]
となって、3つ与えたときは、
arr1: [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2]
arr2: [0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2]
arr3: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2]
こうなる。
余りをとったインデックスで周期的にアクセスしつつ、
index /= array.length
ここで最後の要素に来た時だけ次の配列のインデックスに1でアクセスするてことか〜。あたまいー。
配列(集合)の操作って業務じゃあんまり複雑なことやらないし知らないこと多いから都度書いていこうと思う。