Sorting a Hash by the values in another hash - ruby

I would like to sort the keys of Hash2 by the order of the values of Hash1.
Hash2 does not need to contain all the values of Hash1.
Hash1 does not need to contain all the keys of Hash2.
If a key exists in Hash2 that does not have a corresponding value in Hash1, it should be ordered below any existing ordered keys.
Hash1 = {
p0: "q11",
p1: "q55",
p2: "q92",
p3: "q77"
}
Hash2 = {
q55: {...},
q23: {...},
q59: {...},
q98: {...},
q11: {...}
}
=>
DesiredHash = {
q11: {...},
q55: {...},
q23: {...},
q59: {...},
q98: {...}
}
What is the most Ruby-ish way of achieving this?

Similar to #sawa's but the use of Hash#values really cleans things up imho:
keys = Hash1.values.map &:to_sym
Hash[Hash2.sort_by{|k, v| keys.index(k) || keys.length}]

array = Hash1.map{|k, v| [k, v.to_sym]}
Hash[Hash2.sort_by{|k, _| array.index{|_, v| k == v} || array.length}]
Or, perhaps less efficient but shorter:
array = Hash1.to_a
Hash[Hash2.sort_by{|k, _| array.index{|_, v| k == v.to_s} || array.length}]
Explanation (Using the second one)
Hash1 will be used as a reference, which means you want to do something with its "index". Since hashes do not have the notion of index, you have to convert it into an array. to_a does that. Next, you want to sort Hash2 (which means it will be implicitly converted to an array with key-value pairs) in the order the index of array such that the key of Hash2 matches the "value" of array. index{...} will look up such index. In case there is no match, array.length will be assigned, which is bigger than any index of array. Finally, you want to turn that pairs of key-value back into a hash. That is done by Hash[...].

hash1 = {
p0: "q11",
p1: "q55",
p2: "q92",
p3: "q77"
}
hash2 = {
q55: 1,
q23: 2,
q59: 3,
q98: 4,
q11: 5
}
def get_key(hash, value)
hash.each { |k, v| return k if v == value }
return hash.keys.max
end
Hash[hash2.sort_by { |a, _| get_key(hash1, a.to_s) }]
#=> {:q11=>5, :q55=>1, :q23=>2, :q98=>4, :q59=>3}

Related

Finding `uniq` values in array with their indexes

Let say I have an array
[1, 2, 3, 4, 3, 2]
I wonder how I can get the hash { 1 => 0, 4 => 3 } (value => index). I need the index of the earlier occurrence.
Could also try this
unique = array.select { |a| array.count(a) === 1 }
indexes = unique.map { |u| array.index(u) }
Hash[unique.zip(indexes)]
My algorithm is to first generate a new array of the single elements. I do that by filtering the array for the one's where the count is equal to 1. We can use the array#count method.
I then create a second array, where I map the values of the first array, to their indexes in the original array.
I then combine the values using the array#zip function and convert to a hash with Hash.
Output
{ 1 => 0, 4 => 3 }
a.each_with_index.with_object({}) { |(e, i), h| h.key?(e) ? h[e] = nil : h[e] = i }
.reject! { |k, v| v.nil? }

How do I create a hash where the keys are values from an array Ruby

I have an array:
arr = [a, ab, abc]
I want to make a hash, using the values of the array as the keys:
newhash = [a[1], ab[1], abc[1]]
I have tried:
arr.each do |r|
newhash[r] == 1
end
to no avail.
How would I about accomplishing this in ruby?
If you are feeling like a one-liner, this will work as well
h = Hash[arr.collect { |v| [v, 1] } ]
collect is invoked once per element in the array, so it returns an array of 2-element arrays of key-value pairs.
Then this is fed to the hash constructor, which turns the array of pairs into a hash
You could also use the #reduce method from Enumerable (which is included into the Array class).
new_hash = arr.reduce({}) { |hsh, elem| hsh[elem] = 1; hsh }
And your new_hash looks like this in Ruby:
{"a": 1, "ab": 1, "abc": 1}
== is comparison. = is assigning. So just modify == into =. It works.
newhash = {}
arr.each do |r|
newhash[r] = 1
end
(I believe a, ab, abc are strings)
To learn more, this might help you. Array to Hash Ruby
You can do it like this:
ary = [[:foo, 1], [:bar, 2]]
Hash[ary] # => {:foo=>1, :bar=>2}
If you want to do it like you tried earlier, you want to initialize hash correctly:
ary = [:foo, :bar]
hash = {}
ary.each do |key|
hash[key] = 1
end # => {:foo=>1, :bar=>2}

