Unterschied zwischen Karte und Sammeln in Ruby?

426

Ich habe dies gegoogelt und uneinheitliche / widersprüchliche Meinungen erhalten - gibt es tatsächlich einen Unterschied zwischen einem mapund collecteinem Array in Ruby / Rails?

Die Dokumente scheinen keine zu suggerieren, aber gibt es vielleicht Unterschiede in der Methode oder Leistung?

sscirrus
quelle
5
mapwird bei Code Golf bevorzugt .
Cary Swoveland
1
Als Erklärung dafür, warum mapbei CodeGolf bevorzugt wird, was möglicherweise nicht für alle offensichtlich ist: Nur weil collectvier Zeichen länger sind als map, aber in der Funktionalität gleich.
Jochem Schulenklopper
2
Nur um Devil's Advocate zu spielen, finde ich persönlich collectlesbarer und natürlicher - die Idee, Datensätze zu sammeln und ihnen X zuzuweisen, ist für mich natürlicher, als Datensätze zuzuordnen und ihnen X zuzuweisen.
sscirrus

Antworten:

479

Es gibt keinen Unterschied, tatsächlich mapist es in C als rb_ary_collectund implementiert enum_collect(z. B. gibt es einen Unterschied zwischen mapeinem Array und einer anderen Aufzählung, aber keinen Unterschied zwischen mapund collect).


Warum beides mapund collectin Ruby existieren? Die mapFunktion hat viele Namenskonventionen in verschiedenen Sprachen. Wikipedia bietet einen Überblick :

Die Kartenfunktion stammt ursprünglich aus funktionalen Programmiersprachen, wird heute jedoch auch in vielen prozeduralen, objektorientierten und multiparadigmatischen Sprachen unterstützt (oder kann definiert werden): In der Standardvorlagenbibliothek von C ++ wird sie transformin C # (3.0) genannt LINQ-Bibliothek wird als Erweiterungsmethode bereitgestellt Select. Map ist auch eine häufig verwendete Operation in Hochsprachen wie Perl, Python und Ruby. Die Operation wird mapin allen drei Sprachen aufgerufen . Ein collectAlias ​​für die Karte ist auch in Ruby (von Smalltalk) [Hervorhebung von mir] enthalten. Common Lisp bietet eine Familie von kartenähnlichen Funktionen. mapcarDasjenige, das dem hier beschriebenen Verhalten entspricht, heißt (-car, das den Zugriff mit der CAR-Operation anzeigt).

Ruby bietet Programmierern aus der Smalltalk-Welt einen Alias, um sich besser zu Hause zu fühlen.


Warum gibt es eine andere Implementierung für Arrays und Enums? Eine Aufzählung ist eine verallgemeinerte Iterationsstruktur, was bedeutet, dass Ruby auf keinen Fall vorhersagen kann, was das nächste Element sein kann (Sie können unendliche Aufzählungen definieren, siehe Prime für ein Beispiel). Daher muss eine Funktion aufgerufen werden, um jedes aufeinanderfolgende Element abzurufen (normalerweise ist dies die eachMethode).

Arrays sind die am häufigsten verwendete Sammlung, daher ist es sinnvoll, ihre Leistung zu optimieren. Da Ruby viel über die Funktionsweise von Arrays weiß, muss es nicht aufrufen, eachsondern kann nur eine einfache Zeigermanipulation verwenden, die erheblich schneller ist.

Ähnliche Optimierungen gibt es für eine Reihe von Array-Methoden wie zipoder count.

Jakub Hampl
quelle
13
@ Mark Reed, aber dann wären Programmierer, die nicht von SmallTalk kommen, verwirrt, wenn sie zwei verschiedene Funktionen hätten, die sich nur als Aliase herausstellen. Es verursacht Fragen wie die OP oben.
SasQ
10
@ SasQ Ich bin nicht anderer Meinung - ich denke, es wäre insgesamt besser, wenn es nur einen Namen gäbe. Es gibt jedoch viele andere Aliase in Ruby, und ein Merkmal des Aliasing ist, dass es eine nette Namensparallele zwischen den Operationen Sammeln , Erkennen , Injizieren , Zurückweisen und Auswählen gibt (auch bekannt als Zuordnen , Suchen , Reduzieren , Zurückweisen (kein Alias) ) und find_all ).
Mark Reed
4
Tatsächlich. Anscheinend verwendet Ruby häufiger Aliase / Synonyme. Beispielsweise kann die Anzahl der Elemente in einem Array mit abgerufen werden count, lengthoder size. Verschiedene Wörter für dasselbe Attribut eines Arrays. Auf diese Weise können Sie mit Ruby das am besten geeignete Wort für Ihren Code auswählen: Möchten Sie die Anzahl der Elemente, die Sie sammeln, die Länge eines Arrays oder die aktuelle Größe von die Struktur. Im Wesentlichen sind sie alle gleich, aber wenn Sie das richtige Wort auswählen, ist Ihr Code möglicherweise leichter zu lesen, was eine nette Eigenschaft der Sprache ist.
Jochem Schulenklopper
51

Mir wurde gesagt, dass sie gleich sind.

Tatsächlich sind sie an derselben Stelle unter ruby-doc.org dokumentiert:

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

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

Ruft den Block einmal für jedes Element des Selbst auf. Erstellt ein neues Array mit den vom Block zurückgegebenen Werten. Siehe auch Enumerable # collect.
Wenn kein Block angegeben ist, wird stattdessen ein Enumerator zurückgegeben.

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
a                          #=> ["a", "b", "c", "d"]
OscarRyz
quelle
2
Nur um gründlich zu sein: http://www.ruby-doc.org/core/classes/Enumerable.html#method-i-map
Andre Helberg
13

Ich habe einen Benchmark-Test durchgeführt, um diese Frage zu beantworten, und dann diesen Beitrag gefunden. Hier sind meine Ergebnisse (die sich geringfügig von den anderen Antworten unterscheiden).

Hier ist der Benchmark-Code:

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

Und die Ergebnisse, die ich bekam, waren:

                   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) 

Vielleicht ist ein Alias ​​nicht frei?

ktec
quelle
1
Ich bin mir nicht sicher, ob diese Unterschiede signifikant sind. Bei einer
erneuten Ausführung erhalte
10

Die Methoden collectund collect!sind Aliase für mapund map!, sodass sie austauschbar verwendet werden können. Hier ist eine einfache Möglichkeit, dies zu bestätigen:

Array.instance_method(:map) == Array.instance_method(:collect)
 => true
BrunoFacca
quelle
7

Ruby aliasisiert die Methode Array # map to Array # collect. Sie können austauschbar verwendet werden. (Rubinmönch)

Mit anderen Worten, der gleiche Quellcode:

               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
quelle
4
Ich wünsche mir, dass in der Dokumentation ausdrücklich angegeben wird, dass es sich um Aliase handelt. Im Moment verweisen sie einfach aufeinander und beide haben leicht unterschiedliche Beschreibungen.
Chris Bloom