How do I change a hash using new hash values? - ruby

I have these hashes:
{"a" => 1, "b" => 2, "c" => 3, "k" => 14}
{"b" => 51, "c" => 2, "d" => 8}
I need to write code, so that after manipulation, the result would be:
{"a" => 1, "b" => 51, "c" => 2, "k" => 14}
I tried:
h1.each do |h, j|
h2.each do |hh, jj|
if h == hh
j = jj
end
end
end
but it doesn't work. Also I think this is ugly code, so how would could it be written better/right?
I though I should compare the two hashes, and, if the second key is the same as the first, change the first hash value to the second hash's value.

Just iterate over the entries in h2 and update the corresponding entry in h1 only if it already exists:
h2.each { |k,v| h1[k]=v if h1.include?(k) }
h1 # => {"a"=>1, "b"=>51, "c"=>2, "k"=>14 }
Also, if you want to update the entries as above and also add new entries from h2 you can simply use the Hash#merge! method:
h1.merge!(h2)
h1 # => {"a"=>1, "b"=>51, "c"=>2, "k"=>14, "d"=>8}

Related

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 order, uniq method

Variable fav_food can be either "pie", "cake", or "cookie", which will be inputed by the users. However, I want my food_qty Hash to list the fav_food as the first key.
Thus, I come out with
food_order = ([fav_food] + food_qty.keys).uniq
However, is there a better way to do this?
food_qty = {"pie" => 0, "cake" => 0, "cookie" => 0}
# make the fav_food listed first in the hash
food_order = ([fav_food] + food_qty.keys).uniq
Why do you want a particular key/value pair to be first in a hash? Hashes don't need to be ordered, because you can directly access any element at any time without any extra cost.
If you need to retrieve elements in an order, then get the keys and sort that list, then iterate over that list, or use values_at:
foo = {
'z' => 1,
'a' => 2
}
foo_keys = foo.keys.sort # => ["a", "z"]
foo_keys.map{ |k| foo[k] } # => [2, 1]
foo.values_at(*foo_keys) # => [2, 1]
Hashes remember their insertion order, but you shouldn't rely on that; Ordering a hash doesn't help if you insert something later, and other languages don't support it. Instead, order the keys however you want, and use that list to retrieve the values.
If you want to force a key to be first so its value is retrieved first, then consider this:
foo = {
'z' => 1,
'a' => 2,
'w' => 3,
}
foo_keys = foo.keys # => ["z", "a", "w"]
foo_keys.unshift(foo_keys.delete('w')) # => ["w", "z", "a"]
foo_keys.map{ |k| foo[k] } # => [3, 1, 2]
foo.values_at(*foo_keys) # => [3, 1, 2]
If you want a sorted list of keys with one forced to a position:
foo_keys = foo.keys.sort # => ["a", "w", "z"]
foo_keys.unshift(foo_keys.delete('w')) # => ["w", "a", "z"]
foo_keys.map{ |k| foo[k] } # => [3, 2, 1]
foo.values_at(*foo_keys) # => [3, 2, 1]
RE your first paragraph: Hashes are ordered though, specifically because this is such a common requirement and hashes fill so many roles in Ruby. There is no harm relying on hashes being ordered in Ruby, even if other languages don't support this behavior.
Not ordered, as in sorted, instead they remember their insertion order. From the documentation:
Hashes enumerate their values in the order that the corresponding keys were inserted.
This is easily tested/proven:
foo = {z:0, a:-1} # => {:z=>0, :a=>-1}
foo.to_a # => [[:z, 0], [:a, -1]]
foo[:b] = 3
foo.merge!({w:2})
foo # => {:z=>0, :a=>-1, :b=>3, :w=>2}
foo.to_a # => [[:z, 0], [:a, -1], [:b, 3], [:w, 2]]
foo.keys # => [:z, :a, :b, :w]
foo.values # => [0, -1, 3, 2]
If a hash was ordered foo.to_a would be collated somehow, even after adding additional key/value pairs. Instead, it remains in its insertion order. An ordered hash based on keys would move a:-1 to be the first element, just as an ordered hash based on the values would do.
If hashes were ordered, and, if it was important, we'd have some way of telling a hash what its ordering is, ascending or descending or of having some sort of special order based on the keys or values. Instead we have none of those things, and only have the sort and sort_by methods inherited from Enumerable, both of which convert the hash into an array and sort it and return the array, because Arrays can benefit from having an order.
Perhaps you are thinking of Java, which has SortedMap, and provides those sort of capabilities:
A Map that further provides a total ordering on its keys. The map is ordered according to the natural ordering of its keys, or by a Comparator typically provided at sorted map creation time. This order is reflected when iterating over the sorted map's collection views (returned by the entrySet, keySet and values methods). Several additional operations are provided to take advantage of the ordering.
Again, because Ruby's Hash does not sort ordering beyond its insertion order, we have none of those capabilities.
You could use Hash#merge:
food_qty = { "pie" => 0, "cake" => 0, "cookie" => 0 }
fav_food = "cookie"
{ fav_food => nil }.merge(food_qty)
# => { "cookie" => 0, "pie" => 0, "cake" => 0 }
This works because Hash#merge first duplicates the original Hash and then, for keys that already exist (like "cookie"), updates the values—which preserves the order of existing keys. In case it's not clear, the above is equivalent to this:
{ "cookie" => nil }.merge("pie" => 0, "cake" => 0, "cookie" => 0)
Edit: The below is my original answer, before I realized that I had also stumbled upon the "real" answer above.
I don't really advocate this (the Tin Man's advice should be taken instead), but if you're using Ruby 2.0+, I present the following Stupid Ruby Trick:
food_qty = { :pie => 0, :cake => 0, :cookie => 0}
fav_food = :cookie
{ fav_food => nil, **food_qty }
# => { :cookie => 0, :pie => 0, :cake => 0 }
This works because Ruby 2.0 added the "double splat" or "keyword splat" operator, as an analogue to the splat in an Array:
arr = [ 1, 2, *[3, 4] ] # => [ 1, 2, 3, 4 ]
hsh = { a: 1, b: 2, **{ c: 3 } } # => { :a => 1, :b => 2, :c => 3 }
...but it appears to do a reverse merge (a la ActiveSupport's Hash#reverse_merge), merging the "outer" hash into the "inner."
{ a: 1, b: 2, **{ a: 3 } } # => { :a => 1, :b => 2 }
# ...is equivalent to:
{ a: 3 }.merge( { a: 1, b: 2 } ) # => { :a => 1, :b => 2 }
The double splat was implemented to support keyword arguments in Ruby 2.0, which is presumably the reason why it only works if the "inner" Hash's keys are all Symbols.
Like I said, I don't recommend actually doing it, but I find it interesting nonetheless.

How to invert a hash, maintaining duplicate keys

From an initial hash t:
t = {"1"=>1, "2"=>2, "3"=>2, "6"=>3, "5"=>4, "4"=>1, "8"=>2, "9"=>2, "0"=>1, "7"=>1}
I need to swap the keys and values as follows:
t = {"1"=>1, "2"=>2, "3"=>2, "6"=>3, "5"=>4, "1"=>4, "8"=>2, "9"=>2, "1"=>0, "1"=>7}
While maintaining the structure of the hash (ie, without collapsing duplicate keys).
Then I'll make an array out of this hash.
Is there a way to do this? I tried this:
t.find_all{ |key,value| value == 1 } # pluck all elements with values of 1
#=> [["1", 1], ["4", 1], ["0", 1], ["7", 1]]
But it returns a new array, and the initial hash isn't changed.
The following doesn't work either:
t.invert.find_all{ |key,value| value == 1 }
#=> []
Here's a way to do this:
>> t = {"1" => 1, "2" => 2, "3" => 2, "6" => 3, "5" => 4, "4" => 1, "8" => 2, "9" => 2, "0" => 1, "7" => 1}
Hash#compare_by_identity allows for keys that are duplicates by value but unique by object id:
>> h = Hash.new.compare_by_identity
>> t.each_pair{ |k,v| h[v.to_s] = v.to_i }
The inverse hash of t:
>> h
#=> {"1" => 1, "2" => 2, "2" => 3, "3" => 6, "4" => 5, "1" => 4, "2" => 8, "2" => 9, "1" => 0, "1" => 7}
You can then use find_all to retrieve an array of elements without mutating h:
>> h.find_all{ |k,_| k == "1" }
#=> [["1", 1], ["1", 1], ["1", 1], ["1", 1]]
or keep_if to return the mutated h:
>> h.keep_if{ |k,_| k == "1" }
#=> {"1"=>1, "1"=>1, "1"=>1, "1"=>1}
>> h
#=> {"1"=>1, "1"=>1, "1"=>1, "1"=>1}
Note that this solution assumes you want to maintain the pattern of string keys and integer values in your hash. If you require integer keys, compare_by_identity won't be helpful to you.

Ruby printing all elements of a hash but last

Say I have:
hash = {"a" => 1, "b" => 2, "c" => 3}
keys = hash.keys
In order to print all the keys you would do:
keys.each {|x| puts x}
My question is, how do you print all the keys BUT the last one?
I'd do as below using Enumerable#take :
hash = {"a" => 1, "b" => 2, "c" => 3}
hash.take(hash.size-1).each do |k,_|
p k
end
# >> "a"
# >> "b"
Or, as below :
hash = {"a" => 1, "b" => 2, "c" => 3}
hash.keys.take(hash.size-1) # => ["a", "b"]
puts hash.keys.take(hash.size-1)
# >> a
# >> b
update (As asked by OP - Alright now how do I print just the last element explicitly?)
hash = {"a" => 1, "b" => 2, "c" => 3}
hash.keys.last # => "c"
Hash#keys will give you all the keys of that hash as an array. So, now you can call Array#last on that array to get the last element.
hash = {"a" => 1, "b" => 2, "c" => 3}
keys = hash.keys
keys[0..-2]
#=> ["a", "b"]
[0..-2] would index the array from the first to the one before the last
keys[0..-2].each { |x| puts x }
the last element in an array can be retrieved by calling last method on the array
keys.last
#=> "c"

How do I intersect one hash's keys with another and filter out the matching values?

I got this from the Rails doc:
{1 => 2}.diff(1 => 2) # => {}
{1 => 2}.diff(1 => 3) # => {1 => 2}
{}.diff(1 => 2) # => {1 => 2}
{1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
This is almost perfect but I don't want values that are in the hash passed as parameter and not in the calling hash.
What I want:
{}.diff(1 => 2) # => {}
{a: 1}.diff({a: 1, b: 2}) # => {} instead of {:b => 2}
Also, it must be as efficient as possible. For instance, I don't want to go over the second hash and check that each key that's inside doesn't appear in the first.
Any ideas?
Looking at the source is helpful here.
def diff(h2)
dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
end
Which iterates over every entry in the hash. I'm assuming you don't want to add an unnecessary iteration. So it's easier than the above
def my_diff(h2)
dup.delete_if { |k, v| h2[k] == v }
end
This is pretty easy:
a.select {|k, v| b.key?(k) && b[k] != v }
This is O(n), since both key? and Hash#[] are both O(1).

Resources