Ответ 1
Вы можете взять первый и последний элементы и обработать их по-другому, если хотите.
first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one
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 с параметрами (массив, функция всех элементов, функция первых элементов, функция последних элементов)... но я открыт для другие мысли.
Вы можете взять первый и последний элементы и обработать их по-другому, если хотите.
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(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
Он получил небольшой механизм состояния, потому что он должен смотреть вперед на одну итерацию.
Фокус в том, что каждый, каждый_with_index, и c. return Enumerator, если не задан блок. Перечислители делают все, что делает Enumerable, и немного больше. Но для нас важно то, что мы можем Enumerator Monkey-patch добавить еще один способ итерации, "обертывания" существующей итерации, что бы это ни было.
Или небольшой маленький доменный язык:
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 играет небольшие трюки, почти похожие на несколько блоков.
Как указывали многие, 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
Интересный вопрос, и тот, о котором я тоже немного подумал.
Я думаю, вам нужно создать три разных блока/procs/независимо от того, что они вызывают, а затем создать метод, который вызывает правильный блок /proc/whatever. (Извините за неопределенность - я еще не метапрограммист черного пояса) [ Изменить: однако, я скопировал у кого-то, кто внизу)
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
Изменить: Svante answer (с предложением molf) к связанному вопросу показывает, как передать несколько блоков кода одному методу:
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"})
Мне нужна была эта функция время от времени, поэтому для этой цели я создал небольшой класс.
Последняя версия: 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 a c true 1 2
# c false false b d true 2 3
# d false false c e true 3 4
# e false false d f true 4 5
# f false false e g true 5 6
# g false false f h true 6 7
# h false false g i true 7 8
# i false false h j true 8 9
# j false false i k true 9 10
# k false false j l true 10 11
# l false false k m 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
Фактический класс:
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(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"]
Я не мог удержаться:) Это не настроено на производительность, хотя я думаю, что это не должно быть намного медленнее, чем большинство других ответов здесь. Все о сахаре!
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
Разделите массив на диапазоны, где элементы внутри каждого диапазона должны вести себя по-разному. Сопоставьте каждый диапазон, созданный таким образом для блока.
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
Я вижу здесь много хакеров, которые довольно близки, но все они сильно зависят от заданного итератора с фиксированным размером и НЕ являются итератором. Я также хотел бы предложить сохранить предыдущий элемент при повторном запуске, чтобы узнать первый/последний элемент, который был повторен.
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 - это всего лишь ваш лучший вариант
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-го.. 10thousandth, 10,001th... первой записью или последней.
Предыдущие ответы не удалили назначение в любом классе структур данных.