¿Cuál es la forma "correcta" de recorrer una matriz en Ruby?

341

PHP, para todas sus verrugas, es bastante bueno en este aspecto. No hay diferencia entre una matriz y un hash (tal vez soy ingenuo, pero esto obviamente me parece correcto), y para iterar o simplemente

foreach (array/hash as $key => $value)

En Ruby hay muchas maneras de hacer este tipo de cosas:

array.length.times do |i|
end

array.each

array.each_index

for i in array

Los hashes tienen más sentido, ya que siempre uso

hash.each do |key, value|

¿Por qué no puedo hacer esto para matrices? Si quiero recordar solo un método, creo que puedo usarlo each_index(ya que hace que tanto el índice como el valor estén disponibles), pero es molesto tener que hacerlo en array[index]lugar de solo value.


Oh cierto, me olvidé array.each_with_index. Sin embargo, este apesta porque va |value, key|y hash.eachva |key, value|! ¿No es esto una locura?

Tom Lehman
fuente
1
Supongo que se array#each_with_indexusa |value, key|porque el nombre del método implica el orden, mientras que el orden utilizado para hash#eachimita la hash[key] = valuesintaxis.
Benjineer
3
Si recién está comenzando con los bucles en Ruby, eche un vistazo usando seleccionar, rechazar, recopilar, inyectar y detectar
Matthew Carriere

Respuestas:

560

Esto iterará a través de todos los elementos:

array = [1, 2, 3, 4, 5, 6]
array.each { |x| puts x }

Huellas dactilares:

1
2
3
4
5
6

Esto iterará a través de todos los elementos que le dan el valor y el índice:

array = ["A", "B", "C"]
array.each_with_index {|val, index| puts "#{val} => #{index}" }

Huellas dactilares:

A => 0
B => 1
C => 2

No estoy muy seguro de su pregunta cuál está buscando.

Robert Gamble
fuente
1
Fuera de tema No puedo encontrar each_with_index en ruby ​​1.9 en array
CantGetANick
19
@CantGetANick: la clase Array incluye el módulo Enumerable que tiene este método.
xuinkrbin.
88

Creo que no hay una sola manera correcta . Hay muchas formas diferentes de iterar, y cada una tiene su propio nicho.

  • each es suficiente para muchos usos, ya que a menudo no me importan los índices.
  • each_ with _index actúa como Hash # cada uno: obtienes el valor y el índice.
  • each_index- Solo los índices. No uso este a menudo. Equivalente a "length.times".
  • map es otra forma de iterar, útil cuando quieres transformar una matriz en otra.
  • select es el iterador para usar cuando desea elegir un subconjunto.
  • inject es útil para generar sumas o productos, o para recopilar un solo resultado.

Puede parecer mucho recordar, pero no se preocupe, puede sobrevivir sin conocerlos todos. Pero a medida que comience a aprender y usar los diferentes métodos, su código se volverá más y más claro, y estará en camino hacia el dominio de Ruby.

AShelly
fuente
¡gran respuesta! Me gustaría mencionar el reverso como #reject y alias como #collect.
Emily Tui Ch
60

No digo que Array-> |value,index|y Hash-> |key,value|no sea una locura (ver el comentario de Horace Loeb), pero sí digo que hay una forma sensata de esperar este acuerdo.

Cuando trato con matrices, me centro en los elementos de la matriz (no en el índice porque el índice es transitorio). El método es cada uno con índice, es decir, cada + índice, o | cada, índice | o |value,index|. Esto también es coherente con el índice visto como un argumento opcional, por ejemplo | valor | es equivalente a | value, index = nil | que es consistente con | value, index |.

Cuando estoy tratando con hashes, a menudo estoy más enfocado en las claves que en los valores, y generalmente estoy tratando con claves y valores en ese orden, ya sea key => valueo hash[key] = value.

Si desea escribir pato, entonces explícitamente use un método definido como mostró Brent Longborough, o un método implícito como mostró maxhawkins.

Ruby tiene que ver con acomodar el lenguaje para adaptarse al programador, no para que el programador se adapte al lenguaje. Por eso hay tantas formas. Hay muchas maneras de pensar en algo. En Ruby, eliges el más cercano y el resto del código generalmente se cae de manera extremadamente clara y concisa.

En cuanto a la pregunta original, "¿Cuál es la forma" correcta "de iterar a través de una matriz en Ruby?" Bueno, creo que la forma central (es decir, sin poder sintáctico potente o poder orientado a objetos) es hacer:

for index in 0 ... array.size
  puts "array[#{index}] = #{array[index].inspect}"
end

Pero Ruby tiene que ver con el poderoso azúcar sintáctico y el poder orientado a objetos, pero de todos modos aquí es el equivalente para los hashes, y las claves se pueden ordenar o no:

for key in hash.keys.sort
  puts "hash[#{key.inspect}] = #{hash[key].inspect}"
end

