[英]Magic First and Last Indicator in a Loop in Ruby/Rails?
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
原諒丑陋,但這可以達到人們想要的......是否有一種紅寶石的方式來對循環的第一個和最后一個做一些事情?
[編輯]我認為理想情況下這將是帶參數的數組擴展(數組實例,所有元素函數,第一個元素函數,最后元素函數)......但我對其他想法持開放態度。
如果您願意,可以抓取第一個和最后一個元素並以不同方式處理它們。
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
它有一個小的狀態引擎,因為它必須向前看一次迭代。
訣竅是each,each_with_index,&c。 如果沒有阻止,則返回枚舉器。 枚舉器可以完成Enumerable所做的所有事情。 但對我們來說,重要的是我們可以通過猴子修補Enumerator來添加一種迭代方式,“包裝”現有的迭代,無論它是什么。
或者一點點領域特定語言:
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 /的方法。 (抱歉模糊不清 - 我還不是黑帶元程序員)[ 編輯 :但是,我是從底部的人那里復制的)
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對相關問題的回答 (有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
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 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 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
我看到很多hacks非常接近,但都嚴重依賴於具有固定大小的給定迭代器而不是迭代器。 我還建議在迭代時保存前一個元素,以了解迭代的第一個/最后一個元素。
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 ...... 10ththth,10,001th ...記錄是第一個記錄,還是最后一個記錄。
以前的答案將在任何數據結構類中失敗。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.