Ruby hash keys to array conditional on hash value

I would like to extract hash key values to an array when a condition is met. For example, with hash h I want to extract the keys where the values are "true":
h = { :a => true, :b => false, :c =>true }
I've come up with this:
h.map {|k,v| k if v==true} - [nil]
Any alternatives?
h.select { |_, v| v }.keys
Will do the same, but in more readable way.
You can also do
s = {}
h.each do |k,v|
s[k] = v if v==true
end

Ruby - find the key(s) of the largest value(s) of a hash

I have a hash and I want to return the key(s) (or key/value pair(s)) of the max value(s) of the hash. So, if there is only one true max, it will return that one key; however, if there are multiple key/value pairs with the same value, it will return all of these keys. How can I accomplish this in Ruby?
my_hash.max_by {|k,v| v} #only returns one key/value pair
If you want all pairs, I would do something like
max = my_hash.values.max
Hash[my_hash.select { |k, v| v == max}]
A single liner:
my_hash.reduce({}){|h,(k,v)| (h[v] ||= []) << k;h}.max
irb
> z = {:tree => 3, :two => 2, 'three' => 3}
> z.reduce({}){|h,(k,v)| (h[v] ||= []) << k;h}.max
[3, [:tree, "three"]]

How to merge two hashes with no new keys

How could I merge two hashes that results in no new keys, meaning the merge would merge keys that exist in both hashes?
For example, I want the following:
h = {:foo => "bar"}
j = {:foo => "baz", :extra => "value"}
puts h.merge(j) # {:foo => "baz"}
I'm looking for a really clean way of doing this as my current implementation is pretty messy.
You could remove keys that weren't in the first hash from the second hash, then merge:
h.merge j.select { |k| h.keys.include? k }
Unlike my edited-out alternative, this is safe if you decide to change it to a merge! or update.
If you're using activesupport (part of rails), you can take advantage of 2 extra methods on Hash:
Hash#slice takes the desired keys as separate arguments (not an array of keys) and returns a new hash with just the keys you asked for.
Hash#except takes the same arguments as slice, but returns a new hash with keys that were not in the arguments.
First load activesupport:
require 'active_support/core_ext'
Merge only entries from j whose keys are already in h (i.e. modify, but don't add any or remove entries in h):
h.merge(j.slice(*h.keys))
Example:
ignore_new = ->(h, j) { h.merge(j.slice(* h.keys)) }
ignore_new.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:a=>1, :b=>10, :c=>11}
Get the leftovers from j that weren't in h:
j.except(*h.keys)
Bonus:
If you want true intersection, meaning you want a result that only has keys that are in common between the 2 hashes, do this:
h.merge(j).slice(* ( h.keys & j.keys ) )
Example:
intersect = ->(h, j) { h.merge(j).slice(* (h.keys & j.keys) ) }
intersect.({a: 1, b: 2, c: 3}, {b: 10, c: 11, d: 12})
# => {:b=>10, :c=>11}
and leftovers from h that weren't in j:
h.except(*j.keys)
You may also want to use activesupport's HashWithIndifferentAccess if you want string & symbol key-access to be considered equivalent.
Note that none of the above examples change the original hashes; new hashes are returned instead.
Yjerem's answer works in Ruby 1.9, but not in 1.8.x. In 1.8.x the Hash#select method returns an array. Hash#reject returns a hash.
h.reject { |k,v| !j.keys.include? k }
If you want to keep only key-value pairs that have identical values, you can do this:
h.reject { |k,v| j[k] != h[k] }
The edge case there is nils. If you are storing nils in your Hash then you have to do this:
h.reject { |k,v| !j.has_key? k or j[k] != h[k] }
[h].inject({}) { |m,e| e.merge(j) { |k,o,n| m[k] = n }; m}
or
[{}].inject(h) { |m,e| m.merge(j) { |k,o,n| e[k] = n }; e}
or (probably the best, but not technically a single expression) ...
t = {}; h.merge(j) { |k,o,n| t[k] = n }; t
The more customized way of doing this is:
h = {"foo"=> "bar"}
j = {"foo" => "baz", "extra" => "value"}
k = h.merge(j)
result: {"foo"=>"baz", "extra"=>"value"}
Here the key "foo" in the second hash is overriding the "foo" in first hash.But if you want to keep the old value i.e bar or if you want keep the new value i.e "baz"? You can do something like this:
k = h.merge(j){|key, old, new| old}
result: {"foo"=>"bar", "extra"=>"value"}
k = h.merge(j){|key, old, new| new}
result: {"foo"=>"baz", "extra"=>"value"}

Resources