魔术在Ruby / Rails循环中的第一个和最后一个指标?

Ruby / Rails在糖的基本function上做了很多很酷的事情,我想有一个很常见的情况,我想知道是否有人做过帮手或类似的事情。

a = Array.new(5, 1) a.each_with_index do |x, i| if i == 0 print x+1 elsif i == (a.length - 1) print x*10 else print x end end 

饶恕丑陋,但这得到了什么人可能想要…有一个ruby的方式来做一些循环的第一个和最后一个?

[编辑]我想理想情况下,这将是数组与参数(数组实例,所有元素函数,第一个元素函数,最后一个元素函数)的扩展…但我打开其他想法。

如果你喜欢,你可以抓住第一个和最后一个元素,并以不同的方式处理它们。

 first = array.shift last = array.pop process_first_one array.each { |x| process_middle_bits } process_last_one 

如果第一次和最后一次迭代的代码与其他迭代的代码没有任何共同之处,那么也可以这样做:

 do_something( a.first ) a[1..-2].each do |x| do_something_else( x ) end do_something_else_else( a.last ) 

如果不同的案例有一些共同的代码,你的方式是好的。

如果你能做到这一点呢?

 %w(abcd).each.with_position do |e, position| p [e, position] # => ["a", :first] # => ["b", :middle] # => ["c", :middle] # => ["d", :last] end 

或这个?

 %w(a, b, c, d).each_with_index.with_position do |(e, index), position| p [e, index, position] # => ["a,", 0, :first] # => ["b,", 1, :middle] # => ["c,", 2, :middle] # => ["d", 3, :last] end 

在MRI> = 1.8.7中,只需要这个猴子补丁:

 class Enumerable::Enumerator def with_position(&block) state = :init e = nil begin e_last = e e = self.next case state when :init state = :first when :first block.call(e_last, :first) state = :middle when :middle block.call(e_last, :middle) end rescue StopIteration case state when :first block.call(e_last, :first) when :middle block.call(e_last, :last) end return end while true end end 

它有一个小状态引擎,因为它必须向前看一个迭代。

诀窍是each,each_with_index,&c。 返回一个枚举器,如果没有给定块。 枚举器完成Enumerable做的所有事情,并做更多的事情。 但是对于我们来说,重要的是我们可以对Enumerator进行修改,添加一个迭代的方法,“包装”现有的迭代,不pipe它是什么。

或者一个小小的域特定语言:

 a = [1, 2, 3, 4] FirstMiddleLast.iterate(a) do first do |e| p [e, 'first'] end middle do |e| p [e, 'middle'] end last do |e| p [e, 'last'] end end # => [1, "first"] # => [2, "middle"] # => [3, "middle"] # => [4, "last"] 

和它的代码:

 class FirstMiddleLast def self.iterate(array, &block) fml = FirstMiddleLast.new(array) fml.instance_eval(&block) fml.iterate end attr_reader :first, :middle, :last def initialize(array) @array = array end def first(&block) @first = block end def middle(&block) @middle = block end def last(&block) @last = block end def iterate @first.call(@array.first) unless @array.empty? if @array.size > 1 @array[1..-2].each do |e| @middle.call(e) end @last.call(@array.last) end end end 

我开始思考,“如果只有你可以传递多个块到一个Ruby函数,那么你可以有一个光滑而简单的解决scheme来解决这个问题。” 然后我意识到,DSL的玩法几乎就像传递了多个块一样。

正如许多人所指出的, each_with_index似乎是这个关键。 我有这个我喜欢的代码块。

 array.each_with_index do |item,index| if index == 0 # first item elsif index == array.length-1 # last item else # middle items end # all items end 

要么

 array.each_with_index do |item,index| if index == 0 # first item end # all items if index == array.length-1 # last item end end 

或者通过数组扩展

 class Array def each_with_position array.each_with_index do |item,index| if index == 0 yield item, :first elsif index == array.length-1 yield item, :last else yield item, :middle end end end def each_with_index_and_position array.each_with_index do |item,index| if index == 0 yield item, index, :first elsif index == array.length-1 yield item, index, :last else yield item, index, :middle end end end def each_with_position_and_index array.each_with_index do |item,index| if index == 0 yield item, :first, index elsif index == array.length-1 yield item, :last, index else yield item, :middle, index end end end end 

如果你愿意添加一些样板,你可以添加类似这样的数组类:

 class Array def each_fl each_with_index do |x,i| yield [i==0 ? :first : (i==length-1 ? :last : :inner), x] end end end 

然后在任何你需要的地方,你可以得到如下语法:

 [1,2,3,4].each_fl do |t,x| case t when :first puts "first: #{x}" when :last puts "last: #{x}" else puts "otherwise: #{x}" end end 

对于以下输出:

 first: 1 otherwise: 2 otherwise: 3 last: 4 

没有“在Ruby中这样做(第一个|最后一个)时间”的语法。 但是,如果你正在寻求简洁,你可以这样做:

 a.each_with_index do |x, i| print (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1) end 

