Różnica między mapą a kolekcją w Ruby?

427

Przejrzałem to i otrzymałem niejednoznaczne / sprzeczne opinie - czy faktycznie jest jakaś różnica między robieniem mapa robieniem collectna tablicy w Ruby / Rails?

W docs nie wydają się sugerować, istnieją, ale są tam może różnice w sposobie lub wydajności?

sscirrus
źródło
5
mapjest preferowany w Code Golf .
Cary Swoveland,
1
Jako wyjaśnienie, dlaczego mapjest preferowany w CodeGolf, co może nie być oczywiste dla wszystkich: dzieje się tak tylko dlatego, że collectcztery znaki są dłuższe niż map, ale funkcjonalne.
Jochem Schulenklopper
2
Po prostu jako adwokat diabła, osobiście uważam, że jest collectbardziej czytelny i naturalny - pomysł „gromadzenia” nagrań i robienia z nimi X ma dla mnie bardziej naturalny sens niż „mapowanie” nagrań i robienie z nimi X.
sscirrus

Odpowiedzi:

479

Nie ma różnicy, w rzeczywistości mapjest zaimplementowane w C jako rb_ary_collecti enum_collect(np. Istnieje różnica między maptablicą a dowolnym innym wyliczeniem, ale nie ma różnicy między mapi collect).


Dlaczego oba mapi collectistnieją w Ruby? mapFunkcja ma wiele nazewnictwa w różnych językach. Wikipedia zawiera przegląd :

Funkcja map pochodzi z funkcjonalnych języków programowania, ale obecnie jest obsługiwana (lub może być zdefiniowana) również w wielu językach proceduralnych, obiektowych i wieloparadygmatycznych: w standardowej bibliotece szablonów C ++ jest ona nazywana transformw języku C # (3.0) Biblioteka LINQ jest dostarczana jako metoda rozszerzenia o nazwie Select. Mapa jest również często używaną operacją w językach wysokiego poziomu, takich jak Perl, Python i Ruby; operacja jest wywoływana mapwe wszystkich trzech tych językach. Alias mapie jest również Ruby (z Smalltalku) [nacisk kopalni]. Common Lisp zapewnia rodzinę funkcji podobnych do mapy; to odpowiadające opisanemu tutaj zachowaniu nazywa się (-car wskazuje dostęp za pomocą operacji CAR).collectmapcar

Ruby zapewnia alias dla programistów ze świata Smalltalk, aby czuli się jak w domu.


Dlaczego istnieje inna implementacja tablic i wyliczeń? Wyliczenie jest uogólnioną strukturą iteracyjną, co oznacza, że ​​Ruby nie jest w stanie przewidzieć, jaki może być następny element (możesz zdefiniować nieskończone wyliczenia, patrz przykład Prime ). Dlatego musi wywoływać funkcję, aby uzyskać każdy kolejny element (zazwyczaj będzie to eachmetoda).

Tablice są najczęstszą kolekcją, więc rozsądne jest zoptymalizowanie ich wydajności. Ponieważ Ruby wie dużo o tym, jak działają tablice, nie musi wywoływać, eachale może używać tylko prostej manipulacji wskaźnikiem, która jest znacznie szybsza.

Podobne optymalizacje istnieją dla wielu metod Array, takich jak ziplub count.

Jakub Hampl
źródło
13
@ Mark Reed, ale wtedy programiści nie pochodzący z SmallTalk byliby skonfundowani przez posiadanie dwóch różnych funkcji, które okazałyby się tylko aliasami. Powoduje to pytania takie jak PO powyżej.
SasQ
10
@SasQ Nie zgadzam się - myślę, że ogólnie byłoby lepiej, gdyby istniało tylko jedno nazwisko. Ale istnieje wiele innych aliasów w Rubim, a jedną z cech aliasingu jest to, że istnieje ładna równoległa nazwa między operacjami zbierania , wykrywania , wstrzykiwania , odrzucania i wybierania (inaczej znana jako mapa , znajdowanie , zmniejszanie , odrzucanie (brak aliasu) ) i find_all ).
Mark Reed,
4
W rzeczy samej. Najwyraźniej Ruby częściej używa aliasów / synonimów. Na przykład, liczba elementów w tablicy mogą być pobrane z count, lengthlub size. Różne słowa dla tego samego atrybutu tablicy, ale przez to, Ruby pozwala wybrać najbardziej odpowiednie słowo w kodzie: chcesz się szereg elementów jesteś zbieranie The długość tablicy, czy obecna wielkość od struktura. Zasadniczo wszystkie są takie same, ale wybranie odpowiedniego słowa może ułatwić odczytanie kodu, co jest przyjemną właściwością języka.
Jochem Schulenklopper 10.10.16
51

Powiedziano mi, że są takie same.

W rzeczywistości są one udokumentowane w tym samym miejscu pod ruby-doc.org:

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

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

Wywołuje blok raz dla każdego elementu jaźni. Tworzy nową tablicę zawierającą wartości zwrócone przez blok. Zobacz także Enumerable # collect.
Jeśli nie podano żadnego bloku, zamiast tego zwracany jest moduł wyliczający.

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
a                          #=> ["a", "b", "c", "d"]
OscarRyz
źródło
13

Zrobiłem test porównawczy, aby spróbować odpowiedzieć na to pytanie, a następnie znalazłem ten post, więc oto moje ustalenia (które różnią się nieznacznie od innych odpowiedzi)

Oto kod testu porównawczego:

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

A wyniki, które uzyskałem były:

                   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) 

Być może alias nie jest bezpłatny?

ktec
źródło
1
Nie jestem pewien, czy różnice te są znaczące. Przy
ponownym uruchomieniu uzyskuję
10

collectI collect!metody są aliasy mapi map!, więc mogą być używane zamiennie. Oto prosty sposób, aby potwierdzić, że:

Array.instance_method(:map) == Array.instance_method(:collect)
 => true
BrunoFacca
źródło
7

Ruby aliasuje metodę Array # map do Array # collect; mogą być używane zamiennie. (Ruby Monk)

Innymi słowy, ten sam kod źródłowy:

               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
źródło
4
Chciałbym, aby dokumentacja wyraźnie określała, że ​​są to pseudonimy. W tej chwili po prostu się odwołują i oba mają nieco inne opisy.
Chris Bloom