¿Cómo mapear con índice en Ruby?

438

¿Cuál es la forma más fácil de convertir

[x1, x2, x3, ... , xN]

a

[[x1, 2], [x2, 3], [x3, 4], ... , [xN, N+1]]
Misha Moroshko
fuente

Respuestas:

835

Si usa Ruby 1.8.7 o 1.9, puede usar el hecho de que los métodos iteradores como each_with_index, cuando se llama sin un bloque, devuelven un Enumeratorobjeto, al que puede llamar Enumerablemétodos como mapon. Entonces puedes hacer:

arr.each_with_index.map { |x,i| [x, i+2] }

En 1.8.6 puedes hacer:

require 'enumerator'
arr.enum_for(:each_with_index).map { |x,i| [x, i+2] }
sepp2k
fuente
¡Gracias! ¿Me podría dar un puntero a la documentación .each_with_index.map?
Misha Moroshko
1
@Misha: mapes un método de Enumerablecomo siempre. each_with_index, Cuando se le llama sin un bloque, devuelve un Enumeratorobjeto (en 1.8.7+), que mezcla en Enumerable, por lo que se puede llamar map, select, rejectetc., en que al igual que en una matriz, hachís, etc. variar
sepp2k
99
OMI, esto es más simple y mejor lectura en 1.8.7+:arr.map.with_index{ |o,i| [o,i+2] }
Phrogz
44
@Phrogz: map.with_indexno funciona en 1.8.7 ( mapdevuelve una matriz cuando se llama sin un bloque en 1.8).
sepp2k
2
¡Importante tener en cuenta que esto no funciona con .map! si desea afectar directamente la matriz en la que está haciendo el bucle.
Ash Blue
259

Ruby tiene Enumerator # with_index (offset = 0) , así que primero convierta la matriz en un enumerador usando Object # to_enum o Array # map :

[:a, :b, :c].map.with_index(2).to_a
#=> [[:a, 2], [:b, 3], [:c, 4]]
tokland
fuente
11
¡Creo que esta es la mejor respuesta, porque funcionará con el mapa! foo = ['d'] * 5; foo.map!.with_index { |x,i| x * i }; foo #=> ["", "d", "dd", "ddd", "dddd"]
Connor McKay
130

En ruby ​​1.9.3 hay un método encadenable llamado with_indexque se puede encadenar al mapa.

Por ejemplo:

array.map.with_index { |item, index| ... }
fruqi
fuente
18

Sobre la ofuscación superior:

arr = ('a'..'g').to_a
indexes = arr.each_index.map(&2.method(:+))
arr.zip(indexes)
Andrew Grimm
fuente
12
¡Andrew debe tener una gran seguridad laboral! :)
David J.
9

Aquí hay dos opciones más para 1.8.6 (o 1.9) sin usar el enumerador:

# Fun with functional
arr = ('a'..'g').to_a
arr.zip( (2..(arr.length+2)).to_a )
#=> [["a", 2], ["b", 3], ["c", 4], ["d", 5], ["e", 6], ["f", 7], ["g", 8]]

# The simplest
n = 1
arr.map{ |c| [c, n+=1 ] }
#=> [["a", 2], ["b", 3], ["c", 4], ["d", 5], ["e", 6], ["f", 7], ["g", 8]]
Phrogz
fuente
8

Siempre me ha gustado la sintaxis de este estilo:

a = [1, 2, 3, 4]
a.each_with_index.map { |el, index| el + index }
# => [1, 3, 5, 7]

Invocar each_with_indexle proporciona un enumerador que puede asignar fácilmente con su índice disponible.

Yburyug
fuente
44
¿Cómo es esta forma diferente la respuesta, dada casi 5 años antes que la tuya ?
Andrey Deineko
4

Una forma divertida pero inútil de hacer esto:

az  = ('a'..'z').to_a
azz = az.map{|e| [e, az.index(e)+2]}
Automatico
fuente
¿Porque el odio? Es una forma funcional de hacer esto E incluso digo que es una forma tonta de lograr los resultados.
Automático
la llamada a #index significa que esto es ahora un bucle O (N ^ 2) también ¿por qué el +2? :)
rogerdpack
2
Al momento de escribir A fun, but useless way. +2es crear la salida que pide el OP
Automatico
3
a = [1, 2, 3]
p [a, (2...a.size+2).to_a].transpose
Nikolay Bobrovskiy
fuente
1
module Enumerable
  def map_with_index(&block)
    i = 0
    self.map { |val|
      val = block.call(val, i)
      i += 1
      val
    }
  end
end

["foo", "bar"].map_with_index {|item, index| [item, index] } => [["foo", 0], ["bar", 1]]
Mo Wad
fuente
3
¡DIOS MIO! ¿Incluso leíste las otras respuestas? map.with_indexYa existe en rubí. ¿Por qué sugerir reabrir la clase enumerable y agregar algo que ya existe?
nathanvda
Esta podría ser una forma más fácil de usar para 1.8.6 y 1.8.7 (sí, algunos de nosotros todavía la usamos) en lugar de tener que usar cosas más extrañas como each_with_index.mapetc. e incluso aquellos de nosotros en versiones más nuevas podrían preferir tener que usar map.with_index FWIW :)
rogerdpack
1

A menudo hago esto:

arr = ["a", "b", "c"]

(0...arr.length).map do |int|
  [arr[int], int + 2]
end

#=> [["a", 2], ["b", 3], ["c", 4]]

En lugar de iterar directamente sobre los elementos de la matriz, está iterando sobre un rango de enteros y usándolos como índices para recuperar los elementos de la matriz.

grandinero
fuente
1
Si lees las otras respuestas, espero que ahora te des cuenta de que hay mejores enfoques. Así que no estoy seguro de por qué necesita agregar esto.
nathanvda
Si la respuesta de Andrew Grimm merece diez votos, ¡este merece al menos uno!
Camille Goudeseune