Diferença entre mapa e coleta em Ruby?

428

Eu pesquisei isso no Google e obtive opiniões irregulares / contraditórias - existe realmente alguma diferença entre fazer um mape fazer um collectem uma matriz no Ruby / Rails?

Os documentos parecem não sugerir nenhum, mas talvez haja diferenças de método ou desempenho?

sscirrus
fonte
5
mapé preferido no Code Golf .
Cary Swoveland
1
Como uma explicação do porquê mapé preferível no CodeGolf, o que pode não ser óbvio para todos: é apenas porque collecttem quatro caracteres a mais map, mas o mesmo em funcionalidade.
Jochem Schulenklopper 10/10
2
Apenas para bancar o advogado do diabo, eu pessoalmente acho collectmais legível e natural - a idéia de 'coletar' registros e fazer X com eles faz mais sentido para mim do que 'mapear' registros e fazer X com eles.
Sscirrus 3/10

Respostas:

480

Não há diferença, de fato mapé implementado em C como rb_ary_collecte enum_collect(por exemplo, há uma diferença entre mapem uma matriz e em qualquer outra enumeração, mas não há diferença entre mape collect).


Por que ambos mape collectexistem no Ruby? A mapfunção possui muitas convenções de nomenclatura em diferentes idiomas. A Wikipedia fornece uma visão geral :

A função map originou-se em linguagens de programação funcional, mas hoje é suportada (ou pode ser definida) em muitas linguagens procedurais, orientadas a objetos e com vários paradigmas: Na Biblioteca de Modelos Padrão do C ++, ela é chamada transform, em C # (3.0), Biblioteca LINQ, é fornecido como um método de extensão chamado Select. Map também é uma operação frequentemente usada em linguagens de alto nível, como Perl, Python e Ruby; a operação é chamada mapnos três idiomas. Um collectalias para o mapa também é fornecido em Ruby (do Smalltalk) [ênfase meu]. O Lisp comum fornece uma família de funções semelhantes a mapas; o que corresponde ao comportamento descrito aqui é chamado mapcar(-car indicando acesso usando a operação CAR).

O Ruby fornece um alias para os programadores do mundo Smalltalk se sentirem mais em casa.


Por que existe uma implementação diferente para matrizes e enumerações? Uma enumeração é uma estrutura de iteração generalizada, o que significa que não há como Ruby prever o que o próximo elemento pode ser (você pode definir enumerações infinitas, consulte Prime, por exemplo). Portanto, ele deve chamar uma função para obter cada elemento sucessivo (normalmente esse será o eachmétodo).

Matrizes são a coleção mais comum, portanto, é razoável otimizar seu desempenho. Como Ruby sabe muito sobre como as matrizes funcionam, não precisa chamar, eachmas pode usar apenas a manipulação simples de ponteiros, que é significativamente mais rápida.

Otimizações semelhantes existem para vários métodos de matriz como zipou count.

Jakub Hampl
fonte
14
@ Mark Reed, mas os programadores que não são do SmallTalk ficariam confusos por terem duas funções diferentes que acabam sendo apenas aliases. Isso causa perguntas como a do OP acima.
SasQ
11
@ SasQ Não discordo - acho que seria melhor se houvesse apenas um nome. Mas há uma abundância de outros aliases em Ruby, e uma característica do aliasing é que há uma boa nomeação paralelo entre as operações de coleta , detectar , injetar , rejeitar e selecionar (também conhecido como mapa , encontrar , reduzir , rejeitar (nenhum alias ) e find_all ).
Mark Reed
5
De fato. Aparentemente, Ruby está usando aliases / sinônimos em mais ocasiões. Por exemplo, o número de elementos de uma matriz pode ser obtida com count, lengthou size. Palavras diferentes para o mesmo atributo de uma matriz, mas, com isso, Ruby permite que você escolha a palavra mais apropriada para o seu código: deseja o número de itens que está coletando, o comprimento de uma matriz ou o tamanho atual de a estrutura. Essencialmente, eles são todos iguais, mas escolher a palavra certa pode facilitar a leitura do código, o que é uma boa propriedade do idioma.
Jochem Schulenklopper 10/10
52

Disseram-me que são iguais.

Na verdade, eles estão documentados no mesmo local em ruby-doc.org:

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

  • ary.collect {| item | bloco} → new_ary
  • ary.map {| item | bloco} → new_ary
  • ary.collect → an_enumerator
  • ary.map → an_enumerator

Invoca o bloco uma vez para cada elemento do eu. Cria uma nova matriz contendo os valores retornados pelo bloco. Veja também Enumerable # collect.
Se nenhum bloco for fornecido, um enumerador será retornado.

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
a                          #=> ["a", "b", "c", "d"]
OscarRyz
fonte
14

Fiz um teste de benchmark para tentar responder a essa pergunta e, em seguida, encontrei este post. Aqui estão minhas descobertas (que diferem um pouco das outras respostas)

Aqui está o código de referência:

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

E os resultados que obtive foram:

                   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) 

Talvez um apelido não seja gratuito?

ktec
fonte
1
Não tenho certeza se essas diferenças são significativas. Em uma reprise eu obter resultados diferentes em velocidade (mesmo quando o hash de coleta parece mais lento, sua matriz parece cobrar mais rápido)
murb
11

Os métodos collecte collect!são aliases para mape map!, portanto, eles podem ser usados ​​de forma intercambiável. Aqui está uma maneira fácil de confirmar que:

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

Ruby usa o método Array # map para Array # collect; eles podem ser usados ​​de forma intercambiável. (Ruby Monk)

Em outras palavras, o mesmo código fonte:

               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
fonte
4
Desejo que a documentação declare explicitamente que são aliases. No momento, eles simplesmente se referem e ambos têm descrições ligeiramente diferentes.
31515 Chris Bloom