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
Простите уродство, но это достигает то, что можно было бы хотеть... там рубиновый способ сделать что-то к первому и последнему из цикла?
[РЕДАКТИРОВАНИЕ] я думаю идеально, что это было бы расширением на Массиве с параметрами (экземпляр массива, вся функция элементов, первая функция элементов, последняя функция элементов)..., но я открыт для других мыслей.
Вы можете взять первый и последний элементы и обработать их по-другому, если хотите.
first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one
Если вы знаете, что элементы в массиве уникальны (в отличие от этого случая), вы можете сделать следующее:
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
Если вы не возражаете против того, что "последнее" действие происходит перед тем, что находится в середине, то этот 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"]
В 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
Что, если бы вы могли сделать это?
%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, добавив еще один способ итерации, «обернув» существующую итерацию, какой бы она ни была.
Интересный вопрос, над которым я тоже немного думал.
Думаю, вам придется создать три разных блока/процесса/как бы они ни назывались, а затем создать метод, вызывающий нужный блок/процесс/как бы то ни было. (Извините за неясность - я еще не метапрограммист с черным поясом) [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"})
Разделите массив на диапазоны, где элементы внутри каждого диапазона должны вести себя по-разному. Сопоставить каждый созданный таким образом диапазон с блоком.
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
Если код для первой и последней итерации не имеет ничего общего с кодом для других итераций, можно сделать так:
do_something( a.first )
a[1..-2].each do |x|
do_something_else( x )
end
do_something_else_else( a.last )
Если разные случаи имеют какой-то общий код, то ваш способ подходит.
Или крошечный доменный язык:
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 играют маленькие уловки, которые похожи на передачу нескольких блоков.
Если вы готовы добавить немного шаблонов, вы можете добавить что-то вроде этого в класс массива:
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