Differenza tra mappa e raccolta in Ruby?


428

Ho cercato su Google questo e ho avuto opinioni discordanti / contraddittorie - c'è davvero differenza tra fare un mape fare collectun array su Ruby / Rails?

I documenti non sembrano suggerire alcuno, ma ci sono forse differenze nel metodo o nelle prestazioni?


5
mapè preferito a Code Golf .
Cary Swoveland,

1
Come spiegazione del perché mapè preferito in CodeGolf, che potrebbe non essere ovvio per tutti: è solo perché collectè più lungo di quattro caratteri map, ma uguale per funzionalità.
Jochem Schulenklopper,

2
Solo per interpretare l'avvocato del diavolo, trovo personalmente collectpiù leggibile e naturale: l'idea di "collezionare" dischi e fare X per loro ha più senso naturale di "mappare" i dischi e fare X per loro.
sscirrus,

Risposte:


480

Non c'è differenza, infatti mapè implementato in C come rb_ary_collecte enum_collect(es. C'è una differenza tra mapsu un array e su qualsiasi altro enum, ma nessuna differenza tra mape collect).


Perché entrambi maped collectesistono in Ruby? La mapfunzione ha molte convenzioni di denominazione in diverse lingue. Wikipedia fornisce una panoramica :

La funzione di mappa ha avuto origine in linguaggi di programmazione funzionale ma oggi è supportata (o può essere definita) anche in molti linguaggi procedurali, orientati agli oggetti e multi-paradigma: nella libreria di modelli standard di C ++, è chiamata transform, in C # (3.0) Libreria LINQ, viene fornita come metodo di estensione chiamato Select. Map è anche un'operazione di uso frequente in linguaggi di alto livello come Perl, Python e Ruby; l'operazione viene chiamata mapin tutte e tre queste lingue. Un collectalias per la mappa è fornito anche in Ruby (da Smalltalk) [enfasi mia]. Common Lisp fornisce una famiglia di funzioni simili a mappe; viene chiamato quello corrispondente al comportamento qui descritto mapcar(-car che indica l'accesso usando l'operazione CAR).

Ruby fornisce un alias ai programmatori del mondo Smalltalk per sentirsi più a casa.


Perché esiste un'implementazione diversa per array ed enum? Un enum è una struttura di iterazione generalizzata, il che significa che non c'è modo in cui Ruby possa prevedere quale possa essere il prossimo elemento (puoi definire enumerazioni infinite, vedi Prime per un esempio). Pertanto deve chiamare una funzione per ottenere ogni elemento successivo (in genere questo sarà il eachmetodo).

Gli array sono la raccolta più comune, quindi è ragionevole ottimizzarne le prestazioni. Dato che Ruby sa molto su come funzionano le matrici, non deve chiamare eachma può usare solo una semplice manipolazione del puntatore che è significativamente più veloce.

Ottimizzazioni simili esistono per numerosi metodi di array come zipo count.


14
@Mark Reed, ma poi, i programmatori che non provengono da SmallTalk sarebbero confusi avendo due diverse funzioni che si rivelano essere solo alias. Causa domande come quella OP sopra.
SasQ,

11
@SasQ Non sono in disaccordo - penso che nel complesso sarebbe meglio se ci fosse un solo nome. Ma ci sono molti altri alias in Ruby e una caratteristica dell'aliasing è che c'è un bel parallelo di denominazione tra le operazioni di raccolta , rilevamento , iniezione , rifiuto e selezione (altrimenti noto come mappa , trova , riduci , rifiuta (nessun alias ) e find_all ).
Mark Reed,

5
Infatti. Apparentemente, Ruby sta usando alias / sinonimi in più occasioni. Ad esempio, il numero di elementi di una matrice può essere recuperato con count, lengtho size. Parole diverse per lo stesso attributo di un array, ma in questo modo, Ruby ti consente di scegliere la parola più appropriata per il tuo codice: vuoi il numero di oggetti che stai collezionando, la lunghezza di un array o la dimensione attuale di la struttura. In sostanza, sono tutti uguali, ma scegliere la parola giusta potrebbe rendere più facile la lettura del codice, che è una bella proprietà della lingua.
Jochem Schulenklopper,

52

Mi è stato detto che sono uguali.

In realtà sono documentati nello stesso posto sotto ruby-doc.org:

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

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

Invoca il blocco una volta per ogni elemento di sé. Crea un nuovo array contenente i valori restituiti dal blocco. Vedi anche Enumerable # collect.
Se non viene fornito alcun blocco, viene invece restituito un enumeratore.

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


14

Ho fatto un test di riferimento per provare a rispondere a questa domanda, quindi ho trovato questo post, quindi ecco i miei risultati (che differiscono leggermente dalle altre risposte)

Ecco il codice di riferimento:

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 i risultati che ho ottenuto sono stati:

                   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) 

Forse un alias non è gratuito?


1
Non sono sicuro che queste differenze siano significative. Su una ripetizione ottengo risultati diversi in termini di velocità (anche se la raccolta dell'hash sembra più lenta, la raccolta dell'array sembra più veloce)
murb

11

I metodi collecte collect!sono alias di mape map!, quindi possono essere usati in modo intercambiabile. Ecco un modo semplice per confermare che:

Array.instance_method(:map) == Array.instance_method(:collect)
 => true

8

Ruby alias il metodo Array # map to Array # collect; possono essere usati in modo intercambiabile. (Ruby Monk)

In altre parole, lo stesso codice sorgente:

               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


4
Vorrei che la documentazione affermasse esplicitamente che sono alias. Al momento si riferiscono semplicemente a vicenda ed entrambi hanno descrizioni leggermente diverse.
Chris Bloom,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.