结果是你所期望的:

 irb(main):001:0> a = Array.new(5,1) => [1, 1, 1, 1, 1] irb(main):002:0> a.each_with_index do |x,i| irb(main):003:1* puts (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1) irb(main):004:1> end 2 1 1 1 10 

有趣的问题,我也有一个想法。

我想你必须创build三个不同的块/ proc /不pipe他们被称为,然后创build一个方法,调用正确的块/ proc /无论。 (抱歉,模糊 – 我还不是一个黑带metaprogrammer)[ 编辑 :但是,我已经从底部的人复制)

 class FancyArray def initialize(array) @boring_array = array @first_code = nil @main_code = nil @last_code = nil end def set_first_code(&code) @first_code = code end def set_main_code(&code) @main_code = code end def set_last_code(&code) @last_code = code end def run_fancy_loop @boring_array.each_with_index do |item, i| case i when 0 then @first_code.call(item) when @boring_array.size - 1 then @last_code.call(item) else @main_code.call(item) end end end end fancy_array = FancyArray.new(["Matti Nykanen", "Erik Johnsen", "Michael Edwards"]) fancy_array.set_first_code {|item| puts "#{item} came first in ski jumping at the 1988 Winter Olympics"} fancy_array.set_main_code {|item| puts "#{item} did not come first or last in ski jumping at the 1988 Winter Olympics"} fancy_array.set_last_code {|item| puts "#{item} came last in ski jumping at the 1988 Winter Olympics"} fancy_array.run_fancy_loop 

产生

 Matti Nykanen came first in ski jumping at the 1988 Winter Olympics Erik Johnsen did not come first or last in ski jumping at the 1988 Winter Olympics Michael Edwards came last in ski jumping at the 1988 Winter Olympics 

编辑 :斯万特的回答 (与摩尔夫的build议)的相关问题显示如何传递多个代码块到一个单一的方法:

 class FancierArray < Array def each_with_first_last(first_code, main_code, last_code) each_with_index do |item, i| case i when 0 then first_code.call(item) when size - 1 then last_code.call(item) else main_code.call(item) end end end end fancier_array = FancierArray.new(["Matti Nykanen", "Erik Johnsen", "Michael Edwards"]) fancier_array.each_with_first_last( lambda {|person| puts "#{person} came first in ski jumping at the 1988 Winter Olympics"}, lambda {|person| puts "#{person} did not come first or last in ski jumping at the 1988 Winter Olympics"}, lambda {|person| puts "#{person} came last in ski jumping at the 1988 Winter Olympics"}) 

我不时需要这个function,所以我为此制作了一个小class。

最新版本是: https : //gist.github.com/3823837

样品:

 ("a".."m").to_a.each_pos do |e| puts "Char\tfirst?\tlast?\tprev\tnext\twrapped?\tindex\tposition" if e.first? print "#{e.item}\t" print "#{e.first?}\t" print "#{e.last?}\t" print "#{e.prev}\t" print "#{e.next}\t" print "#{e.wrapped?}\t\t" print "#{e.index}\t" puts "#{e.position}\t" end # Char first? last? prev next wrapped? index position # a true false b false 0 1 # b false false ac true 1 2 # c false false bd true 2 3 # d false false ce true 3 4 # e false false df true 4 5 # f false false eg true 5 6 # g false false fh true 6 7 # h false false gi true 7 8 # i false false hj true 8 9 # j false false ik true 9 10 # k false false jl true 10 11 # l false false km true 11 12 # m false true l false 12 13 { a: "0", b: "1", c: "2", d: "3", e: "4", f: "5", g: "6", h: "7", i: "8", j: "9", k: "10", l: "11", m: "12", }.each_pos do |(k, v), e| puts "KV\tChar\t\tfirst?\tlast?\tprev\t\tnext\t\twrapped?\tindex\tposition" if e.first? print "#{k} => #{v}\t" print "#{e.item}\t" print "#{e.first?}\t" print "#{e.last?}\t" print "#{e.prev || "\t"}\t" print "#{e.next || "\t"}\t" print "#{e.wrapped?}\t\t" print "#{e.index}\t" puts "#{e.position}\t" end # KV Char first? last? prev next wrapped? index position # a => 0 [:a, "0"] true false [:b, "1"] false 0 1 # b => 1 [:b, "1"] false false [:a, "0"] [:c, "2"] true 1 2 # c => 2 [:c, "2"] false false [:b, "1"] [:d, "3"] true 2 3 # d => 3 [:d, "3"] false false [:c, "2"] [:e, "4"] true 3 4 # e => 4 [:e, "4"] false false [:d, "3"] [:f, "5"] true 4 5 # f => 5 [:f, "5"] false false [:e, "4"] [:g, "6"] true 5 6 # g => 6 [:g, "6"] false false [:f, "5"] [:h, "7"] true 6 7 # h => 7 [:h, "7"] false false [:g, "6"] [:i, "8"] true 7 8 # i => 8 [:i, "8"] false false [:h, "7"] [:j, "9"] true 8 9 # j => 9 [:j, "9"] false false [:i, "8"] [:k, "10"] true 9 10 # k => 10 [:k, "10"] false false [:j, "9"] [:l, "11"] true 10 11 # l => 11 [:l, "11"] false false [:k, "10"] [:m, "12"] true 11 12 # m => 12 [:m, "12"] false true [:l, "11"] false 12 13 

