例題
配列からある要素がひとつ前の要素と別の値になる箇所のインデックス、その一覧を取り出したいとします。
names = ['ゆの', 'ゆの', 'ゆの', '宮子', '宮子', '沙英', 'ヒロ', 'ヒロ', 'ヒロ']
例えば、上記の配列では [3, 5, 6]
です。
方法 1
実直にやるとこのような実装になると思います。
names
.each_with_object([])
.with_index do |(name, indexes), i|
indexes << i + 1 if names[i + 1] && name != names[i + 1]
end
#=> [3, 5, 6]
names[i + 1]
で次の要素を取得し、それが存在し、なおかつ現在の値と異なるかどうかを調べています。ループ内で、元の配列 names
を利用しているのが個人的に好きではないです。
方法 2
今回オススメするのが以下の方法です。
names
.each_cons(2)
.each_with_object([])
.with_index do |((current_val, next_val), indexes), i|
indexes << i + 1 if current_val != next_val
end
#=> [3, 5, 6]
ループ内で直接次の要素にアクセスできるので便利です。
この方法では Enumerable#each_cons を使って、配列をあらかじめ重複ありの 2 要素ずつに区切り、それからさらにループを回すというアプローチを取っています。
names.each_cons(2).to_a
#=> [["ゆの", "ゆの"], ["ゆの", "ゆの"], ["ゆの", "宮子"], ["宮子", "宮子"], ["宮子", "沙英"], ["沙英", "ヒロ"], ["ヒロ", "ヒロ"], ["ヒロ", "ヒロ"]]
なお、Enumerable#each_slice と挙動が異なることに注意してください。
names.each_slice(2).to_a
#=> [["ゆの", "ゆの"], ["ゆの", "宮子"], ["宮子", "沙英"], ["ヒロ", "ヒロ"], ["ヒロ"]]