How to get unique elements from an array of hashes in Ruby?

I have an array of hashes and I want to get unique values. Calling Array.uniq does not give me what I expect.

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

Where did I expect:

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

When searching the net, I did not come up with a solution that I was pleased with. Hash.eql? people encouraged to override Hash.eql? and Hash.hash , since this is an Array.uniq request.

Edit: Where I came across this in the real world, the hashes were a bit more complicated. They were the result of parsed JSON that had several fields, some of which were also hash values. I had an array of these results that I wanted to filter out unique values.

I don't like redefine Hash.eql? and Hash.hash , because I need to either override Hash globally or override it for every entry in my array. Changing the Hash definition for each record would be cumbersome, especially since there may be nested hashes inside each record.

Changing Hash around the world has some potential, especially if it was done temporarily. I would like to create another class or helper function that completes saving old definitions and restores them, but I think this adds more complexity than it really needs.

Using inject seems like a good alternative to overriding Hash .

+23
arrays ruby unique hash
Oct 08 '08 at 1:40
source share
8 answers

I can get what I want by inject

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

This will return:

 [{:a=>1}, {:a=>2}] 
+27
Oct 08 '08 at 1:44
source share

Ruby 1.8.7+ will return only what you expected:

 [{:a=>1}, {:a=>2}, {:a=>1}].uniq #=> [{:a=>1}, {:a=>2}] 
+17
Apr 11 '11 at 19:27
source share

I had a similar situation, but the hashes had keys. I used the sorting method.

What I mean:

you have an array:

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

you sort it ( #sort_by {|t| t[:x]} ) and get the following:

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

now a slightly modified version of Aaron Hinny's answer:

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

I also tried:

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

but he is very slow. here is my guideline:

 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 

results:

 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) 
+5
May 7 '09 at 1:18
source share

Assuming your hashes are always single-key pairs, this will work:

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

Hash.to_a creates an array of arrays with a key, so the first map gets you:

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

uniq on Arrays does what you want, giving you:

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

and then the second card puts them back as hashes again.

+2
Oct 08 '08 at 16:35
source share

You can use (verified in 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}] 
+1
Jan 05 '15 at 8:49
source share

The answer you give is similar to the one below. eql? it override hash and eql? for hashes that should appear in the array, and then uniq behaves correctly.

0
Oct 08 '08 at 4:52
source share
0
Mar 06 '09 at 10:29
source share

The pipe method on arrays (available since version 1.8.6) executes the set union (return array) command, so the next way is to get the unique elements of any array a :

[] | a

0
Nov 07
source share



All Articles