实际class级:

 module Enumerable # your each_with_position method def each_pos &block EachWithPosition.each(self, &block) end end class EachWithPosition attr_reader :index class << self def each *a, &b handler = self.new(*a, :each, &b) end end def initialize collection, method, &block @index = 0 @item, @prev, @next = nil @collection = collection @callback = block self.send(method) end def count @collection.count end alias_method :length, :count alias_method :size, :count def rest count - position end def first? @index == 0 end def last? @index == (count - 1) end def wrapped? !first? && !last? end alias_method :inner?, :wrapped? def position @index + 1 end def prev @prev end def next @next end def current @item end alias_method :item, :current alias_method :value, :current def call if @callback.arity == 1 @callback.call(self) else @callback.call(@item, self) end end def each @collection.each_cons(2) do |e, n| @prev = @item @item = e @next = n self.call @index += 1 # fix cons slice behaviour if last? @prev, @item, @next = @item, @next, nil self.call @index += 1 end end end end 

 arr.each.with_index do |obj, index| p 'first' if index == 0 p 'last' if index == arr.count-1 end 

如果你不介意在最后的动作发生在中间的东西之前,那么这个猴子补丁:

 class Array def for_first return self if empty? yield(first) self[1..-1] end def for_last return self if empty? yield(last) self[0...-1] end end 

允许:

 %w(abcd).for_first do |e| p ['first', e] end.for_last do |e| p ['last', e] end.each do |e| p ['middle', e] end # => ["first", "a"] # => ["last", "d"] # => ["middle", "b"] # => ["middle", "c"] 

我无法抗拒:)这不是性能调整,虽然我认为它不应该比这里的大多数其他答案慢得多。 这一切都是关于糖!

 class Array class EachDSL attr_accessor :idx, :max def initialize arr self.max = arr.size end def pos idx + 1 end def inside? range range.include? pos end def nth? i pos == i end def first? nth? 1 end def middle? not first? and not last? end def last? nth? max end def inside range yield if inside? range end def nth i yield if nth? i end def first yield if first? end def middle yield if middle? end def last yield if last? end end def each2 &block dsl = EachDSL.new self each_with_index do |x,i| dsl.idx = i dsl.instance_exec x, &block end end end 

例1:

 [1,2,3,4,5].each2 do |x| puts "#{x} is first" if first? puts "#{x} is third" if nth? 3 puts "#{x} is middle" if middle? puts "#{x} is last" if last? puts end # 1 is first # # 2 is middle # # 3 is third # 3 is middle # # 4 is middle # # 5 is last 

例2:

 %w{some short simple words}.each2 do |x| first do puts "#{x} is first" end inside 2..3 do puts "#{x} is second or third" end middle do puts "#{x} is middle" end last do puts "#{x} is last" end end # some is first # short is second or third # short is middle # simple is second or third # simple is middle # words is last 

将数组分割成每个范围内的元素应该performance不同的范围。 将每个创build的范围映射到一个块。

 class PartitionEnumerator include RangeMaker def initialize(array) @array = array @handlers = {} end def add(range, handler) @handlers[range] = handler end def iterate @handlers.each_pair do |range, handler| @array[range].each { |value| puts handler.call(value) } end end end 

可以手动创build范围,但是下面的这些帮助器可以使其更容易:

 module RangeMaker def create_range(s) last_index = @array.size - 1 indexes = (0..last_index) return (indexes.first..indexes.first) if s == :first return (indexes.second..indexes.second_last) if s == :middle return (indexes.last..indexes.last) if s == :last end end class Range def second self.first + 1 end def second_last self.last - 1 end end 

用法:

 a = [1, 2, 3, 4, 5, 6] e = PartitionEnumerator.new(a) e.add(e.create_range(:first), Proc.new { |x| x + 1 } ) e.add(e.create_range(:middle), Proc.new { |x| x * 10 } ) e.add(e.create_range(:last), Proc.new { |x| x } ) e.iterate 

我在这里看到很多非常接近的黑客,但是都严重依赖于给定的具有固定大小的迭代器,而不是一个迭代器。 我还想build议在迭代时保存前一个元素,以了解迭代的第一个/最后一个元素。

 previous = {} elements.each do |element| unless previous.has_key?(:element) # will only execute the first time end # normal each block here previous[:element] = element end # the last element will be stored in previous[:element] 

如果你知道数组中的项是唯一的(不像这种情况),你可以这样做:

 a = [1,2,3,4,5] a.each_with_index do |x, i| if x == a.first print x+1 elsif x == a.last print x*10 else print x end end 

有时候for循环只是你最好的select

 if(array.count > 0) first= array[0] #... do something with the first cx = array.count -2 #so we skip the last record on a 0 based array for x in 1..cx middle = array[x] #... do something to the middle end last = array[array.count-1] #... do something with the last item. end 

我知道这个问题已经回答了,但是这个方法没有副作用,也没有检查第13,14,15,第10,100,001个logging是第一个logging还是最后一个logging。

以前的答案在任何数据结构类中都将失败。