¿Diferencia entre mapa y recoger en Ruby?

428

Busqué en Google esto y obtuve opiniones irregulares / contradictorias: ¿hay realmente alguna diferencia entre hacer un mapy hacer un collecten una matriz en Ruby / Rails?

Los documentos no parecen sugerir ninguno, pero ¿hay quizás diferencias en el método o el rendimiento?

sscirrus
fuente
55
mapse prefiere en Code Golf .
Cary Swoveland
1
Como explicación de por qué mapse prefiere en CodeGolf, lo que puede no ser obvio para todos: es solo porque collecttiene cuatro caracteres más map, pero la misma funcionalidad.
Jochem Schulenklopper
2
Solo para jugar al abogado del diablo, personalmente me parece collectmás legible y natural: la idea de 'recopilar' registros y hacerles X tiene más sentido natural para mí que 'mapear' registros y hacerles X.
sscirrus

Respuestas:

480

No hay diferencia, de hecho, mapse implementa en C como rb_ary_collecty enum_collect(por ejemplo, hay una diferencia entre mapuna matriz y cualquier otra enumeración, pero no hay diferencia entre mapy collect).


¿Por qué ambos mapy collectexisten en Ruby? La mapfunción tiene muchas convenciones de nomenclatura en diferentes idiomas. Wikipedia proporciona una descripción general :

La función de mapa se originó en lenguajes de programación funcionales, pero hoy en día también es compatible (o puede definirse) en muchos lenguajes de procedimientos, orientados a objetos y multi-paradigmas: en la Biblioteca de plantillas estándar de C ++, se llama transform, en C # (3.0) Biblioteca LINQ, se proporciona como un método de extensión llamado Select. Map también es una operación de uso frecuente en lenguajes de alto nivel como Perl, Python y Ruby; La operación se llama mapen los tres idiomas. También se proporciona un collectalias para el mapa en Ruby (de Smalltalk) [énfasis mío]. Common Lisp proporciona una familia de funciones tipo mapa; el que corresponde al comportamiento descrito aquí se llama mapcar(-car que indica acceso usando la operación CAR).

Ruby proporciona un alias para que los programadores del mundo Smalltalk se sientan más en casa.


¿Por qué hay una implementación diferente para matrices y enumeraciones? Una enumeración es una estructura de iteración generalizada, lo que significa que no hay forma de que Ruby pueda predecir cuál puede ser el siguiente elemento (puede definir enumeraciones infinitas, consulte Prime para ver un ejemplo). Por lo tanto, debe llamar a una función para obtener cada elemento sucesivo (generalmente este será el eachmétodo).

Las matrices son la colección más común, por lo que es razonable optimizar su rendimiento. Dado que Ruby sabe mucho sobre cómo funcionan las matrices, no tiene que llamar, eachpero solo puede usar la manipulación de puntero simple, que es significativamente más rápido.

Existen optimizaciones similares para varios métodos de matriz como zipo count.

Jakub Hampl
fuente
14
@ Mark Reed pero luego, los programadores que no provengan de SmallTalk se confundirían al tener dos funciones diferentes que resultan ser solo alias. Provoca preguntas como la OP anterior.
SasQ
11
@SasQ No estoy en desacuerdo: creo que sería mejor en general si hubiera un solo nombre. Pero hay muchos otros alias en Ruby, y una característica del alias es que hay un buen paralelismo de nombres entre las operaciones de recopilar , detectar , inyectar , rechazar y seleccionar (también conocido como mapa , buscar , reducir , rechazar (sin alias ) y find_all ).
Mark Reed
55
En efecto. Aparentemente, Ruby está usando alias / sinónimos en más ocasiones. Por ejemplo, el número de elementos de una matriz puede ser recuperada con count, lengtho size. Diferentes palabras para el mismo atributo de una matriz, pero con esto, Ruby le permite elegir la palabra más apropiada para su código: ¿desea la cantidad de elementos que está recopilando, la longitud de una matriz o el tamaño actual de la estructura. Esencialmente, son todos iguales, pero elegir la palabra correcta puede hacer que su código sea más fácil de leer, lo cual es una buena propiedad del lenguaje.
Jochem Schulenklopper
52

Me han dicho que son lo mismo.

En realidad, están documentados en el mismo lugar en ruby-doc.org:

http://www.ruby-doc.org/core/classes/Array.html#M000249

  • ary.collect {| item | bloque} → new_ary
  • ary.map {| item | bloque} → new_ary
  • ary.collect → un_enumerador
  • ary.map → un_enumerador

Invoca bloque una vez para cada elemento de uno mismo. Crea una nueva matriz que contiene los valores devueltos por el bloque. Ver también Enumerable # collect.
Si no se proporciona ningún bloque, se devuelve un enumerador.

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
a                          #=> ["a", "b", "c", "d"]
OscarRyz
fuente
2
Solo para ser exhaustivo: http://www.ruby-doc.org/core/classes/Enumerable.html#method-i-map
Andre Helberg
14

Hice una prueba de referencia para intentar responder esta pregunta, luego encontré esta publicación, así que aquí están mis hallazgos (que difieren ligeramente de las otras respuestas)

Aquí está el código de referencia:

require 'benchmark'

h = { abc: 'hello', 'another_key' => 123, 4567 => 'third' }
a = 1..10
many = 500_000

Benchmark.bm do |b|
  GC.start

  b.report("hash keys collect") do
    many.times do
      h.keys.collect(&:to_s)
    end
  end

  GC.start

  b.report("hash keys map") do
    many.times do
      h.keys.map(&:to_s)
    end
  end

  GC.start

  b.report("array collect") do
    many.times do
      a.collect(&:to_s)
    end
  end

  GC.start

  b.report("array map") do
    many.times do
      a.map(&:to_s)
    end
  end
end

Y los resultados que obtuve fueron:

                   user     system      total        real
hash keys collect  0.540000   0.000000   0.540000 (  0.570994)
hash keys map      0.500000   0.010000   0.510000 (  0.517126)
array collect      1.670000   0.020000   1.690000 (  1.731233)
array map          1.680000   0.020000   1.700000 (  1.744398) 

¿Quizás un alias no es gratis?

ktec
fuente
1
No estoy seguro de si estas diferencias son significativas. En una repetición obtengo resultados diferentes en la velocidad (incluso mientras su hash collect parece más lento, su array collect parece más rápido)
murb
11

Los métodos collecty collect!son alias de mapy map!, por lo que se pueden usar indistintamente. Aquí hay una manera fácil de confirmar eso:

Array.instance_method(:map) == Array.instance_method(:collect)
 => true
BrunoFacca
fuente
8

Ruby alias el método Array # map to Array # collect; Se pueden usar indistintamente. (Monje rubí)

En otras palabras, el mismo código fuente:

               static VALUE
rb_ary_collect(VALUE ary)
{
long i;
VALUE collect;

RETURN_SIZED_ENUMERATOR(ary, 0, 0, ary_enum_length);
collect = rb_ary_new2(RARRAY_LEN(ary));
for (i = 0; i < RARRAY_LEN(ary); i++) {
    rb_ary_push(collect, rb_yield(RARRAY_AREF(ary, i)));
}
return collect;
}

http://ruby-doc.org/core-2.2.0/Array.html#method-i-map

jeton
fuente
44
Deseo que la documentación declare explícitamente que son alias. Por el momento, simplemente se refieren entre sí, y ambos tienen descripciones ligeramente diferentes.
Chris Bloom