Hash destructuring in Ruby 3 - ruby

I Ruby 2 you could do the following:
my_hash = {a: {aa: 1, ab: 2, ac: 3}}
my_hash.each do |key, aa:, ab: 4, **|
puts key
puts aa
puts ab
end
In Ruby 3 this now results in missing keywords :aa, :ab. What would be the best way to refactor code like this in Ruby 3?
Something like the following would not work because it doesn't support setting default values:
my_hash.each do |key, values|
values in {aa: aa, ab: ab}
end
The best way I can think of is putting the existing code in a wrapper:
lambda = ->(key, aa:, ab: 4, **) do
puts key
puts aa
puts ab
end
my_hash.each do |key, values|
lambda.call(key, **values)
end
Any better options?

I can't think of a way to convert the hash to keyword arguments.
But why not use the hash the way it is, i.e. without treating its keys like keywords? Unless your actual code is more complex, fetch seems to do what you want:
my_hash = {a: {aa: 1, ab: 2, ac: 3}}
my_hash.each do |key, values|
puts key
puts values.fetch(:aa)
puts values.fetch(:ab, 4)
end
The above works fine in both, Ruby 2 and 3. Just like your example, it will raise an error if :aa is missing and it will use a default value of 4 if :ab is missing.

with ||
As stated by #Stefan it will not works if the value can contains nil or false
my_hash = {a: {aa: 1, ac: 3}}
my_hash.each do |key, values|
puts key
puts values[:aa]
puts values[:ab] || 4
end
with key?
my_hash = {a: {aa: 1, ac: 3}}
default_values_hash = { :ab => 4 }
my_hash.each do |key, values|
puts key
puts values[:aa]
puts values.key?(:ab) ? values[:ab] : default_values_hash[:ab]
end
with default
my_hash = {a: {aa: 1, ab: 2, ac: 3}}
my_hash.each do |key, values|
values.default = :notfound
puts key
puts values[:aa]
puts values[:ab] == :notfound ? 4 : values[:ab]
end
my_hash = {a: {aa: 1, ac: 3}}
my_hash.each do |key, values|
values.default = :notfound
puts key
puts values[:aa]
puts values[:ab] == :notfound ? 4 : values[:ab]
end
with default-proc
my_hash = {a: {aa: 1, ab: 2, ac: 3}}
default_values_hash = { :ab => 4 }
my_hash.each do |key, values|
values.default_proc = proc { |hash, key| default_values_hash[key] || :notfound }
puts key
puts values[:aa]
puts values[:ab]
end
with merge
my_hash = {a: {aa: 1, ac: 3}}
default_values_hash = { :ab => 4 }
my_hash.each do |key, values|
values = default_values_hash.merge(values)
puts key
puts values[:aa]
puts values[:ab]
end
All above code works in both Ruby 2 and 3
Note:
I would suggest the key? method to test and check if key exists and handle the case accordingly as it would be much faster as comparing to other cases which require additional steps internally.

If you don't need to use default value but need just destruct hash, you can use => operator for pattern matching
my_hash = { a: { aa: 1, ab: 2, ac: 3 } }
my_hash.each do |key, values|
values => { aa:, ab:, ac: }
puts key, aa, ab, ac
end
This code will raise NoMatchingPatternKeyError if values don't include all keys

We can assign each value to a variable and check if it is hash or not then we can print the values again in inner loop
my_hash = {a: { aa: 1, ab: 2, ac:3 }}
may_hash.each do |key, value|
puts key
if value.kind_of(Hash)
value.each do |inner_key, inner_value|
puts inner_key
puts inner_value
end
else
puts value
end
end
In this we can refactor outer hash and inner hash at same time.

Related

Merge Ruby Hash values with same key

