2008-10-08 29 views
24

Tôi có một mảng băm, và tôi muốn các giá trị duy nhất của nó. Gọi số Array.uniq không cho tôi những gì tôi mong đợi.Làm thế nào để có được các yếu tố độc đáo từ một mảng băm trong Ruby?

a = [{:a => 1},{:a => 2}, {:a => 1}] 
a.uniq # => [{:a => 1}, {:a => 2}, {:a => 1}] 

đâu tôi mong đợi:

[{:a => 1}, {:a => 2}] 

Trong việc tìm kiếm xung quanh trên mạng, tôi không đưa ra một giải pháp mà tôi đang hài lòng với. Folks đã đề xuất xác định lại Hash.eql?Hash.hash, vì đó là những gì Array.uniq đang truy vấn.

Chỉnh sửa: Nơi tôi gặp phải điều này trong thế giới thực, các băm hơi phức tạp hơn một chút. Chúng là kết quả của JSON được phân tích cú pháp có nhiều trường, một số trong đó các giá trị là băm. Tôi đã có một loạt các kết quả mà tôi muốn lọc ra các giá trị duy nhất.

Tôi không thích định nghĩa lại các giải pháp Hash.eql?Hash.hash, vì tôi sẽ phải xác định lại Hash trên toàn cầu hoặc xác định lại nó cho mỗi mục nhập trong mảng của tôi. Thay đổi định nghĩa của Hash cho mỗi mục nhập sẽ rất cồng kềnh, đặc biệt là vì có thể có băm lồng nhau bên trong mỗi mục nhập.

Thay đổi Hash trên toàn cầu có một số tiềm năng, đặc biệt nếu nó được thực hiện tạm thời. Tôi muốn xây dựng một lớp hoặc chức năng trợ giúp khác bao bọc việc lưu các định nghĩa cũ và khôi phục chúng, nhưng tôi nghĩ điều này làm tăng thêm sự phức tạp hơn là thực sự cần thiết.

Sử dụng inject có vẻ như là một lựa chọn tốt để xác định lại Hash.

Trả lời

27

tôi có thể có được những gì tôi muốn bằng cách gọi inject

a = [{:a => 1},{:a => 2}, {:a => 1}] 
a.inject([]) { |result,h| result << h unless result.include?(h); result } 

này sẽ trở lại:

[{:a=>1}, {:a=>2}] 
+0

nhiều hơn tôi nghĩ hơn một liên kết tôi đăng trên – edthix

0

Câu trả lời bạn đưa ra cũng tương tự như một trong những thảo luận here. Nó ghi đè các phương pháp hasheql? trên các băm sẽ xuất hiện trong mảng mà sau đó làm cho uniq hoạt động chính xác.

+0

Đó là một trong những giải pháp tôi tìm thấy trên mạng. Tôi không thích rằng tôi cần phải xác định lại băm, chỉ cần gọi uniq. –

+0

Nếu các lớp vanilla Hash và Array không làm những gì bạn cần, bạn nên thực sự xem xét việc định nghĩa các lớp của riêng bạn để thực hiện hành vi cần thiết. Bạn có thể mô tả bạn đang cố gắng mô hình hóa gì với các mảng băm không? –

2

Giả sử băm của bạn luôn luôn đơn cặp khóa-giá trị, điều này sẽ làm việc:

a.map {|h| h.to_a[0]}.uniq.map {|k,v| {k => v}} 

Hash.to_a tạo ra một mảng của mảng quan trọng có giá trị, vì vậy bản đồ đầu tiên giúp bạn:

[[:a, 1], [:a, 2], [:a, 1]] 

uniq trên Mảng làm những gì bạn muốn, cho bạn:

[[:a, 1], [:a, 2]] 

và sau đó bản đồ thứ hai đặt chúng trở lại togeth er như băm một lần nữa.

+0

Vấn đề thế giới thực mà tôi gặp phải khi sử dụng các băm phức tạp hơn. –

+0

Không chắc tại sao điều này lại bị bỏ phiếu, vì vậy tôi đã sao lưu nó. –

5

Tôi đã có một tình huống tương tự, nhưng băm có khóa. Tôi đã sử dụng phương pháp sắp xếp.

Những gì tôi có nghĩa là:

bạn có một mảng:

[{:x=>1},{:x=>2},{:x=>3},{:x=>2},{:x=>1}] 

bạn sắp xếp nó (#sort_by {|t| t[:x]}) và có được điều này:

[{:x=>1}, {:x=>1}, {:x=>2}, {:x=>2}, {:x=>3}] 

bây giờ là một phiên bản sửa đổi chút câu trả lời bằng Aaaron Hinni:

your_array.inject([]) do |result,item| 
    result << item if !result.last||result.last[:x]!=item[:x] 
    result 
end 

Tôi cũng đã thử:

test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]} 

nhưng nó rất chậm. đây là điểm chuẩn của tôi:

test=[] 
1000.times {test<<{:x=>rand}} 

Benchmark.bmbm do |bm| 
    bm.report("sorting: ") do 
    test.sort_by {|t| t[:x]}.inject([]) {|r,h| r<<h if !r.last||r.last[:x]!=h[:x]; r} 
    end 
    bm.report("inject: ") {test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]} } 
end 

kết quả:

Rehearsal --------------------------------------------- 
sorting: 0.010000 0.000000 0.010000 ( 0.005633) 
inject:  0.470000 0.140000 0.610000 ( 0.621973) 
------------------------------------ total: 0.620000sec 

       user  system  total  real 
sorting: 0.010000 0.000000 0.010000 ( 0.003839) 
inject:  0.480000 0.130000 0.610000 ( 0.612438) 
17

của Ruby 1.8.7+ sẽ trả lại chỉ là những gì bạn đã mong đợi:

[{:a=>1}, {:a=>2}, {:a=>1}].uniq 
#=> [{:a=>1}, {:a=>2}] 
0

Phương pháp ống trên mảng (có sẵn từ 1.8 .6) thực hiện công đoàn thiết lập (trả về mảng), vì vậy sau đây là một cách có thể khác để lấy các phần tử duy nhất của bất kỳ mảng nào a:

[] | a

+0

tính năng này không hoạt động đối với tôi. –

+0

@SunnyRGupta, bạn đang sử dụng phiên bản Ruby nào? – yoniLavi

+0

'ruby 1.9.3p448 (2013-06-27 bản sửa đổi 41675) [x86_64-darwin13.2.0]' –

1

Bạn có thể sử dụng (thử nghiệm trong ruby ​​1.9.3),

[{a: 1},{a: 2},{a:1}].uniq => [{a:1},{a: 2}] 
[{a: 1,b: 2},{a: 2, b: 2},{a: 1, b: 3}].uniq_by {|v| v[:a]} => [{a: 1,b: 2},{a: 2, b: 2}] 
Các vấn đề liên quan