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.
I created a hash that has an array as a value.
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
I want the sum of all values in each array. I expect to have such output as:
{
"0":[24],
"1":[14],
"2":[23],
"3":[23],
"4":[34],
"5":[47],
"6":[12],
"7":[],
"8":[26],
"9":[39],
"10":[23],
"11":[27]
}
I do not know how to proceed from here. Any pointers are thankful.
I would do something like this:
hash = { "0" => [0,14,0,14], "1" => [0,14], "7" => [] }
hash.each { |k, v| hash[k] = Array(v.reduce(:+)) }
# => { "0" => [28], "1" => [14], "7" => [] }
For hash object you as this one.
hash = {"0"=>[0,14,0,14],"1"=>[0,14],"2"=>[0,11,0,12],"3"=>[0,11,0,12],"4"=>[0,10,0,13,0,11],"5"=>[0,10,0,14,0,0,0,11,12,0],"6"=>[0,0,12],"7"=>[],"8"=>[0,14,0,12],"9"=>[0,14,0,0,11,14],"10"=>[0,11,0,12],"11"=>[0,13,0,14]}
You could change value of each k => v pair
hash.each_pair do |k, v|
hash[k] = [v.reduce(0, :+)]
end
will resolve in
hash = {"0"=>[28], "1"=>[14], "2"=>[23], "3"=>[23], "4"=>[34], "5"=>[47], "6"=>[12], "7"=>[0], "8"=>[26], "9"=>[39], "10"=>[23], "11"=>[27]}
If your string is just like you mentioned you can parse it using JSON, or jump that step if you have already an Hash.
You can check the documentation of inject here
require 'json'
json_string = '
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
'
hash = JSON.parse json_string
result = Hash.new
hash.each do |key, value|
result[key] = value.inject(:+)
end
puts result.inspect
and the result:
{"0"=>28, "1"=>14, "2"=>23, "3"=>23, "4"=>34, "5"=>47, "6"=>12, "7"=>nil, "8"=>26, "9"=>39, "10"=>23, "11"=>27}
Classic example for map/reduce. You need to iterate on the hash keys and values (map {|k,v|}), and for each value count the sum using reduce(0) {|acc, x| acc+x}
h = {1 => [1,2,3], 2 => [3,4,5], 7 => []} #example
a = h.map {|k,v| [k ,v.reduce(0) {|acc, x| acc+x}] }
=> [[1, 6], [2, 12], [7, 0]]
Hash[a]
=> {1=>6, 2=>12, 7=>0}
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}
Typically, we define a hash as
h={:a=>val1, :b=>val2}
However, i want to add a condition to only add in the key :b if val2 is not a nil value. Something like
h={:a=>val1}
h[:b]=val2 if val2
But can it be encapsulated in a single line?
h = { :a => val1 }.merge(val2 ? { :b => val2 } : {})
But don't do this. Just keep it simple.
You don't have to worry about nil elements in hash, because you can simply clean up hash from them:
{:a => 1, :b => nil}.reject { |k, v| v.nil? } # {:a => 1}
h[:b] = val unless val.nil?
as of ruby 2.4, you can use Hash#compact
h = { a: 1, b: false, c: nil }
h.compact #=> { a: 1, b: false }
h #=> { a: 1, b: false, c: nil }
You could override the []= operator for just that one hash, or make a subclass of Hash and override it there.
hash = {}
class << hash
def []=(key, value)
case key
when :b
raise StandardError("Invalid :b value") if value.nil?
end
super(key,value)
end
end
hash[:a] = 10
hash[:b] = nil # Will raise exception