Frequency of value in Ruby hash - ruby

If I have a hash of book ratings like
books = {"Gravity's Rainbow"=>:splendid,
"House in Hyde Park"=>:splendid,
"The Week"=>:quite_good
}
and I want to count the number of occurrences of a particular value, or rating, how is this done?
I tried books.values[:splendid].length - but I suppose the error is brought about because it thinks I want to slice everything up to "splendid", which is of the wrong type, from "books.values".
How do I remove everything not ":splendid" from books.values? Should I be looking at list operations rather than hash? I'm totally new to Ruby, thinking as I type. I'm not sure if books.values has returned a list or some other type though?

books.values returns an array:
books.values
#=> [:splendid, :splendid, :quite_good]
So you can just use Array#count:
books.values.count(:splendid)
#=> 2
Or something like this to count all values:
Hash[books.group_by { |k, v| v }.map { |k, v| [k, v.count] }]
#=> {:splendid=>2, :quite_good=>1}

Using Enumerable's #count method:
books.values.count { |v| v == :splendid }
As #Stefan answered, you can also shorten it by just books.values.count(:splendid)

Related

Understanding Ruby Code

I need some help understanding the below ruby code.
counted = Hash.new(0)
parsed_reponse["result"]["data"].each { |h| counted[h["version"]] += 1 }
counted = Hash[counted.map {|k,v| [k,v.to_s] }]
I understand line 1 creats a hash which I believe is similar to a
dictionary in python.
Line 2 loops through my json data set a adds a
new key and value pair if none exits and increments the count if 1
does exist.
What does the 3rd line do?
Your last line just converts all values to String:
Hash[{a: 2, b: 3}.map {|k, v| [k, v.to_s]}]
#=> {:a=>"2", :b=>"3"}
I would refactor it to:
counted.transform_values!(&:to_s) # for ruby >= 2.4
#=> {:a=>"1", :b=>"2"}
Or for older versions:
counted.each { |k, v| counted[k] = v.to_s }
Because:
counted.map {|k,v| [k,v.to_s] } - creates new array of arrays
Hash[result] - creates new Hash object from result array.
Both steps are redundant, you can just modify existing hash.

How to compare ruby hash with same key?

I have two hashes like this:
hash1 = Hash.new
hash1["part1"] = "test1"
hash1["part2"] = "test2"
hash1["part3"] = "test3"
hash2 = Hash.new
hash2["part1"] = "test1"
hash2["part2"] = "test2"
hash2["part3"] = "test4"
Expected output: part3
Basically, I want to iterate both of the hashes and print out "part3" because the value for "part3" is different in the hash. I can guarantee that the keys for both hashes will be the same, the values might be different. I want to print out the keys when their values are different?
I have tried iterating both hashes at once and comparing values but does not seem to give the right solution.
The cool thing about Ruby is that it is so high level that it is often basically English:
Print keys from the first hash if the values in the two hashes are different:
hash1.keys.each { |key| puts key if hash1[key] != hash2[key] }
Select the first hash keys that have different values in the two hashes and print each of them:
hash1.keys.select { |key| hash1[key] != hash2[key] }.each { |key| puts key }
Edit: I'll leave this should it be of interest, but #ndn's solution is certainly better.
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part3"]
hash1["part1"] = "test99"
p hash1.merge(hash2) { |_,v1,v2| v1==v2 }.reject { |_,v| v }.keys
# ["part1", "part3"]
This uses the form of Hash#merge that employs a block (here { |_,v1,v2| v1==v2 }) to determine the values of keys that are present in both hashes being merged. See the doc for an explanation of the three block variables, _, v1 and v2. The first block variable equals the common key. I've used the local variable _ for that, as is customary when the variable is not used in the block calculation.
The steps (for the original hash1):
g = hash1.merge(hash2) { |_,v1,v2| v1==v2 }
#=> {"part1"=>true, "part2"=>true, "part3"=>false}
h = g.reject { |_,v| v }
#=> {"part3"=>false}
h.keys
#=> ["part3"]
The obvious way is that of ndn, here a solution without blocks by converting to arrays, joining them and subtracting the elements that are the same, followed by converting back to hash and asking for the keys.
Next time it would be better to include what you tried so far.
((hash1.to_a + hash2.to_a) - (hash1.to_a & hash2.to_a)).to_h.keys
# ["part3"]

How would you simplify this ruby hash operation?

I need to create a hash which contains a number of parameters. If the value of the max_id parameter is nil though I want to remove the key from the hash:
params = { since_id: since_id, count: 50, max_id: max_id }
params.delete( :max_id ) unless max_id
The above code works fine but Ruby has so many nice hash and array operators that I wonder if there's an even cleaner way to write it (perhaps something using the splat operator).
Your solution looks good, although I'd do it the other way round:
params = { since_id: since_id, count: 50 }
params[:max_id] = max_id if max_id
You could also use reject:
params = params.reject {|key,value| value == nil }
A specific key
If you just want to check for :max_id then your solution:
params.delete( :max_id ) unless max_id
is the cleanest one. Notice that if max_id is false then the key-value pair will be deleted, therefore I suggest you to use the below version instead:
params.delete( :max_id ) if max_id.nil?
A generic key
The cleanest way I can think of, using hash methods, to delete a key-value pair if the value is nil, is by using Hash#reject!:
params.reject! { |k, v| v.nil? }
This will reject all key-value pairs that has nil as value in the params hash.
There are other alternatives. All of the followings lines are equivalent (except for their returned value):
params.reject! { |k, v| v.nil? }
params.select! { |k, v| not v.nil? }
params.delete_if { |k, v| not v.nil? }

Select Hash Value when Key is an Array

I have a hash that has some keys as an array like so:
foo = {[45, 121]=>:some_field}
How can I select :some_field where a foo key contains 45?
And secondary to that, if it finds a match, how do I retrieve the other elements in the same key?
Although you can do this, it kind of defeats the purpose of using a hash since you will have to do a linear scan through the entire thing. It would be a lot better to have multiple hash keys for the same value since you can use the hash as an index then.
Example:
found = foo.find { |k, v| k.include?(n) }
found and found[1]
Keep in mind the performance of this will be terrible if you have large numbers of entries in the key and a large number of items in the hash since it will have to test against all keys and all values individually.
foo = {[45, 121]=>:some_field}
foo.detect{ |k,v| k.include? 45 }
#=> [[45, 121], :some_field]
foo.detect{ |k,v| k.include? 45 }.last
#=> :some_field
I would suggest to reverse your hash if it's not one element only:
foo = {[45, 121]=>:some_field, [1, 45, 7] => :some_other_field}
bar = {}
foo.each do |k, v|
k.each do |x|
if bar.has_key?(x)
bar[x] << [[k, v]]
else
bar[x] = [[k, v]]
end
end
end
p bar[45]

Determine if a value exists in an array of hashes

I have the following:
array_of_hashes = [{:a=>10, :b=>20}, {:a=>11, :b=>21}, {:a=>13, :b=>23}]
How would I go about finding if :a=>11 exists in array_of_hashes
array_of_hashes.include? does not seem to work
array_of_hashes.any? {|h| h[:a] == 11}
You did ask for a boolean result in the OQ, but if you really want the hash element itself do:
array_of_hashes.detect { |h| h[:a] == 11 }
If you want the result really fast you could group the original object and then get the result with a single hash lookup:
t = array_of_hashes.group_by { |x| x[:a] }
t[11]

Resources