Волшебный первый и последний индикатор в цикле в Ruby / Rails? - PullRequest
55 голосов
/ 11 февраля 2010

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

Прошу прощения за уродство, но это дает то, что вы могли бы хотеть ... есть ли рубиновый способ сделать что-то с первым и последним из цикла?

[EDIT] Я думаю, в идеале это было бы расширение для Array с параметрами (экземпляр массива, функция всех элементов, функция первых элементов, функция последних элементов) ... но я открыт для других мысли.

Ответы [ 16 ]

1 голос
/ 06 марта 2013

Я не мог удержаться :) Это не настроено на производительность, хотя я думаю, что это не должно быть намного медленнее, чем большинство других ответов здесь. Это все о сахаре!

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
1 голос
/ 11 февраля 2010

Если вы не возражаете, что «последнее» действие происходит перед посередине, то это обезьяна-патч:

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(a b c d).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"]
0 голосов
/ 16 ноября 2017

Я вижу здесь много хаков, которые довольно близки, но все сильно зависят от того, что данный итератор имеет фиксированный размер и НЕ является итератором.Я также хотел бы предложить сохранить предыдущий элемент, когда вы выполняете итерацию, чтобы узнать первый / последний элемент, который был повторен.

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] 
0 голосов
/ 01 марта 2012

Иногда цикл for - это просто ваш лучший вариант

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-тысячного, 10 001-го ... первой или последней.

Предыдущие ответы не дали бы назначения в любом классе структур данных.

0 голосов
/ 11 февраля 2010

Разделите массив на диапазоны, где элементы в каждом диапазоне должны вести себя по-разному. Сопоставьте каждый диапазон, созданный таким образом, с блоком.

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

Может создавать диапазоны вручную, но эти помощники ниже облегчают:

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
0 голосов
/ 11 февраля 2010

Если вы знаете, что элементы в массиве уникальны (в отличие от этого случая), вы можете сделать это:

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
...