Entonces, mi respuesta es: "La forma" correcta "de recorrer una matriz en Ruby depende de usted (es decir, del programador o del equipo de programación) y del proyecto". El mejor programador de Ruby hace la mejor elección (de qué poder sintáctico y / o qué enfoque orientado a objetos). El mejor programador de Ruby continúa buscando más formas.


Ahora, quiero hacer otra pregunta, "¿Cuál es la forma" correcta "de recorrer un rango en Ruby hacia atrás?" (Esta pregunta es cómo llegué a esta página).

Es bueno hacer (para los delanteros):

(1..10).each{|i| puts "i=#{i}" }

pero no me gusta hacer (al revés):

(1..10).to_a.reverse.each{|i| puts "i=#{i}" }

Bueno, en realidad no me importa hacer eso demasiado, pero cuando enseño hacia atrás, quiero mostrarles a mis estudiantes una buena simetría (es decir, con una diferencia mínima, por ejemplo, solo agregando un reverso o un paso -1, pero sin modificando cualquier otra cosa). Puedes hacer (por simetría):

(a=*1..10).each{|i| puts "i=#{i}" }

y

(a=*1..10).reverse.each{|i| puts "i=#{i}" }

que no me gusta mucho, pero no puedes hacer

(*1..10).each{|i| puts "i=#{i}" }
(*1..10).reverse.each{|i| puts "i=#{i}" }
#
(1..10).step(1){|i| puts "i=#{i}" }
(1..10).step(-1){|i| puts "i=#{i}" }
#
(1..10).each{|i| puts "i=#{i}" }
(10..1).each{|i| puts "i=#{i}" }   # I don't want this though.  It's dangerous

En última instancia, podrías hacer

class Range

  def each_reverse(&block)
    self.to_a.reverse.each(&block)
  end

end

pero quiero enseñar Ruby puro en lugar de enfoques orientados a objetos (todavía). Me gustaría repetir al revés:

  • sin crear una matriz (considere 0..1000000000)
  • trabajando para cualquier rango (por ejemplo, cadenas, no solo enteros)
  • sin usar ningún poder adicional orientado a objetos (es decir, sin modificación de clase)

Creo que esto es imposible sin definir un predmétodo, lo que significa modificar la clase Range para usarlo. Si puede hacer esto, hágamelo saber, de lo contrario se agradecería la confirmación de imposibilidad, aunque sería decepcionante. Quizás Ruby 1.9 aborde esto.

(Gracias por tu tiempo leyendo esto).


fuente
55
1.upto(10) do |i| puts i endy 10.downto(1) do puts i endle da la simetría que deseaba. Espero que ayude. No estoy seguro de si funcionaría para cadenas y tal.
Suren
(1..10) .to_a.sort {| x, y | y <=> x}. cada {| i | pone "i = # {i}"} - revertir es más lento.
Davidslv
Puede [*1..10].each{|i| puts "i=#{i}" }.
Hauleth
19

Use each_with_index cuando necesite ambos.

ary.each_with_index { |val, idx| # ...
J Cooper
fuente
12

Las otras respuestas están bien, pero quería señalar otra cosa periférica: las matrices están ordenadas, mientras que los hashes no están en 1.8. (En Ruby 1.9, los hashes se ordenan por orden de inserción de claves.) Por lo tanto, no tendría sentido antes de 1.9 iterar sobre un hash de la misma manera / secuencia que las matrices, que siempre han tenido un orden definido. No sé cuál es el orden predeterminado para las matrices asociativas de PHP (aparentemente mi Google Fu tampoco es lo suficientemente fuerte como para descubrir eso), pero no sé cómo puede considerar las matrices PHP regulares y las matrices asociativas PHP para ser "lo mismo" en este contexto, ya que el orden de las matrices asociativas parece indefinido.

Como tal, la forma Ruby me parece más clara e intuitiva. :)

Pistos
fuente
1
¡los hashes y las matrices son lo mismo! Las matrices asignan enteros a objetos y los hash asignan objetos a objetos. Las matrices son solo casos especiales de hashes, ¿no?
Tom Lehman el
1
Como dije, una matriz es un conjunto ordenado. Un mapeo (en el sentido genérico) no está ordenado. Si restringe el conjunto de claves a enteros (como con las matrices), resulta que el conjunto de claves tiene un orden. En un mapeo genérico (Hash / Matriz asociativa), las claves pueden no tener un orden.
Pistos
@Horace: los hashes y las matrices no son lo mismo. Si uno es un acase especial del otro, no pueden ser lo mismo. Pero lo que es peor, las matrices no son un tipo especial de hashes, es solo una forma abstracta de verlos. Como Brent señaló anteriormente, el uso de hashes y matrices de manera intercambiable podría indicar un olor a código.
Zane
9

Intentar hacer lo mismo de manera consistente con las matrices y los hashes podría ser solo un olor a código, pero, a riesgo de que me tilden de medio parche codoroso, si buscas un comportamiento coherente, ¿funcionaría? ?:

class Hash
    def each_pairwise
        self.each { | x, y |
            yield [x, y]
        }
    end
end

class Array
    def each_pairwise
        self.each_with_index { | x, y |
            yield [y, x]
        }
    end