Is this possible to achieve with selected keys:
Eg
h = [
{a: 1, b: "Hello", c: "Test1"},
{a: 2, b: "Hey", c: "Test1"},
{a: 3, b: "Hi", c: "Test2"}
]
Expected Output
[
{a: 1, b: "Hello, Hey", c: "Test1"}, # See here, I don't want key 'a' to be merged
{a: 3, b: "Hi", c: "Test2"}
]
My Try
g = h.group_by{|k| k[:c]}.values
OUTPUT =>
[
[
{:a=>1, :b=>"Hello", :c=>"Test1"},
{:a=>2, :b=>"Hey", :c=>"Test1"}
], [
{:a=>3, :b=>"Hi", :c=>"Test2"}
]
]
g.each do |v|
if v.length > 1
c = v.reduce({}) do |s, l|
s.merge(l) { |_, a, b| [a, b].uniq.join(", ") }
end
end
p c #{:a=>"1, 2", :b=>"Hello, Hey", :c=>"Test1"}
end
So, the output I get is
{:a=>"1, 2", :b=>"Hello, Hey", :c=>"Test1"}
But, I needed
{a: 1, b: "Hello, Hey", c: "Test1"}
NOTE: This is just a test array of HASH I have taken to put my question. But, the actual hash has a lots of keys. So, please don't reply with key comparison answers
I need a less complex solution
I can't see a simpler version of your code. To make it fully work, you can use the first argument in the merge block instead of dismissing it to differentiate when you need to merge a and b or when you just use a. Your line becomes:
s.merge(l) { |key, a, b| key == :a ? a : [a, b].uniq.join(", ") }
Maybe you can consider this option, but I don't know if it is less complex:
h.group_by { |h| h[:c] }.values.map { |tmp| tmp[0].merge(*tmp[1..]) { |key, oldval, newval| key == :b ? [oldval, newval].join(' ') : oldval } }
#=> [{:a=>1, :b=>"Hello Hey", :c=>"Test1"}, {:a=>3, :b=>"Hi", :c=>"Test2"}]
The first part groups the hashes by :c
h.group_by { |h| h[:c] }.values #=> [[{:a=>1, :b=>"Hello", :c=>"Test1"}, {:a=>2, :b=>"Hey", :c=>"Test1"}], [{:a=>3, :b=>"Hi", :c=>"Test2"}]]
Then it maps to merge the first elements with others using Hash#merge
h.each_with_object({}) do |g,h|
h.update(g[:c]=>g) { |_,o,n| o.merge(b: "#{o[:b]}, #{n[:b]}") }
end.values
#=> [{:a=>1, :b=>"Hello, Hey", :c=>"Test1"},
# {:a=>3, :b=>"Hi", :c=>"Test2"}]
This uses the form of Hash#update that employs a block (here { |_,o,n| o.merge(b: "#{o[:b]}, #{n[:b]}") }) to determine the values of keys that are present in both hashes being merged. The first block variable holds the common key. I’ve used an underscore for that variable mainly to signal to the reader that it is not used in the block calculation. See the doc for definitions of the other two block variables.
Note that the receiver of values equals the following.
h.each_with_object({}) do |g,h|
h.update(g[:c]=>g) { |_,o,n| o.merge(b: "#{o[:b]}, #{n[:b]}") }
end
#=> { “Test1”=>{:a=>1, :b=>"Hello, Hey", :c=>"Test1"},
# “Test2=>{:a=>3, :b=>"Hi", :c=>"Test2"} }

Counting unique occurrences of values in a hash

I'm trying to count occurrences of unique values matching a regex pattern in a hash.
If there's three different values, multiple times, I want to know how much each value occurs.
This is the code I've developed to achieve that so far:
def trim(results)
open = []
results.map { |k, v| v }.each { |n| open << n.to_s.scan(/^closed/) }
puts open.size
end
For some reason, it returns the length of all the values, not just the ones I tried a match on. I've also tried using results.each_value, to no avail.
Another way:
hash = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
hash.each_with_object(Hash.new(0)) {|(k,v),h| h[v]+=1 if v.start_with?('foo')}
#=> {"foo"=>2}
or
hash.each_with_object(Hash.new(0)) {|(k,v),h| h[v]+=1 if v =~ /^foo|bar/}
#=> {"foo"=>2, "bar"=>1}
Something like this?
hash = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
groups = hash.group_by{ |k, v| v[/(?:foo|bar)/] }
# => {"foo"=>[[:a, "foo"], [:d, "foo"]],
# "bar"=>[[:b, "bar"]],
# nil=>[[:c, "baz"]]}
Notice that there is a nil key, which means the regex didn't match anything. We can get rid of it because we (probably) don't care. Or maybe you do care, in which case, don't get rid of it.
groups.delete(nil)
This counts the number of matching "hits":
groups.map{ |k, v| [k, v.size] }
# => [["foo", 2], ["bar", 1]]
group_by is a magical method and well worthy of learning.
def count(hash, pattern)
hash.each_with_object({}) do |(k, v), counts|
counts[k] = v.count{|s| s.to_s =~ pattern}
end
end
h = { a: ['open', 'closed'], b: ['closed'] }
count(h, /^closed/)
=> {:a=>1, :b=>1}
Does that work for you?
I think it worths to update for RUBY_VERSION #=> "2.7.0" which introduces Enumerable#tally:
h = {a: 'foo', b: 'bar', c: 'baz', d: 'foo'}
h.values.tally #=> {"foo"=>2, "bar"=>1, "baz"=>1}
h.values.tally.select{ |k, _| k=~ /^foo|bar/ } #=> {"foo"=>2, "bar"=>1}

