2013-12-13 14 views
6

Tôi đã nhìn thấy rất nhiều câu hỏi về điều này nhưng chỉ với một khóa, không bao giờ cho nhiều khóa.Ruby xóa các mục trùng lặp trong mảng băm nhưng dựa trên nhiều hơn một giá trị

Tôi có mảng sau băm:

a = [{:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"3:21"}, 
{:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, 
{:name=>"Luv Is", :duration=>"3:13"}, 
{:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"2"}, 
{:name=>"Chick on the Side", :artist=>"Another Dude"}] 

a.uniq sẽ không làm việc ở đây vì thời gian là khác nhau hoặc có thể thậm chí không tồn tại. Tôi có một khóa duy nhất được thiết lập trong cơ sở dữ liệu không cho phép các mục trùng lặp bằng cùng tên, nghệ sĩ và nhà soạn nhạc vì vậy đôi khi tôi gặp lỗi khi mọi người có mục nhập trùng lặp cho 3 khóa này.

Có cách nào để chạy uniq có thể kiểm tra 3 khóa đó không? Tôi đã thử một khối như thế này:

new_tracks.uniq do |a_track| 
    a_track[:name] 
    a_track[:artist] 
    a_track[:composer] 
end 

Nhưng mà bỏ qua bất cứ điều gì mà quan trọng là không có mặt (bất kỳ mục mà không có một nhà soạn nhạc không đáp ứng được các tiêu chí trên ví dụ).

Tôi luôn có thể chỉ sử dụng khóa :name nhưng điều đó có nghĩa là tôi sẽ loại bỏ các bản nhạc có khả năng hợp lệ trong cùng một tập hợp có cùng tên nhưng nghệ sĩ hoặc nhà soạn nhạc khác nhau.

Điều này là với Ruby 2.0.

Trả lời

13

uniq chấp nhận một khối. Nếu một khối được đưa ra, nó sẽ sử dụng giá trị trả về của khối để so sánh.

Mã của bạn gần với giải pháp, nhưng trong mã của bạn, giá trị trả về chỉ là a_track[:composer] là tuyên bố được đánh giá cuối cùng.

Bạn có thể tham gia các thuộc tính bạn muốn vào chuỗi và trả về chuỗi đó.

new_tracks.uniq { |track| [track[:name], track[:artist], track[:composer]].join(":") } 

Một refactoring có thể là

new_tracks.uniq { |track| track.attributes.slice('name', 'artist', 'composer').values.join(":") } 

Hoặc thêm một phương pháp tùy chỉnh trong mô hình của bạn mà thực hiện việc tham gia, và gọi nó là

class Track < ActiveRecord::Base 
    def digest 
    attributes.slice('name', 'artist', 'composer').values.join(":") 
    end 
end 

new_tracks.uniq(&:digest) 
+0

Tuyệt vời, ** cảm ơn rất nhiều! ** Điều đầu tiên hoạt động tốt: '.uniq {| track | [track [: name], track [: artist], theo dõi [: composer]]. tham gia (":")} '. Cái thứ hai cho tôi một lỗi 'SyntaxError: unexpected '}', mong đợi ']''. Nếu tôi sửa lỗi đó, tôi sẽ nhận được 'phương thức undefined' attributes''. Nhưng cái đầu tiên thực hiện thủ thuật. Cảm ơn một lần nữa. – kakubei

+0

Tôi đã sửa lỗi cú pháp. –

+0

Tôi vẫn nhận được 'phương thức không xác định' thuộc tính '' cho dòng đó ... – kakubei

2

Nếu tôi hiểu câu hỏi của bạn, nó chỉ là một vấn đề sử dụng kết hợp dữ liệu bên phải trong khối uniq:

a = [ 
    {:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"3:21"}, 
    {:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=> 'First Dude', :duration=>"2"}, 
    {:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, 
    {:name=>"Chick on the Side", :artist=>"Another Dude"}, 
    {:name=>"Luv Is", :duration=>"3:13"}, 
] 

a.uniq{ |a_track| 
    [ 
    a_track[:name], 
    a_track[:artist], 
    a_track[:composer], 
    ] 
} 

nào sẽ trở lại:

[ 
    {:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=>"First Dude", :duration=>"3:21"}, 
    {:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, 
    {:name=>"Luv Is", :duration=>"3:13"} 
] 

uniq cho phép chúng ta tạo ra bất cứ điều gì bên trong khối của nó, và sử dụng nó để so sánh nó. Tôi đang lựa chọn để sử dụng một mảng, vì của Ruby biết làm thế nào để so sánh mảng, nhưng giá trị có thể là một checksum MD5 hoặc một tấm séc CRC nếu điều đó có ý nghĩa thực hiện:

a.uniq{ |a_track| 
    OpenSSL::Digest::MD5.digest(a_track[:name] + (a_track[:artist] || '') + (a_track[:composer] || '')) 
} 
# => [{:name=>"Yes, Yes, Yes", :artist=>"Some Dude", :composer=>"First Dude", :duration=>"3:21"}, {:name=>"Chick on the Side", :artist=>"Another Dude", :duration=>"3:20"}, {:name=>"Luv Is", :duration=>"3:13"}] 

tôi phải sử dụng (a_track[:artist] || '') bởi vì chúng tôi có thể' t nối một số nil vào một Chuỗi, do đó, || '' trả về một chuỗi trống để thay thế.

+0

Điều này thật thú vị, tôi thích cách tiếp cận này: 'a.uniq {| a_track | [a_track [: name], a_track [: nghệ sĩ], a_track [: nhà soạn nhạc]]} 'Tôi đã thấy câu trả lời của Simone trước nên tôi chấp nhận câu trả lời đó, nhưng tôi thích câu trả lời này tốt hơn. Cảm ơn rất nhiều. – kakubei

+0

Bạn cũng có thể 'to_s' mỗi giá trị, mà sẽ chuyển đổi bất kỳ nils thành chuỗi rỗng. –

+0

Chúng tôi có thể, nhưng nó ẩn ý định. –

0

Một cách khác để thực hiện việc này là sử dụng values_at.nếu bạn không muốn sử dụng slice và tham gia

a.uniq {|hash| hash.values_at(:name, :composer, :artist)} 
Các vấn đề liên quan