Різниця між картою та збиранням у Ruby?


427

Я переглянув це і отримав невідповідні / суперечливі думки - чи є насправді якась різниця між тим, як mapробити та робити collectна масиві в Ruby / Rails?

У документах , здається, не передбачають будь - яких, але є , можливо , відмінність в методі або продуктивності?


5
mapє кращим у Code Golf .
Cary Swoveland

1
Як пояснення того, чому mapвіддається перевага в CodeGolf, що може бути не очевидним для всіх: це лише тому collect, що на чотири символи довше map, але однаково у функціональності.
Йохім Шуленклоппер

2
Тільки для того, щоб грати в захисника диявола, я особисто вважаю collectбільш зрозумілим і природним - ідея "збирати" записи і робити X для них має для мене більш природний сенс, ніж "зіставляти" записи і робити X з ними.
sscirrus

Відповіді:


479

Немає різниці, насправді mapреалізовано в C як rb_ary_collectі enum_collect(наприклад, є різниця між mapмасивом і будь-яким іншим перерахуванням, але різниці між mapі collect).


Чому так mapі collectіснують в Ruby? mapФункція має багато угод про імена на різних мовах. Вікіпедія пропонує огляд :

Функція карти виникла у функціональних мовах програмування, але сьогодні підтримується (або може бути визначена) у багатьох процедурних, об'єктно-орієнтованих та мульти-парадигмах: також у стандартній бібліотеці шаблонів C ++ вона називається transformв C # (3.0) Бібліотека LINQ, вона надається як метод розширення, який називається Select. Карта також є часто використовуваною операцією на мовах високого рівня, таких як Perl, Python та Ruby; операція викликається mapна всіх трьох цих мовах. Ім'я користувача карти також надається в Ruby (від Smalltalk) [курсив мій]. Common Lisp забезпечує сімейство карт-функцій; той, що відповідає поведінці, описаній тут, називається (-кар, що вказує на доступ за допомогою операції CAR).collectmapcar

Ruby надає псевдонім для програмістів зі світу Smalltalk, щоб відчути себе більше як вдома.


Чому існує різна реалізація для масивів та перерахунків? Перерахунок - це узагальнена структура ітерації, що означає, що немає способу, щоб Рубі міг передбачити, яким може бути наступний елемент (можна визначити нескінченні перерахунки, див. Приклад Prime ). Тому необхідно викликати функцію для отримання кожного наступного елемента (зазвичай це буде eachметод).

Масиви - це найпоширеніша колекція, тому розумно оптимізувати їх продуктивність. Оскільки Рубі багато знає про те, як працюють масиви, їй не потрібно дзвонити, eachа може використовувати лише просту маніпуляцію з покажчиком, що значно швидше.

Подібні оптимізації існують для ряду методів Array, таких як zipабо count.


13
@Mark Reed, але тоді, програмісти, які не надходять з SmallTalk, були б заплутані двома різними функціями, які виявляються просто псевдонімами. Це викликає такі питання, як ОП вище.
SasQ

10
@SasQ Я не погоджуюся - думаю, було б краще в цілому, якби було б одне ім’я. Але в Ruby є багато інших псевдонімів, і однією з особливостей псевдоніму є те, що серед операцій збирати , виявляти , вводити , відхиляти та вибирати є приємна паралельна іменування (інакше відома як карта , знайти , зменшити , відхилити (немає псевдоніму). ), і find_all ).
Марк Рід

4
Справді. Мабуть, Рубі більше разів використовує псевдоніми / синоніми. Наприклад, кількість елементів в масиві може бути отримано з count, lengthабо size. Різні слова для того ж атрибут масиву, але при цьому, Ruby дозволяє вибрати найбільш підходяще слово для вашого коду: ви хочете , кількість пунктів , які Ви збираєте, то довжина масиву, або поточного розміру від будова. По суті, вони всі однакові, але вибір правильного слова може полегшити читання вашого коду, що є приємною властивістю мови.
Йохем Шуленклоппер

51

Мені сказали, що вони однакові.

Насправді вони задокументовані там же під ruby-doc.org:

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

  • ary.collect {| пункт | блок} → new_ary
  • ary.map {| пункт | блок} → new_ary
  • ary.collect → an_enumerator
  • ary.map → an_enumerator

Викликає один раз блок для кожного елемента "Я". Створює новий масив, що містить значення, повернені блоком. Дивіться також Численні # збирати.
Якщо блок не вказаний, замість нього повертається нумератор.

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


13

Я зробив тест на тест, щоб спробувати відповісти на це запитання, потім знайшов цю посаду, ось ось мої висновки (які трохи відрізняються від інших відповідей)

Ось базовий код:

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

І отримані результати:

                   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) 

Можливо, псевдонім не безкоштовний?


1
Я не впевнений, чи є ці відмінності суттєвими. Після повторення я отримую різні результати за швидкістю (навіть якщо ваш хеш-збір здається повільнішим, ваш масив збирається швидше)
мерт

10

Методи collectта collect!способи є псевдонімами до, mapі map!тому їх можна використовувати взаємозамінно. Ось простий спосіб підтвердити це:

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

7

Ruby псевдоніми методу Array # map для Array # collection; їх можна використовувати взаємозамінно. (Рубі Монк)

Іншими словами, той самий вихідний код:

               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
Я хочу, щоб документація чітко сказала, що вони псевдоніми. На даний момент вони просто посилаються один на одного, і обидва мають дещо різні описи.
Кріс Блум
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.