ruby-on-rails - operador - porcentaje en ruby
Primer indicador mágico y último en un bucle en Ruby/Rails? (16)
¿Qué pasaría si pudieras hacer esto?
%w(a b c d).each.with_position do |e, position|
p [e, position] # => ["a", :first]
# => ["b", :middle]
# => ["c", :middle]
# => ["d", :last]
end
¿O esto?
%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
En MRI> = 1.8.7, todo lo que se necesita es este parche de mono:
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
Tiene un pequeño motor de estado porque debe mirar hacia adelante una iteración.
El truco es que cada uno, cada_con_index, & c. devuelve un Enumerador si no tiene bloque. Los enumeradores hacen todo lo que hace un Enumerable y un poco más. Pero para nosotros, lo importante es que podemos aplicar un parche a Enumerator para agregar una forma más de iterar, "envolviendo" la iteración existente, sea lo que sea.
Ruby / Rails hace muchas cosas geniales cuando se trata de azúcar para cosas básicas, y creo que hay un escenario muy común en el que me pregunto si alguien ha hecho alguna ayuda o algo similar.
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
Perdonen la fealdad, pero esto llega a lo que uno podría desear ... ¿hay alguna manera de hacer algo con Ruby en el primer y último ciclo?
[EDIT] Creo que idealmente sería una extensión en Array con parámetros (instancia de matriz, función de todos los elementos, función de los primeros elementos, función de los últimos elementos) ... pero estoy abierto a otros pensamientos.
A veces un bucle for es solo tu mejor opción
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
Sé que esta pregunta fue respondida, pero este método no tiene efectos secundarios y no verifica si el registro 13º, 14º, 15º ... 10º.OOO, 10.001º es el primer registro, o el último.
Las respuestas anteriores habrían fallado la asignación en cualquier clase de estructura de datos.
BESO
arr.each.with_index do |obj, index|
p ''first'' if index == 0
p ''last'' if index == arr.count-1
end
Como muchos han señalado, each_with_index
parece ser la clave para esto. Tengo este bloque de código que me gustó.
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
O
array.each_with_index do |item,index|
if index == 0
# first item
end
# all items
if index == array.length-1
# last item
end
end
O por extensiones Array
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
Necesitaba esta funcionalidad de vez en cuando, así que diseñé una pequeña clase para ese propósito.
La última versión está en: https://gist.github.com/3823837
Muestra:
("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
Clase real:
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
No existe la sintaxis "do this the time (first | last) time" en Ruby. Pero si buscas concisión, podrías hacer esto:
a.each_with_index do |x, i|
print (i > 0 ? (i == a.length - 1 ? x*10 : x) : x+1)
end
El resultado es lo que esperarías:
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
No pude resistir :) Esto no está ajustado para el rendimiento, aunque supongo que no debería ser mucho más lento que la mayoría de las otras respuestas aquí. ¡Todo se trata del azúcar!
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
Ejemplo 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
Ejemplo 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
O un pequeño pequeño lenguaje específico del dominio:
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"]
y el código que lo hace funcionar:
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
Empecé a pensar, "si solo pudieras pasar múltiples bloques a una función de Ruby, entonces podrías tener una solución fácil y lisa para esta pregunta". Luego me di cuenta de que los DSL juegan pequeños trucos que son casi como pasar múltiples bloques.
Particione la matriz en rangos donde se supone que los elementos dentro de cada rango se comporten de manera diferente. Asigne cada rango así creado a un bloque.
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
Podría crear rangos a mano, pero estos ayudantes a continuación lo hacen más fácil:
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
Uso:
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
Podría tomar los primeros y últimos elementos y procesarlos de manera diferente, si lo desea.
first = array.shift
last = array.pop
process_first_one
array.each { |x| process_middle_bits }
process_last_one
Pregunta interesante, y una que he pensado un poco también.
Creo que tendrías que crear tres bloques / procs / lo que se llame, y luego crear un método que llame al bloque / proc / lo que sea correcto. (Perdón por la imprecisión - todavía no soy un metaprogramador de cinturón negro) [ Editar : sin embargo, he copiado de alguien que está en la parte inferior)
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
produce
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
Editar : la answer de Svante (con la sugerencia de molf) a una pregunta relacionada muestra cómo pasar múltiples bloques de código a un único método:
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"})
Si el código para la primera y última iteración no tiene nada en común con el código para las otras iteraciones, también podría hacer:
do_something( a.first )
a[1..-2].each do |x|
do_something_else( x )
end
do_something_else_else( a.last )
Si los diferentes casos tienen algún código en común, tu camino está bien.
Si está dispuesto a agregar un texto estándar, puede agregar algo como esto a la clase de matriz:
class Array
def each_fl
each_with_index do |x,i|
yield [i==0 ? :first : (i==length-1 ? :last : :inner), x]
end
end
end
y luego en cualquier lugar que necesites, obtienes la siguiente sintaxis:
[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
para el siguiente resultado:
first: 1
otherwise: 2
otherwise: 3
last: 4
Si no te importa que la "última" acción ocurra antes del material en el medio, entonces este parche de mono:
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
Permite esto:
%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"]
Si sabe que los elementos del conjunto son únicos (a diferencia de este caso), puede hacer esto:
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
Veo muchos hacks aquí que están bastante cerca, pero todos dependen en gran medida de que el iterador dado tenga un tamaño fijo y NO sea un iterador. También me gustaría proponer guardar el elemento anterior a medida que avanzas para conocer el primer / último elemento que se repitió.
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]