Ruby hash order, uniq method - ruby

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.

Related

Is there a way to add all properties of objects together in Ruby? [duplicate]

This question already has answers here:
ruby / merge-sum values from two distincts objects having the same keys
(2 answers)
Closed 6 days ago.
I'm wanting to do something like:
{:a => 1, :b => 2} + {:a => 2, :b => 1} = {:a => 3, :b => 3}
The best solution I can think of thus far is to run sum on each(they're in an array) but i would really prefer to not have to do that many iterations
Yes, you can achieve this by using the Hash#merge method, which combines the key-value pairs of two hashes. You can then iterate over the resulting hash and add up the values for each key. Here's an example:
hash1 = { :a => 1, :b => 2 }
hash2 = { :a => 2, :b => 1 }
result = hash1.merge(hash2) { |key, val1, val2| val1 + val2 }
#=> { :a => 3, :b => 3 }
The merge method combines the key-value pairs of hash1 and hash2, using a block to specify how to handle conflicts (in this case, we add up the values for each key). The resulting hash result contains the combined key-value pairs.
Note that this creates a new hash object, so it doesn't modify the original hashes.
a={:a => 1, :b => 2}
b={:a => 2, :b => 1}
p a.merge(b){|k,v1,v2| v1+v2}
Output:
{:a=>3, :b=>3}

How does `Hash#sort{|a, b| block}` work?

In ruby-doc I see this example:
h = { "a" => 20, "b" => 30, "c" => 10 }
h.sort {|a,b| a[1]<=>b[1]} #=> [["c", 10], ["a", 20], ["b", 30]]
Can anyone explain what a[1]<=>b[1] means? What are we comparing here? Is a is a key and b its value? Why we are comparing index 1?
a and b are both arrays of [key, value] which come from Hash#sort.
Converts hsh to a nested array of [ key, value ] arrays and sorts it, using Array#sort.
So a[1]<=>b[1] sorts the resulting pairs by the value. If it were a[0]<=>b[0] it would be sorting by the key.
Ruby doesn't have a key-value-pair or tuple datatype, so all Hash iteration methods (each, map, select, sort, …) represent hash entries as an Array with two elements, [key, value]. (In fact, most methods aren't even implemented in Hash, they are inherited from Enumerable and don't even know anything about keys and values.)
Meditate on this:
h = { "a" => 20, "b" => 30, "c" => 10 }
h.sort { |a,b| # => {"a"=>20, "b"=>30, "c"=>10}
a[1]<=>b[1]
} # => [["c", 10], ["a", 20], ["b", 30]]
For each loop of the key/value pairs in h, Ruby passes each key/value pair into the block as an array of two elements.

Array of objects or hash in Ruby [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
While working with a hash today in Ruby, I kept coming up against a few complications when sorting and accessing it. In my mind, I needed just a simple array.pop method to do what I needed. being that I just learned about class definitions, I had an idea and wanted to see if there was a reason not to do it this way.
hash = {"a" => 1, "b" => 2, "c" => 3}
Could I not do the same thing, but leave it open to more sorting methods and extensible data if I create this an array of objects containing data values?
Something like this
class Key
attr_accessor :value
def initialize (value)
#value = value
end
end
I'd then create an array of Key objects. I can then sort the array easier than the hash and still get the data from the inside the Keys. I figure this keeps things open for a more extensible bit of code if I find several bits of data need to be held together and sorted through.
Is this bad practice? Can you see a situation this would bite me? Am I solving a problem with a hammer because I just got one?
This ability is built into the Hash and Array data structures.
If you are using key-based access, get the keys and sort them:
hsh = {'a' => 1, 'c' => 3, 'b' => 2}
keys = hsh.keys.sort # keys: ['a', 'b', 'c']
If you need the values sorted, get the values and sort:
values = hsh.values.sort # values: [1, 2, 3]
Hash includes the Enumerable module which gives you all kinds of nifty ways of enumerating and sorting the hash.
irb(main):006:0> h = {'a' => 1, 'c' => 3, 'b' => 2}
=> {"a"=>1, "c"=>3, "b"=>2}
irb(main):007:0> h.sort { |a,b| b<=> a }
=> [["c", 3], ["b", 2], ["a", 1]]
irb(main):010:0> h = { 'three' => 3, 'one' => 1, 'two' => 2 }
=> {"three"=>3, "one"=>1, "two"=>2}
irb(main):011:0> h.sort { |a,b| a[0] <=> b[0] }
=> [["one", 1], ["three", 3], ["two", 2]]
irb(main):012:0> h.sort { |a,b| a[1] <=> b[1] }
=> [["one", 1], ["two", 2], ["three", 3]]
If you are looking for an array of hash values based on some key sorting:
irb(main):016:0> h.keys.sort.map { |key| h[key] }
=> [1, 3, 2]

Sort hash by order of keys in secondary array

I have a hash:
hash = {"a" => 1, "b" =>2, "c" => 3, "d" => 4}
And I have an array:
array = ["b", "a", "d"]
I would like to create a new array that is made up of the original hash values that correspond with original hash keys that are also found in the original array while maintaining the sequence of the original array. The desired array being:
desired_array = [2, 1, 3]
The idea here is to take the word "bad", assign numbers to the alphabet, and then make an array of the numbers that correspond with "b" "a" and "d" in that order.
Since your question is a little unclear I'm assuming you want desired_array to be an array (you say you want a new array and finish the sentence off with new hash). Also in your example I'm assuming you want desired_array to be [2, 1, 4] for ['b', 'a', 'd'] and not [2, 1, 3] for ['b', 'a', 'c'].
You should just you the Enumerable#map method to create a array that will map the first array to the your desired array like so:
desired_array = array.map { |k| hash[k] }
You should familiarize yourself with the Enumerable#map method, it's quite the handy method. From the rubydocs for the method: Returns a new array with the results of running block once for every element in enum. So in this case we are iterating through array and invoking hash[k] to select the value from the hash and creating a new array with values selected by the hash. Since iteration is in order, you will maintain the original sequence.
I would use Enumerable#map followed by Enumerable#sort_by, for example:
hash = {"d" => 4, "b" =>2, "c" => 3, "a" => 1}
order = ["b", "a", "d"]
# For each key in order, create a [key, value] pair from the hash.
# (Doing it this way instead of filtering the hash.to_a is O(n) vs O(n^2) without
# an additional hash-probe mapping. It also feels more natural.)
selected_pairs = order.map {|v| [v, hash[v]]}
# For each pair create a surrogate ordering based on the `order`-index
# (The surrogate value is only computed once, not each sort-compare step.
# This is, however, an O(n^2) operation on-top of the sort.)
sorted = selected_pairs.sort_by {|p| order.find_index(p[0]) }
p sorted
# sorted =>
# [["b", 2], ["a", 1], ["d", 4]]
I've not turned the result back into a Hash, because I am of the belief that hashes should not be treated as having any sort of order, except for debugging aids. (Do keep in mind that Ruby 2 hashes are ordered-by-insertion.)
All you need is values_at:
hash.values_at *array
Enumerable methods map, each works perfect
desired_array = array.map { |k| hash[k] }
or
desired_array = array.each { |k| hash[k] }

How can you easily test hash equality in Ruby when you only care about intersecting keys?

Say I have the following hashes:
hash_x = {
:a => 1,
:b => 2
}
hash_y = {
:b => 2,
:c => 3
}
I need a chunk of logic that compares the two for equality only taking into consideration intersecting keys.
In this example the 'b' key is the only commonality between the two hashes and it's value is set to '2' in both so by that logic these two hashes would be considered equal.
Likewise these two hashes would not be equal due to the inequality of the 'd' key (the 'a' and 'c' key values are ignored since they are unique to their respective hashes):
hash_p = {
:a => 1,
:b => 2,
:d => 3,
}
hash_q = {
:b => 2,
:c => 3,
:d => 4
}
Is there a clever one-liner in Ruby that can calculate the intersecting keys of the two hashes then compare their values for equality based on those keys?
Bonus points if you provide tests.
More bonus points if you monkey-patch it into the Hash class.
def compare_intersecting_keys(a, b)
(a.keys & b.keys).all? {|k| a[k] == b[k]}
end
Use like this:
compare_intersecting_keys(hash_x, hash_y) # => true
compare_intersecting_keys(hash_p, hash_q) # => false
If you want it monkey-patched:
class Hash
def compare_intersection(other)
(self.keys & other.keys).all? {|k| self[k] == other[k]}
end
end

Resources