Différence entre carte et collectionner en Ruby?

428

J'ai googlé ceci et obtenu des opinions inégales / contradictoires - y a-t-il réellement une différence entre faire un mapet faire un collectsur un tableau dans Ruby / Rails?

Les documents ne semblent pas en suggérer, mais y a-t-il peut-être des différences de méthode ou de performances?

sscirrus
la source
5
mapest préféré au Code Golf .
Cary Swoveland
1
Pour expliquer pourquoi mapest préféré chez CodeGolf, ce qui pourrait ne pas être évident pour tous: c'est seulement parce qu'il collectfait quatre caractères de plus que map, mais la même fonctionnalité.
Jochem Schulenklopper
2
Juste pour jouer l'avocat du diable, je trouve personnellement collectplus lisible et naturel - l'idée de `` collecter '' des enregistrements et de leur faire X est plus logique pour moi que de `` mapper '' des enregistrements et de leur faire X.
sscirrus

Réponses:

480

Il n'y a pas de différence, en fait mapest implémenté en C au fur rb_ary_collectet à mesure enum_collect(par exemple, il y a une différence entre mapsur un tableau et sur tout autre énumération, mais aucune différence entre mapet collect).


Pourquoi les deux mapet collectexistent-ils en Ruby? La mapfonction possède de nombreuses conventions de dénomination dans différentes langues. Wikipedia donne un aperçu :

La fonction de cartographie est originaire de langages de programmation fonctionnels mais est aujourd'hui prise en charge (ou peut être définie) dans de nombreux langages procéduraux, orientés objet et multi-paradigmes: dans la bibliothèque de modèles standard de C ++, elle est appelée transform, en C # (3.0). Bibliothèque LINQ, elle est fournie comme une méthode d'extension appelée Select. La carte est également une opération fréquemment utilisée dans les langages de haut niveau tels que Perl, Python et Ruby; l'opération est appelée mapdans ces trois langues. Un collectalias pour la carte est également fourni dans Ruby (de Smalltalk) [emphase sur moi]. Common Lisp fournit une famille de fonctions de type carte; celui correspondant au comportement décrit ici est appelé mapcar(-car indiquant l'accès via l'opération CAR).

Ruby fournit un alias pour que les programmeurs du monde Smalltalk se sentent plus à l'aise.


Pourquoi existe-t-il une implémentation différente pour les tableaux et les énumérations? Une énumération est une structure d'itération généralisée, ce qui signifie qu'il n'y a aucun moyen pour Ruby de prédire ce que peut être l'élément suivant (vous pouvez définir des énumérations infinies, voir Prime pour un exemple). Il doit donc appeler une fonction pour obtenir chaque élément successif (ce sera généralement la eachméthode).

Les tableaux sont la collection la plus courante, il est donc raisonnable d'optimiser leurs performances. Étant donné que Ruby sait beaucoup de choses sur le fonctionnement des tableaux, il n'a pas à appeler, eachmais ne peut utiliser qu'une manipulation de pointeur simple, ce qui est beaucoup plus rapide.

Des optimisations similaires existent pour un certain nombre de méthodes de tableau comme zipou count.

Jakub Hampl
la source
14
@Mark Reed mais alors, les programmeurs ne venant pas de SmallTalk seraient confus en ayant deux fonctions différentes qui s'avèrent être juste des alias. Cela provoque des questions comme l'OP ci-dessus.
SasQ
11
@SasQ Je ne suis pas en désaccord - je pense que ce serait mieux dans l'ensemble s'il n'y avait qu'un seul nom. Mais il existe de nombreux autres alias dans Ruby, et l'une des caractéristiques de l'alias est qu'il existe un joli parallèle de nommage entre les opérations de collecte , de détection , d' injection , de rejet et de sélection (autrement connu sous le nom de carte , rechercher , réduire , rejeter (pas d'alias) ) et find_all ).
Mark Reed
5
En effet. Apparemment, Ruby utilise plus souvent des alias / synonymes. Par exemple, le nombre d'éléments dans un tableau peut être récupéré avec count, lengthou size. Différents mots pour le même attribut d'un tableau, mais par cela, Ruby vous permet de choisir le mot le plus approprié pour votre code: voulez-vous le nombre d'éléments que vous collectez, la longueur d'un tableau ou la taille actuelle de la structure. Essentiellement, ils sont tous les mêmes, mais choisir le bon mot pourrait rendre votre code plus facile à lire, ce qui est une belle propriété du langage.
Jochem Schulenklopper
52

On m'a dit que ce sont les mêmes.

En fait, ils sont documentés au même endroit sous ruby-doc.org:

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

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

Invoque le bloc une fois pour chaque élément de soi. Crée un nouveau tableau contenant les valeurs renvoyées par le bloc. Voir aussi Enumerable # collect.
Si aucun bloc n'est donné, un énumérateur est renvoyé à la place.

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
a                          #=> ["a", "b", "c", "d"]
OscarRyz
la source
2
Juste pour être complet: http://www.ruby-doc.org/core/classes/Enumerable.html#method-i-map
Andre Helberg
14

J'ai fait un test de référence pour essayer de répondre à cette question, puis j'ai trouvé ce post alors voici mes résultats (qui diffèrent légèrement des autres réponses)

Voici le code de référence:

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

Et les résultats que j'ai obtenus étaient:

                   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) 

Peut-être qu'un alias n'est pas gratuit?

ktec
la source
1
Je ne sais pas si ces différences sont importantes. Lors d'une réexécution, j'obtiens des résultats différents en
termes de
11

Les méthodes collectet collect!sont des alias de mapet map!elles peuvent donc être utilisées de manière interchangeable. Voici un moyen simple de confirmer que:

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

Ruby alias la méthode Array # map to Array # collect; ils peuvent être utilisés de manière interchangeable. (Ruby Monk)

En d'autres termes, même code source:

               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
la source
4
Je souhaite que la documentation indique explicitement qu'il s'agit d'alias. Pour le moment, ils se réfèrent simplement l'un à l'autre, et les deux ont des descriptions légèrement différentes.
Chris Bloom