end

["a","b","c"].each_pairwise { |x,y|
    puts "#{x} => #{y}"
}

{"a" => "Aardvark","b" => "Bogle","c" => "Catastrophe"}.each_pairwise { |x,y|
    puts "#{x} => #{y}"
}
Brent.Longborough
fuente
7

Aquí están las cuatro opciones enumeradas en su pregunta, ordenadas por libertad de control. Es posible que desee utilizar uno diferente dependiendo de lo que necesite.

  1. Simplemente ve a través de los valores:

    array.each
  2. Simplemente revise los índices:

    array.each_index
  3. Ir a través de índices + variable de índice:

    for i in array
  4. Control de conteo de bucle + índice variable:

    array.length.times do | i |
Jake
fuente
5

Había estado tratando de construir un menú (en Camping y Markaby ) usando un hash.

Cada elemento tiene 2 elementos: una etiqueta de menú y una URL , por lo que un hash parecía correcto, pero la URL '/' para 'Inicio' siempre aparecía en último lugar (como era de esperar para un hash), por lo que los elementos de menú aparecían incorrectos orden.

Usar una matriz con each_slicehace el trabajo:

['Home', '/', 'Page two', 'two', 'Test', 'test'].each_slice(2) do|label,link|
   li {a label, :href => link}
end

Agregar valores adicionales para cada elemento del menú (por ejemplo, como un nombre de ID de CSS ) solo significa aumentar el valor del segmento. Entonces, como un hash pero con grupos que consisten en cualquier número de elementos. Perfecto.

¡Esto es solo para decir gracias por insinuar inadvertidamente una solución!

Obvio, pero vale la pena declarar: sugiero verificar si la longitud de la matriz es divisible por el valor de corte.

Dave Everitt
fuente
4

Si usa el mixin enumerable (como lo hace Rails), puede hacer algo similar al fragmento de php enumerado. Solo usa el método each_slice y aplana el hash.

require 'enumerator' 

['a',1,'b',2].to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

# is equivalent to...

{'a'=>1,'b'=>2}.to_a.flatten.each_slice(2) {|x,y| puts "#{x} => #{y}" }

Se requieren menos parches de mono.

Sin embargo, esto causa problemas cuando tiene una matriz recursiva o un hash con valores de matriz. En ruby ​​1.9, este problema se resuelve con un parámetro para el método de aplanamiento que especifica qué tan profundo debe repetirse.

# Ruby 1.8
[1,2,[1,2,3]].flatten
=> [1,2,1,2,3]

# Ruby 1.9
[1,2,[1,2,3]].flatten(0)
=> [1,2,[1,2,3]]

En cuanto a la pregunta de si esto es un olor a código, no estoy seguro. Por lo general, cuando tengo que inclinarme hacia atrás para iterar sobre algo, retrocedo y me doy cuenta de que estoy atacando mal el problema.

maxhawkins
fuente
2

La forma correcta es aquella con la que se siente más cómodo y que hace lo que quiere que haga. En la programación rara vez hay una forma "correcta" de hacer las cosas, más a menudo hay varias formas de elegir.

Si se siente cómodo con cierta forma de hacer las cosas, hágalo, a menos que no funcione, entonces es hora de encontrar una mejor manera.

Dariusz G. Jagielski
fuente
2

En Ruby 2.1, se elimina cada método index_with_index. En su lugar, puede usar each_index

Ejemplo:

a = [ "a", "b", "c" ]
a.each_index {|x| print x, " -- " }

produce:

0 -- 1 -- 2 --
Amjed Shareef
fuente
44
Esto no es verdad. Como se indica en los comentarios de la respuesta aceptada, el motivo each_with_indexque no aparece en la documentación es porque lo proporciona el Enumerablemódulo.
ihaztehcodez
0

Usar el mismo método para iterar a través de matrices y hash tiene sentido, por ejemplo, para procesar estructuras de hash y matrices anidadas que a menudo resultan de analizadores, de leer archivos JSON, etc.

Una forma inteligente que aún no se ha mencionado es cómo se hace en la biblioteca Ruby Facets de extensiones de biblioteca estándar. Desde aquí :

class Array

  # Iterate over index and value. The intention of this
  # method is to provide polymorphism with Hash.
  #
  def each_pair #:yield:
    each_with_index {|e, i| yield(i,e) }
  end

end

Ya hay Hash#each_pairun alias de Hash#each. Entonces, después de este parche, también lo tenemos Array#each_pairy podemos usar indistintamente para iterar a través de Hashes y Arrays. Esto corrige la locura observada del OP que Array#each_with_indextiene los argumentos de bloque invertidos en comparación con Hash#each. Ejemplo de uso:

my_array = ['Hello', 'World', '!']
my_array.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"

my_hash = { '0' => 'Hello', '1' => 'World', '2' => '!' }
my_hash.each_pair { |key, value| pp "#{key}, #{value}" }

# result: 
"0, Hello"
"1, World"
"2, !"
Tanius
fuente