Волшебный первый и последний индикатор в цикле в Ruby/направляющих?

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

   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

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

[РЕДАКТИРОВАНИЕ] я думаю идеально, что это было бы расширением на Массиве с параметрами (экземпляр массива, вся функция элементов, первая функция элементов, последняя функция элементов)..., но я открыт для других мыслей.

55
задан aronchick 10 February 2010 в 22:12
поделиться

10 ответов

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

first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one
31
ответ дан 7 November 2019 в 07:26
поделиться

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

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
-1
ответ дан 7 November 2019 в 07:26
поделиться

Если вы не возражаете против того, что "последнее" действие происходит перед тем, что находится в середине, то этот monkey-patch:

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"]
1
ответ дан 7 November 2019 в 07:26
поделиться

В 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
3
ответ дан 7 November 2019 в 07:26
поделиться

Что, если бы вы могли сделать это?

%w(a b c d).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 и т. Д. вернуть перечислитель, если не указан блок. Перечислители делают то же, что и Enumerable, и даже немного больше. Но для нас важно то, что мы можем обезопасить Enumerator, добавив еще один способ итерации, «обернув» существующую итерацию, какой бы она ни была.

9
ответ дан 7 November 2019 в 07:26
поделиться

Интересный вопрос, над которым я тоже немного думал.

Думаю, вам придется создать три разных блока/процесса/как бы они ни назывались, а затем создать метод, вызывающий нужный блок/процесс/как бы то ни было. (Извините за неясность - я еще не метапрограммист с черным поясом) [Edit: однако, я скопировал у кого-то, кто находится внизу)

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

Edit: ответ Сванте (с подсказкой Мольфа) на смежный вопрос показывает, как передать несколько блоков кода в один метод:

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"})
3
ответ дан 7 November 2019 в 07:26
поделиться

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

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
ответ дан 7 November 2019 в 07:26
поделиться

Если код для первой и последней итерации не имеет ничего общего с кодом для других итераций, можно сделать так:

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

Если разные случаи имеют какой-то общий код, то ваш способ подходит.

14
ответ дан 7 November 2019 в 07:26
поделиться

Или крошечный доменный язык:

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, тогда у вас может быть изящное и простое решение этого вопроса ». Затем я понял, что DSL играют маленькие уловки, которые похожи на передачу нескольких блоков.

7
ответ дан 7 November 2019 в 07:26
поделиться

Если вы готовы добавить немного шаблонов, вы можете добавить что-то вроде этого в класс массива:

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
5
ответ дан 7 November 2019 в 07:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: