Sự khác biệt giữa bản đồ và thu thập trong Ruby?


426

Tôi đã Googled điều này và có ý kiến ​​chắp vá / mâu thuẫn - thực sự có sự khác biệt nào giữa việc thực hiện mapvà thực hiện collecttrên một mảng trong Ruby / Rails không?

Các tài liệu dường như không đề xuất bất kỳ, nhưng có lẽ có sự khác biệt trong phương pháp hoặc hiệu suất?


5
mapđược ưa thích tại Code Golf .
Cary Swoveland 17/2/2016

1
Như một lời giải thích cho lý do tại sao mapđược ưa thích tại CodeGolf, điều này có thể không rõ ràng đối với tất cả: đó chỉ collectlà do có bốn ký tự dài hơn map, nhưng giống nhau về chức năng.
Joool Schulenklopper

2
Chỉ để chơi người ủng hộ của quỷ, cá nhân tôi thấy collectdễ đọc và tự nhiên hơn - ý tưởng 'thu thập' hồ sơ và thực hiện X với chúng có ý nghĩa tự nhiên hơn đối với tôi so với 'lập bản đồ' và thực hiện X với chúng.
sscirrus

Câu trả lời:


479

Không có sự khác biệt, trên thực tế mapđược triển khai trong C như rb_ary_collectenum_collect(ví dụ: có một sự khác biệt giữa maptrên một mảng và trên bất kỳ enum nào khác, nhưng không có sự khác biệt giữa mapcollect).


Tại sao cả hai mapcollecttồn tại trong Ruby? Các mapchức năng có nhiều công ước đặt tên trong ngôn ngữ khác nhau. Wikipedia cung cấp một cái nhìn tổng quan :

Hàm bản đồ có nguồn gốc từ các ngôn ngữ lập trình chức năng nhưng ngày nay được hỗ trợ (hoặc có thể được xác định) bằng nhiều ngôn ngữ theo thủ tục, hướng đối tượng và đa mô hình: Trong Thư viện mẫu chuẩn của C ++, được gọi là transform, trong C # (3.0) Thư viện LINQ, nó được cung cấp như một phương thức mở rộng được gọi là Select. Bản đồ cũng là một hoạt động được sử dụng thường xuyên trong các ngôn ngữ cấp cao như Perl, Python và Ruby; các hoạt động được gọi maptrong cả ba ngôn ngữ này. Một collectbí danh cho bản đồ cũng được cung cấp trong Ruby (từ Smalltalk) [nhấn mạnh của tôi]. Lisp chung cung cấp một nhóm các chức năng giống như bản đồ; hành vi tương ứng với hành vi được mô tả ở đây được gọi là mapcar(-car biểu thị quyền truy cập bằng thao tác CAR).

Ruby cung cấp một bí danh cho các lập trình viên từ thế giới Smalltalk để cảm thấy thoải mái hơn khi ở nhà.


Tại sao có một triển khai khác nhau cho mảng và enum? Một enum là một cấu trúc lặp tổng quát, có nghĩa là không có cách nào mà Ruby có thể dự đoán được phần tử tiếp theo có thể là gì (bạn có thể định nghĩa các enum vô hạn, xem Prime chẳng hạn). Do đó, nó phải gọi một hàm để lấy từng phần tử liên tiếp (thông thường đây sẽ là eachphương thức).

Mảng là bộ sưu tập phổ biến nhất vì vậy rất hợp lý để tối ưu hóa hiệu suất của chúng. Vì Ruby biết rất nhiều về cách các mảng hoạt động, nó không phải gọi eachmà chỉ có thể sử dụng thao tác con trỏ đơn giản , nhanh hơn đáng kể.

Tối ưu hóa tương tự tồn tại cho một số phương thức Array như ziphoặc count.


13
@Mark Reed nhưng sau đó, các lập trình viên không đến từ SmallTalk sẽ bị nhầm lẫn bởi có hai chức năng khác nhau, hóa ra chỉ là bí danh. Nó gây ra những câu hỏi như OP ở trên.
SasQ

10
@SasQ Tôi không đồng ý - Tôi nghĩ rằng sẽ tốt hơn nếu chỉ có một tên. Nhưng có rất nhiều bí danh khác trong Ruby, và một đặc điểm của bí danh là có một cách đặt tên song song giữa các thao tác thu thập , phát hiện , tiêm , từ chốichọn (còn gọi là bản đồ , tìm , giảm , từ chối (không có bí danh ) và find_all ).
Mark Reed

4
Thật. Rõ ràng, Ruby đang sử dụng bí danh / từ đồng nghĩa trong nhiều dịp. Ví dụ, số phần tử trong một mảng có thể được lấy với count, lengthhoặc size. Các từ khác nhau cho cùng một thuộc tính của một mảng, nhưng bằng cách này, Ruby cho phép bạn chọn từ thích hợp nhất cho mã của mình: bạn có muốn số lượng mục bạn đang thu thập, độ dài của một mảng hoặc kích thước hiện tại của cấu trúc. Về cơ bản, tất cả đều giống nhau, nhưng chọn từ đúng có thể giúp mã của bạn dễ đọc hơn, đây là một thuộc tính tốt của ngôn ngữ.
Joool Schulenklopper

51

Tôi đã nói họ giống nhau.

Trên thực tế, chúng được ghi lại ở cùng một nơi theo ruby-doc.org:

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

  • ary.collect {| mục | khối} → new_ary
  • ary.map {| mục | khối} → new_ary
  • ary.collect → an_enumerator
  • ary.map → an_enumerator

Gọi khối một lần cho mỗi yếu tố của bản thân. Tạo một mảng mới chứa các giá trị được trả về bởi khối. Xem thêm Vô số # thu thập.
Nếu không có khối nào được đưa ra, thay vào đó, một điều tra viên được trả về.

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


13

Tôi đã làm một bài kiểm tra điểm chuẩn để thử và trả lời câu hỏi này, sau đó tìm thấy bài đăng này vì vậy đây là những phát hiện của tôi (khác một chút so với các câu trả lời khác)

Đây là mã điểm chuẩn:

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

Và kết quả tôi nhận được là:

                   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) 

Có lẽ một bí danh không miễn phí?


1
Tôi không chắc liệu những khác biệt này có đáng kể hay không. Khi chạy lại, tôi nhận được các kết quả khác nhau về tốc độ (ngay cả khi thu thập băm của bạn có vẻ chậm hơn, thu thập mảng của bạn có vẻ nhanh hơn)
murb

10

Các phương thức collectcollect!là bí danh mapmap!, vì vậy chúng có thể được sử dụng thay thế cho nhau. Đây là một cách dễ dàng để xác nhận rằng:

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

7

Ruby bí danh phương thức Array # map thành Array # coll; chúng có thể được sử dụng thay thế cho nhau. (Nhà sư Ruby)

Nói cách khác, cùng một mã nguồn:

               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
Tôi muốn các tài liệu được tuyên bố rõ ràng rằng họ là bí danh. Hiện tại họ chỉ đơn giản là tham khảo lẫn nhau, và cả hai có mô tả hơi khác nhau.
Chris Bloom
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.