Trong Ruby, làm cách nào để tạo một hàm băm từ một mảng?


76

Tôi có một mảng đơn giản:

arr = ["apples", "bananas", "coconuts", "watermelons"]

Tôi cũng có một hàm fsẽ thực hiện thao tác trên một đầu vào chuỗi đơn và trả về một giá trị. Thao tác này rất tốn kém, vì vậy tôi muốn ghi nhớ kết quả trong hàm băm.

Tôi biết mình có thể tạo hàm băm mong muốn bằng một thứ như sau:

h = {}
arr.each { |a| h[a] = f(a) }

Những gì tôi muốn làm là không phải khởi tạo h, vì vậy tôi có thể viết một cái gì đó như sau:

h = arr.(???) { |a| a => f(a) }

Điều đó có thể được thực hiện?

Câu trả lời:


128

Giả sử bạn có một hàm có tên linh hoạt: "f"

def f(fruit)
   fruit + "!"
end

arr = ["apples", "bananas", "coconuts", "watermelons"]
h = Hash[ *arr.collect { |v| [ v, f(v) ] }.flatten ]

sẽ cung cấp cho bạn:

{"watermelons"=>"watermelons!", "bananas"=>"bananas!", "apples"=>"apples!", "coconuts"=>"coconuts!"}

Đã cập nhật:

Như đã đề cập trong các bình luận, Ruby 1.8.7 giới thiệu một cú pháp đẹp hơn cho điều này:

h = Hash[arr.collect { |v| [v, f(v)] }]

Tôi nghĩ bạn muốn nói ... { |v| [v, f(v)] }, nhưng điều này đã làm một mẹo nhỏ!
Wizzlewott

3
Chỉ một điều - tại sao lại có *bên cạnh *arr.collect?
Jeriko

3
@Jeriko - toán tử biểu tượng *thu thập một danh sách vào một mảng hoặc tháo một mảng thành một danh sách, tùy thuộc vào ngữ cảnh. Ở đây, nó giải nén mảng thành một danh sách (được sử dụng làm các mục cho hàm băm mới).
Telemachus

2
Sau khi xem xét câu trả lời của Jörg và suy nghĩ này qua một số chi tiết, lưu ý rằng bạn có thể loại bỏ tất cả *flattencho một phiên bản đơn giản hơn: h = Hash[ arr.collect { |v| [ v, f(v) ] } ]. Tuy nhiên, tôi không chắc liệu có món gotcha mà tôi không nhìn thấy hay không.
Telemachus

3
Trong Ruby 1.8.7, cái xấu Hash[*key_pairs.flatten]chỉ đơn giản là Hash[key_pairs]. Đẹp hơn nhiều, và require 'backports'nếu bạn chưa cập nhật từ 1.8.6.
Marc-André La bất hạnh,

56

Đã làm một số điểm chuẩn nhanh, bẩn trên một số câu trả lời đã cho. (Những phát hiện này có thể không hoàn toàn giống với của bạn dựa trên phiên bản Ruby, bộ nhớ đệm lạ, v.v. nhưng kết quả chung sẽ tương tự.)

arr là một tập hợp các đối tượng ActiveRecord.

Benchmark.measure {
    100000.times {
        Hash[arr.map{ |a| [a.id, a] }]
    }
}

Điểm chuẩn @ real = 0,860651, @ cstime = 0,0, @ cutime = 0,0, @ stime = 0,0, @ utime = 0,8500000000000005, @ tổng = 0,8500000000000005

Benchmark.measure { 
    100000.times {
        h = Hash[arr.collect { |v| [v.id, v] }]
    }
}

Điểm chuẩn @ real = 0,74612, @ cstime = 0,0, @ cutime = 0,0, @ stime = 0,010000000000000009, @ utime = 0,740000000000002, @ tổng = 0,750000000000002

Benchmark.measure {
    100000.times {
        hash = {}
        arr.each { |a| hash[a.id] = a }
    }
}

Điểm chuẩn @ real = 0,627355, @ cstime = 0,0, @ cutime = 0,0, @ stime = 0,010000000000000009, @ utime = 0,6199999999999974, @ tổng = 0,6299999999999975

