Compare values from multiple hashes within an array - ruby

I know for-loops should be avoided and i guess the better way to iterate through an array is instead of doing
for i in 0..array.size-1 do
puts array[i]
end
do
array.each{ |x|
puts x
}
But what if i had an array of hashes like
array = [{:value0 => 1, :value1 => 0}, {:value0 => 2, :value1 => 1}, {:value0 => 1, :value1 => 2}]
and wanted to check if :value0 is unique in all hashes.. intuitively I would do something like
for i in 0..array.size-1 do
_value_buffer = array[i][:value0]
for j in i+1..array.size-1 do
if _value_buffer == array[j][:value0]
puts "whatever"
end
end
end
Is there a better way to do this?

Why not just get all the values in question and see if they’re unique?
!array.map { |h| h[:value0] }.uniq!
(uniq! returns nil when there are no duplicates)

As what Andrew Marshall says, you can use uniq!, but with a more concise way:
!array.uniq!{|a| a[:value0]}

Here is how I would do it:
2.0.0p195 :001 > array = [{:value0 => 1, :value2 => 0}, {:value0 => 2, :value2 => 1}, {:value0 => 1, :value2 => 2}]
=> [{:value0=>1, :value2=>0}, {:value0=>2, :value2=>1}, {:value0=>1, :value2=>2}]
2.0.0p195 :002 > val0 = array.map { |hash| hash[:value0] }
=> [1, 2, 1]
2.0.0p195 :003 > puts val0.uniq == val0
false
=> nil
I would collect the values of :value0 and then compare them to the array of unique values.

Related

Is it possible to sort multidimensional hashes by child hash values?

Is is possible to sort parent hashes by values of children keys?
For example:
{
:a =>
{:order => 3},
:b =>
{:order => 1},
:c =>
{:order => 2}
}
resorted as
{
:b =>
{:order => 1},
:c =>
{:order => 2},
:a =>
{:order => 3}
}
You can convert it to an array of pairs, use sort_by method to target the value you want to sort by, and then convert it back to a hash:
h = {
:a =>
{:order => 3},
:b =>
{:order => 1},
:c =>
{:order => 2}
}
h.sort_by {|k,v| v[:order]}.to_h
=> {:b=>{:order=>1}, :c=>{:order=>2}, :a=>{:order=>3}}
Keep in mind that the only order that a Ruby hash can have is based on insertion order. You need to create a new hash (no sort!) and create the new hash element by element in the order you wish it to have.
Given:
> hash
=> {:a=>{:order=>3}, :b=>{:order=>1}, :c=>{:order=>2}}
You can use .sort_by to do:
> hash.sort_by {|k, h| h[:order]}.to_h
=> {:b=>{:order=>1}, :c=>{:order=>2}, :a=>{:order=>3}}
You can also use the more classic .sort with the spaceship <=> by unpacking the arguments associated with the usual a,b:
> hash.sort {|(a,ha),(b,hb)| ha[:order] <=> hb[:order] }.to_h
=> {:b=>{:order=>1}, :c=>{:order=>2}, :a=>{:order=>3}}
In either case, the .to_h method creates a new hash based on the sorted key, value pairs from the source hash.
Best

Sort hash by array

I have a hash and an array with same length like the following:
h = {:a => 1, :b => 2, :c => 3, :d => 4}
a = [2, 0, 1, 0]
I want to order the hash in increasing order of the values in the array. So the output would be something like:
h = {:b => 2, :d => 4, :c=> 3, :a => 1}
Ideally I want to introduce some randomness for ties. For the previous example, I want either the previous output or:
h = {:d => 4, :b => 2, :c=> 3, :a => 1}
This is what I tried.
b = a.zip(h).sort.map(&:last)
p Hash[b]
# => {:b=>2, :d=>4, :c=>3, :a=>1}
But I am not sure how to introduce the randomness.
h.to_a.sort_by.each_with_index{|el,i| [a[i], rand]}.to_h
You could modify what you have slightly:
def doit(h,a)
Hash[a.zip(h).sort_by { |e,_| [e,rand] }.map(&:last)]
end
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }
doit(h,a) #=> { d=>4, b=>2, c=>3, a=>1 }
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }
doit(h,a) #=> { b=>2, d=>4, c=>3, a=>1 }

