必要に応じて前後の要素にもアクセスできる each


こういうのはまだ出ていないみたいなので…。与えたブロック引数の個数をみて適当に割り振ります。

class Array
  def ya_each(&block)
    n_args = block.arity.abs
    mid = n_args / 2
    ds = (0...n_args).map{ |i| i - mid }
    each_index do |i|
      yield(*ds.map{ |d| self[i + d] })
    end
  end
end
(1..10).to_a.ya_each{ |e| p e }
=> 1
   2
   3
   4
   5
   6
   7
   8
   9
   10
(1..10).to_a.ya_each{ |pv,ea| p [pv,ea] }
=> [10, 1]
   [1, 2]
   [2, 3]
   [3, 4]
   [4, 5]
   [5, 6]
   [6, 7]
   [7, 8]
   [8, 9]
   [9, 10]
(1..10).to_a.ya_each{ |pv,ea,su| p [pv,ea,su] }
=> [10, 1, 2]
   [1, 2, 3]
   [2, 3, 4]
   [3, 4, 5]
   [4, 5, 6]
   [5, 6, 7]
   [6, 7, 8]
   [7, 8, 9]
   [8, 9, 10]
   [9, 10, nil]
(1..10).to_a.ya_each{ |pv2,pv1,ea,su1,su2| p [pv2,pv1,ea,su1,su2] }
=> [9, 10, 1, 2, 3]
   [10, 1, 2, 3, 4]
   [1, 2, 3, 4, 5]
   [2, 3, 4, 5, 6]
   [3, 4, 5, 6, 7]
   [4, 5, 6, 7, 8]
   [5, 6, 7, 8, 9]
   [6, 7, 8, 9, 10]
   [7, 8, 9, 10, nil]
   [8, 9, 10, nil, nil]

ちなみに SmalltalkRuby の #each に当たるのは #do:)だとこんな感じ。

SequenceableCollection>>yetAnotherDo: doBlock
    | numArgs middleIndex deltas |
    numArgs := doBlock numArgs.
    middleIndex := numArgs // 2 + 1.
    deltas := (1 to: numArgs) collect: [:idx | idx - middleIndex].
    1 to: self size do: [:idx |
        doBlock valueWithArguments: (deltas collect: [:dlt | self atWrap: idx + dlt])]
World findATranscript: nil.
Transcript cr.
(1 to: 9) do: [:each | Transcript space; show: each].
"=> 1 2 3 4 5 6 7 8 9 "

Transcript cr.
(1 to: 9) yetAnotherDo: [:each | Transcript space; show: each].
"=> 1 2 3 4 5 6 7 8 9 "

Transcript cr.
(1 to: 9) yetAnotherDo: [:prev :each | Transcript space; show: {prev. each}].
"=> #(9 1) #(1 2) #(2 3) #(3 4) #(4 5) #(5 6) #(6 7) #(7 8) #(8 9) "
Transcript cr.

(1 to: 9) yetAnotherDo: [:prev :each :next | Transcript space; show: {prev. each. next}]
"=> #(9 1 2) #(1 2 3) #(2 3 4) #(3 4 5) #(4 5 6) #(5 6 7) #(6 7 8) #(7 8 9) #(8 9 1) "


さらに Smalltalk では擬変数「thisContext」を使ってブロックが評価される際のコンテキストをたぐることで、実用性はともかくいろいろと遊べます。

(1 to: 10) do: [:each |
    | rcvr idx |
    rcvr := thisContext sender receiver.
    idx := thisContext sender tempAt: 2.
    Transcript cr; show: {rcvr atWrap: idx - 1. each. rcvr atWrap: idx + 1}]
=> #(10 1 2)
   #(1 2 3)
   #(2 3 4)
   #(3 4 5)
   #(4 5 6)
   #(5 6 7)
   #(6 7 8)
   #(7 8 9)
   #(8 9 10)
   #(9 10 1)

(ブロック引数が可変長のときに arity が負数を返すのに対応するための abs の追加と、Smalltalk での #yetAnotherDo: の例を追記しました。del.icio.us でのご指摘を受けて、Array に変えました。)