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

aro*_*ick 55 ruby ruby-on-rails syntactic-sugar

Ruby/Rails在谈到基本用品的糖时会做很多很酷的事情,我认为有一种非常常见的情况,我想知道是否有人做过帮手或类似的东西.

   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
Run Code Online (Sandbox Code Playgroud)

原谅丑陋,但这可以达到人们想要的......是否有一种红宝石的方式来对循环的第一个和最后一个做一些事情?

[编辑]我认为理想情况下这将是带有参数的数组的扩展(数组实例,所有元素函数,第一个元素函数,最后元素函数)......但我对其他想法持开放态度.

Mat*_*chu 31

如果您愿意,可以抓取第一个和最后一个元素并以不同方式处理它们.

first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one
Run Code Online (Sandbox Code Playgroud)

  • 为了避免副作用,你可以做`first,middle,last = array.first,array [1 ..- 2],array.last` (11认同)

sep*_*p2k 14

如果第一次和最后一次迭代的代码与其他迭代的代码没有任何共同之处,您还可以执行以下操作:

do_something( a.first )
a[1..-2].each do |x|
  do_something_else( x )
end
do_something_else_else( a.last )
Run Code Online (Sandbox Code Playgroud)

如果不同的情况有一些共同的代码,你的方式很好.


Way*_*rad 9

如果你能这样做怎么办?

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

或这个?

%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
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

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

诀窍是each,each_with_index,&c.如果没有阻止,则返回枚举器.枚举器可以完成Enumerable所做的所有事情.但对我们来说,重要的是我们可以通过猴子修补Enumerator来添加一种迭代方式,"包装"现有的迭代,无论它是什么.

  • 对于ruby 2及以上,用`class Enumerator`替换`class Enumerable :: Enumerator` (2认同)

Way*_*rad 7

或者一点点领域特定语言:

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"]
Run Code Online (Sandbox Code Playgroud)

和它的代码:

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
Run Code Online (Sandbox Code Playgroud)

我开始思考,"如果只有你可以将多个块传递给Ruby函数,那么你可以对这个问题有一个灵活而简单的解决方案." 然后我意识到DSL的玩法几乎就像传递多个块一样.


Nat*_*ate 6

正如许多人所指出的,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
Run Code Online (Sandbox Code Playgroud)

要么

array.each_with_index do |item,index|
  if index == 0
    # first item
  end
  # all items
  if index == array.length-1
    # last item
  end
end
Run Code Online (Sandbox Code Playgroud)

或者通过数组扩展

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
Run Code Online (Sandbox Code Playgroud)


clo*_*ord 5

如果您愿意添加一些样板文件,可以在数组类中添加以下内容:

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

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

[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
Run Code Online (Sandbox Code Playgroud)

对于以下输出:

first: 1
otherwise: 2
otherwise: 3
last: 4
Run Code Online (Sandbox Code Playgroud)

  • 你正在对数组中的每个项进行比较,以检查它是第一个还是最后一个元素.在一百万的数组上,你做了1,999,998无用的比较. (3认同)