Ruby hash permutation

Is there any quick way to get a (random) permutation of a given hash? For example with arrays I can use the sample method as in
ruby-1.9.2-p180 :031 > a = (1..5).to_a
=> [1, 2, 3, 4, 5]
ruby-1.9.2-p180 :032 > a.sample(a.length)
=> [3, 5, 1, 2, 4]
For hashes I can use the same method on hash keys and build a new hash with
ruby-1.9.2-p180 :036 > h = { 1 => 'a', 2 => 'b', 3 => 'c' }
=> {1=>"a", 2=>"b", 3=>"c"}
ruby-1.9.2-p180 :037 > h.keys.sample(h.length).inject({}) { |h2, k| h2[k] = h[k]; h2 }
=> {3=>"c", 2=>"b", 1=>"a"}
but this is so ugly. Is there any 'sample' method for hashes which can avoid all that code?
Update As pointed out by #Michael Kohl in comments, this question is meaningful only for ruby 1.9.x. Since in 1.8.x Hash are unordered there is no way to do that.
A slight refinement of mu is too short's answer:
h = Hash[h.to_a.shuffle]
Just add a to_a and Hash[] to your array version to get a Hash version:
h = Hash[h.to_a.sample(h.length)]
For example:
>> h = { 1 => 'a', 2 => 'b', 3 => 'c' }
=> {1=>"a", 2=>"b", 3=>"c"}
>> h = Hash[h.to_a.sample(h.length)]
=> {2=>"b", 1=>"a", 3=>"c"}
Do you really need to shuffle or do you just need a way to access/iterate on a random key ?
Otherwise, a maybe less expensive solution would be to shuffle the hash keys and access your items based on the permutation of those hash keys
h = your_hash
shuffled_hash_keys = hash.keys.shuffle
shuffled_hash_keys.each do |key|
# do something with h[key]
end
I believe (but would need a proof with a benchmark) that this avoids the need/cost to build a brand new hash and is probably more efficient if you have big hashes (you only need to pay the cost of an array permutation)

How to work out frequency of certain key's value in an array of hashes?

I have an array of hashes. Each hash has an uses key. Multiple hashes can share the same uses value.
[{uses => 0},{uses => 1},{uses => 2},{uses => 1},{uses => 0},{uses => 1},{uses => 3}]
How can I generate an array of the most frequent uses values, in a descending order?
[1,0,2,3]
Referencing this discussion of frequency of items in a list, we can easily modify this for your task.
> unsorted = [{:uses=>0}, {:uses=>1}, {:uses=>2}, {:uses=>1}, {:uses=>0}, {:uses=>1}, {:uses=>3}].map{|h| h[:uses]}
> sorted = unsorted.uniq.sort_by{|u| unsorted.grep(u).size}.reverse
=> [1, 0, 2, 3]
hs.inject({}) do |histogram, h|
histogram.merge(h[:uses] => (histogram[h[:uses]] || 0) + 1)
end.sort_by { |k, v| -v }.map { |k, v| k }
# => [1, 0, 2, 3]
I always recommend to use Facets, though:
http://rubyworks.github.com/facets/doc/api/core/Enumerable.html
hs.frequency.sort_by { |k, v| -v }.map { |k, v| k }
# => [1, 0, 2, 3]
Here is a one pass solution:
a = [{:uses => 0},{:uses => 1},{:uses => 2},{:uses => 1},{:uses => 0},
{:uses => 1},{:uses => 3}]
# A hash with the frequency count is formed in one iteration of the array
# followed by the reverse sort and extraction
a.inject(Hash.new(0)) { |h, v| h[v[:uses]] += 1;h}.
sort{|x, y| x <=> y}.map{|kv| kv[0]}

Ruby sort by boolean and number