Benchmark.measure {
    100000.times {
        arr.each_with_object({}) { |v, h| h[v.id] = v }
    }
}

Điểm chuẩn @ real = 1.650568, @ cstime = 0.0, @ cutime = 0.0, @ stime = 0.12999999999999998, @ utime = 1.51, @ total = 1.64

Tóm lại là

Chỉ vì Ruby là biểu cảm và năng động, không có nghĩa là bạn nên luôn tìm kiếm giải pháp tốt nhất. Mỗi vòng lặp cơ bản là nhanh nhất trong việc tạo một băm.


7
Bạn, người bạn của tôi, là tuyệt vời để làm bài tập ở nhà của bạn và đăng nó :)
Alexander Bird

Nhanh hơn một chút khi sử dụng biến vòng lặp tăng dần theo cách thủ công: Tôi không có tập dữ liệu của bạn - tôi vừa nấu một đối tượng tầm thường với trình truy cập @id và ít nhiều khớp với các số của bạn - nhưng việc lặp lại trực tiếp đã giảm một vài%. Về mặt phong cách, tôi thích {} .tap {| h | ....} để gán một hàm băm, vì tôi thích các phần được đóng gói.
android.weasel



11

Đây là những gì tôi có thể sẽ viết:

h = Hash[arr.zip(arr.map(&method(:f)))]

Đơn giản, rõ ràng, rõ ràng, tuyên bố. Bạn có cần gì nữa không?


1
Tôi cũng thích zipanh chàng tiếp theo, nhưng vì chúng tôi đã gọi map, tại sao không để nó ở đây? h = Hash[ arr.map { |v| [ v, f(v) ] } ]Có một lợi thế cho phiên bản của bạn mà tôi không thấy?
Telemachus

@Telemachus: Với tất cả mã Haskell mà tôi đã đọc, tôi chỉ quen với lập trình không điểm, vậy thôi.
Jörg W Mittag,

5

Tôi đang làm nó như được mô tả trong bài viết tuyệt vời này http://robots.thoughtbot.com/iteration-as-an-anti-pattern#build-a-hash-from-an-array

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h.merge(fruit => f(fruit)) }

Thông tin thêm về injectphương pháp: http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-inject


Điều này thực hiện một mergecho mỗi bước lặp lại. Hợp nhất là O (n), cũng như lặp lại. Vì vậy, đây là O(n^2)trong khi bản thân vấn đề rõ ràng là tuyến tính. Về mặt tuyệt đối, tôi chỉ thử điều này trên một mảng có 100k phần tử và nó mất 730 secondsthời gian, trong khi các phương pháp khác được đề cập trong chủ đề này lấy từ 0.7đến 1.1 seconds. Vâng, đó là sự chậm lại của Factor 700 !
Matthias Winkelmann

1

Một cái khác, IMHO rõ ràng hơn một chút -

Hash[*array.reduce([]) { |memo, fruit| memo << fruit << f(fruit) }]

Sử dụng độ dài dưới dạng f () -

2.1.5 :026 > array = ["apples", "bananas", "coconuts", "watermelons"]
 => ["apples", "bananas", "coconuts", "watermelons"] 
2.1.5 :027 > Hash[*array.reduce([]) { |memo, fruit| memo << fruit << fruit.length }]
 => {"apples"=>6, "bananas"=>7, "coconuts"=>8, "watermelons"=>11} 
2.1.5 :028 >

1

ngoài câu trả lời của Vlado Cingel (tôi chưa thể thêm nhận xét, vì vậy tôi đã thêm câu trả lời).

Inject cũng có thể được sử dụng theo cách này: khối phải trả lại bộ tích lũy. Chỉ phép gán trong khối mới trả về giá trị của phép gán và lỗi được báo cáo.

array = ["apples", "bananas", "coconuts", "watermelons"]
hash = array.inject({}) { |h,fruit| h[fruit]= f(fruit); h }

Tôi đã đánh giá điểm chuẩn của hai phiên bản: việc sử dụng hợp nhất làm tăng gấp đôi thời gian thực hiện. Phiên bản tiêm trên là một comparabe lên phiên bản thu thập của microspino
ruud
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.