Why the functional differences in Hash initialization?

Is there documentation on the differences of initialization? The docs on Hash didn't have anything that would explain the difference.
foo = [1,2,3,4]
test1 = Hash.new([])
test2 = Hash.new{|h,k| h[k] = []}
foo.each do |i|
test1[i] << i
test2[i] << i
end
puts "test 1: #{test1.size}" #0
puts "test 2: #{test2.size}" #4
There is mentioning in the doc. Read the doc:
new(obj) → new_hash
new {|hash, key| block } → new_hash
[...] If obj is specified, this single object will be used for all default values. If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.
h = Hash.new("Go Fish")
h["a"] = 100
h["b"] = 200
h["a"] #=> 100
h["c"] #=> "Go Fish"
# The following alters the single default object
h["c"].upcase! #=> "GO FISH"
h["d"] #=> "GO FISH"
h.keys #=> ["a", "b"]
# While this creates a new default object each time
h = Hash.new { |hash, key| hash[key] = "Go Fish: #{key}" }
h["c"] #=> "Go Fish: c"
h["c"].upcase! #=> "GO FISH: C"
h["d"] #=> "Go Fish: d"
h.keys #=> ["c", "d"]
This is a common gotcha. With test1 (the non-block) you are modifying the default object, the thing which you get when the key does not exist in the hash.
foo = [1,2,3,4]
test1 = Hash.new([])
test2 = Hash.new{|h,k| h[k] = []}
foo.each do |i|
test1[i] << i
test2[i] << i
p test1['doesnotexist'] #added line
end
puts "test 1: #{test1.size}" #0
puts "test 2: #{test2.size}" #4
Output:
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
test 1: 0
test 2: 4
There's a difference, in some situation it could be significant
test1 = Hash.new([])
test2 = Hash.new{|h,k| h[k] = []}
test1['foo'] #=> []
test2['foo'] #=> []
test1.keys == test2.keys #=> false
The first construction just returns the default value but doesn't do anything with current hash but second construction initialize the hash with key/value where value is calculated by given block.

Search Ruby hash for value?

Trying to search through a hash for a value, no methods I have tried previously have worked.
def input
#search_term = STDIN.gets.chomp
end
def execute
#reader.searchKey(#search_term).each{|b| puts b}
end
def searchKey(search_term)
puts books_catalogue.has_value?(search_term)
end
hash = {foo: 'val', bar: 'other_val', bak: 'val'}
selected_hash = hash.select { |k,v| v == 'val' } # => {foo: 'val', bak: 'val'}
selected_hash.keys # => [:foo, :bak]
So the method looks like:
def search_key(value)
#hash.select { |k, v| v == value }.keys
end
Try this:
hash = {a: 1, b: 2, c: 2}
value_to_search_for = 2
hash.select {|_,value| value == value_to_search_for}
# output is {b: 2, c: 2}

Possible to separate out key and value of a hash when using inject?

When calling each on a hash in ruby, you can get the key and value nicely separated like this:
{ :a => 1, :b => 2, :c => 3 }.each do |key, value|
puts "key is #{key} and value is #{value}"
end
=========================
key is :a and value is 1
key is :b and value is 2
key is :c and value is 3
=> {:a=>1, :b=>2, :c=>3}
However this doesn't seem to work when using inject.
{ :a => 1, :b => 2, :c => 3 }.inject(0) do |result, key, value|
puts "key is #{key} and value is #{value}"
result + value
end
=========================
key is [:a, 1] and value is
TypeError: nil can't be coerced into Fixnum
In the simplified example above I don't really need the keys so I could just call hash.values.inject, but assuming I need both, is there a cleaner way to do this than this horrible bodge?
{ :a => 1, :b => 2, :c => 3 }.inject(0) do |result, key_and_value|
puts "key is #{key_and_value[0]} and value is #{key_and_value[1]}"
result + key_and_value[1]
end
It looks like you need:
{ :a => 1, :b => 2, :c => 3 }.inject(0) do |result, (key, value)|
puts "key is #{key} and value is #{value}"
result + value
end

Resources