I'm using Ruby 1.8.7. I have the following array of hashes. I need to sort by the boolean value first, but those results must be ordered as well in the original order. I basically need to shift all the true hashes to the top of the array but maintain the original ordering.
Any help would be appreciated!
array = [{:id => 1, :accepts => false},
{:id => 2, :accepts => false},
{:id => 3, :accepts => true},
{:id => 4, :accepts => false},
{:id => 5, :accepts => true}]
sorted = array.sort do |x, y|
if x[:accepts] == y[:accepts]
0
elsif x[:accepts] == true
-1
elsif x[:accepts] == false
1
end
end
This sort that I have yields:
5 - true
3 - true
2 - false
4 - false
1 - false
I need it to yield:
3 - true
5 - true
1 - false
2 - false
4 - false
Use sort_by for these things, not sort!
array.sort_by {|h| [h[:accepts] ? 0 : 1,h[:id]]}
This does the job:
array.sort{|a,b| (a[:accepts] == b[:accepts]) ? ((a[:id] < b[:id]) ? -1 : 1) : (a[:accepts] ? -1 : 1)}
array = [{:id => 1, :accepts => false},
{:id => 2, :accepts => false},
{:id => 3, :accepts => true},
{:id => 4, :accepts => false},
{:id => 5, :accepts => true}]
sorted = array.sort do |x, y|
if x[:accepts] ^ y[:accepts]
x[:accepts] ? -1 : 1
else
x[:id] <=> y[:id]
end
end
puts sorted
Or != instead of ^, if you wish.
Well, from your question I deduce you really wanted to group the results by the :accepts value and merge both result sets back into one array. My solution to this would've been:
array.select {|where| where[:accepts] } | array.reject {|where| where[:accepts] }
# => [{:accepts=>true, :id=>3},
# {:accepts=>true, :id=>5},
# {:accepts=>false, :id=>1},
# {:accepts=>false, :id=>2},
# {:accepts=>false, :id=>4}]
This will maintain original order without implying any sorts on the :id key. This means you won't need a helper key to preserve order, and you can preserve order on the result regardless of the transported data.
This may also be useful (and maybe exactly what you need for further evaluations):
array.group_by {|where| where[:accepts] }
# => {false=>[{:accepts=>false, :id=>1},
# {:accepts=>false, :id=>2},
# {:accepts=>false, :id=>4}],
# true=>[{:accepts=>true, :id=>3},
# {:accepts=>true, :id=>5}]}
Again, no artificial sorts involved... group_by is new in 1.8.7.
PS: If you don't want the first code snippet remove duplicates from your array, replace the bar operator with the plus operator. "|" merges two sets according to the theory of sets (union) while "+" concatenates two sets (the result is not really a set but a plain array).
You could add an extra check on the :id key if :accepts is equal, as follows:
array = [{:id => 1, :accepts => false},
{:id => 2, :accepts => false},
{:id => 3, :accepts => true},
{:id => 4, :accepts => false},
{:id => 5, :accepts => true}]
sorted = array.sort do |x, y|
if x[:accepts] == y[:accepts]
if x[:id] == y[:id]
0
elsif x[:id] > y[:id]
1
elsif x[:id] < y[:id]
-1
end
  elsif x[:accepts] == true
    -1
  elsif x[:accepts] == false
    1
  end
end
This is because sorting in Ruby 1.8.7 is not stable
http://redmine.ruby-lang.org/issues/show/1089
http://en.wikipedia.org/wiki/Stable_sort#Stability
All you need to do is have your sort block not return 0:
sorted = array.sort do |x, y|
if x[:accepts] == y[:accepts]
x[:id] <=> y[:id] # not 0
elsif x[:accepts]
-1
else
1
end
end
(no need to explicitly compare a boolean to true and false)
a.sort_by { |x| (x[:accepts] ? 0 : 99999) + x[:id] }
Update: Well, obviously this requires x[:id].respond_to? "+" and additionally there are restrictions on its range relative to the constants.
This is, however, the shortest and probably the fastest answer, if also obviously the most questionable.
The really important lesson is that it illustrates that one should look beyond Array (or whatever) and check Enumerable if that's in (your object).class.ancestors. These questions and their viewers are often after an answer to "what should I learn about Ruby next, I suspect there are other ways".
Regardless of whether this is a good way to sort (admittedly it's questionable) this answer suggests #sort_by and just finding the docs for #sort_by (it's not in Array) will teach a small but important lesson to a beginner.

Resources