Разница между картой и коллекцией в Ruby?

428

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

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

sscirrus
источник
5
mapявляется предпочтительным в Code Golf .
Кэри Свовеланд,
1
В качестве объяснения того, почему mapпредпочтение отдается CodeGolf, что может быть неочевидно для всех: это только потому, что collectон на четыре символа длиннее map, но по функциональности одинаков.
Йохем Шуленклоппер
2
Просто для того, чтобы сыграть роль адвоката дьявола, я лично нахожу collectболее читабельным и естественным - идея «собирать» записи и делать X для них имеет для меня более естественный смысл, чем «отображать» записи и делать X для них.
sscirrus

Ответы:

480

Там нет никакой разницы, на самом деле mapреализовано в C как rb_ary_collectи enum_collect(например, есть разница между mapмассивом и любым другим перечислением, но нет разницы между mapи collect).


Почему оба mapи collectсуществуют в Ruby? mapФункция имеет много соглашений об именах на разных языках. Википедия предоставляет обзор :

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

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


Почему существует другая реализация для массивов и перечислений? Перечисление - это обобщенная итерационная структура, которая означает, что Ruby не может предсказать, каким может быть следующий элемент (вы можете определить бесконечные перечисления, см. Пример Prime ). Поэтому он должен вызывать функцию для получения каждого последующего элемента (обычно это будет eachметод).

Массивы являются наиболее распространенной коллекцией, поэтому целесообразно оптимизировать их производительность. Поскольку Ruby знает много о том, как работают массивы, ему не нужно вызывать, eachа можно использовать только простое манипулирование указателем, что значительно быстрее.

Подобные оптимизации существуют для ряда методов Array, таких как zipor count.

Якуб Хэмпл
источник
14
@Mark Reed, но программисты, не работающие с SmallTalk, будут сбиты с толку тем, что две разные функции окажутся просто псевдонимами. Это вызывает такие вопросы, как OP выше.
SasQ
11
@SasQ Я не согласен - я думаю, что было бы лучше, если бы было только одно имя. Но в Ruby существует множество других псевдонимов, и одна из особенностей псевдонимов заключается в том, что существует хорошая параллель именования между операциями сбора , обнаружения , внедрения , отклонения и выбора (иначе называемыми сопоставлением , поиском , уменьшением , отклонением (без псевдонимов). ) и find_all ).
Марк Рид
5
Верно. Очевидно, Ruby чаще использует псевдонимы / синонимы. Например, количество элементов в массиве может быть получено с count, lengthили size. Различные слова для того же атрибут массива, но при этом, Ruby позволяет выбрать наиболее подходящее слово для вашего кода: вы хотите , количество пунктов , которые Вы собираете, то длина массива, или текущего размера от структура. По сути, они все одинаковые, но выбор правильного слова может облегчить чтение вашего кода, что является хорошим свойством языка.
Йохем Шуленклоппер
52

Мне сказали, что они одинаковы.

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

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

  • ary.collect {| item | блок} → new_ary
  • ary.map {| item | блок} → new_ary
  • ary.collect → an_enumerator
  • ary.map → an_enumerator

Вызывает блок один раз для каждого элемента себя. Создает новый массив, содержащий значения, возвращаемые блоком. Смотрите также Enumerable # collect.
Если блок не задан, вместо него возвращается перечислитель.

a = [ "a", "b", "c", "d" ]
a.collect {|x| x + "!" }   #=> ["a!", "b!", "c!", "d!"]
a                          #=> ["a", "b", "c", "d"]
OscarRyz
источник
2
Просто чтобы быть тщательным: http://www.ruby-doc.org/core/classes/Enumerable.html#method-i-map
Андре Хельберг
14

Я выполнил тест теста, чтобы попытаться ответить на этот вопрос, затем нашел этот пост, так что вот мои выводы (которые немного отличаются от других ответов)

Вот эталонный код:

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) 

Возможно, псевдоним не бесплатный?

KTEC
источник
1
Я не уверен, значимы ли эти различия. При повторном запуске я получаю разные результаты по скорости (даже если сбор хеша кажется медленнее, сбор массива кажется быстрее)
Мурб
11

collectИ collect!методы псевдонимы mapи map!, таким образом , они могут быть использованы как взаимозаменяемые. Вот простой способ подтвердить это:

Array.instance_method(:map) == Array.instance_method(:collect)
 => true
BrunoFacca
источник
8

Ruby псевдоним метода Array # map для Array # collect; они могут использоваться взаимозаменяемо. (Руби Монах)

Другими словами, тот же исходный код:

               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
источник
4
Я бы хотел, чтобы в документации было указано, что это псевдонимы. На данный момент они просто ссылаются друг на друга, и оба имеют немного разные описания.
Крис Блум