||= with hash wrapped in a method - ruby

I put a hash in a method:
def example
#_hash ||= {:a => {}, :b => {}}
end
I call that hash from another method, and add key/value to the sub-hash like so:
example[:a][:c] = "test"
This will change the hash to:
{:a => {:c => "test"}, :b => {}}
I don't get why I can still update the :c hash by calling the method again like so:
example[:a][:c] = "test2" #=> {:a => {:c => "test2"}, :b => {}}
This is odd because the equals/or operator should not assign the values again if #_hash is not nil/false. So I assume we do example.[]= method to change the value of an individual hash key/value. If that's true, then why is it that, when I remove ||= from the hash method like so:
def example
#_hash = {:a => {}, :b => {}}
end
nothing is changed?

The reason for this is that, when you call the example method again, it correctly returns the following hash:
example #=> {:a => {:c => "test"}, :b => {}} # variable like syntax
example() #=> {:a => {:c => "test"}, :b => {}} # method like syntax
But, when you do this:
example[:a][:c] = "test2"
What you are really doing is:
hash = example() #=> {:a => {:c => "test"}, :b => {}}
hash[:a][:c] = "test2" #=> {:a => {:c => "test2"}, :b => {}}
And, therefore, the hash is updated.

Related

Combine keys and values in RUBY

Given a hash:
{:a => "123", :b => "345", :c => "678", :d => "910"}
Write a code that generates an array that combines the keys and values. So the resulting array should be:
["a123", "b345", "c678", "d910"]
I would do something like this:
{:a => "123", :b => "345", :c => "678", :d => "910"}.map { |k, v| "#{k}#{v}" }
#=> ["a123", "b345", "c678", "d910"]

Ruby, turn array of hashes into single hash

I have the following Array of Hashes:
a = [{:a => 1, :b => "x"}, {:a => 2, :b => "y"}]
I need to turn it into:
z={"x" => 1, "y" => 2}
or:
z={1 => "x", 2 => "y"}
Can I do this in a clean and functional way?
Something like this:
Hash[a.map(&:values)] # => {1=>"x", 2=>"y"}
if you want the other way:
Hash[a.map(&:values).map(&:reverse)] # => {"x"=>1, "y"=>2}
incorporating the suggestion from #squiguy:
Hash[a.map(&:values)].invert

set ruby hash element value by array of keys

here is what i got:
hash = {:a => {:b => [{:c => old_val}]}}
keys = [:a, :b, 0, :c]
new_val = 10
hash structure and set of keys can vary.
i need to get
hash[:a][:b][0][:c] == new_val
Thanks!
You can use inject to traverse your nested structures:
hash = {:a => {:b => [{:c => "foo"}]}}
keys = [:a, :b, 0, :c]
keys.inject(hash) {|structure, key| structure[key]}
# => "foo"
So, you just need to modify this to do a set on the last key. Perhaps something like
last_key = keys.pop
# => :c
nested_hash = keys.inject(hash) {|structure, key| structure[key]}
# => {:c => "foo"}
nested_hash[last_key] = "bar"
hash
# => {:a => {:b => [{:c => "bar"}]}}
Similar to Andy's, but you can use Symbol#to_proc to shorten it.
hash = {:a => {:b => [{:c => :old_val}]}}
keys = [:a, :b, 0, :c]
new_val = 10
keys[0...-1].inject(hash, &:fetch)[keys.last] = new_val

Create Nested Hashes from a List of Hashes in Ruby

I have a set of categories and their values stored as a list of hashes:
r = [{:A => :X}, {:A => :Y}, {:B => :X}, {:A => :X}, {:A => :Z}, {:A => :X},
{:A => :X}, {:B => :Z}, {:C => :X}, {:C => :Y}, {:B => :X}, {:C => :Y},
{:C => :Y}]
I'd like to get a count of each value coupled with its category as a hash like this:
{:A => {:X => 4, :Y => 1, :Z => 1},
:B => {:X => 2, :Z => 1},
:C => {:X => 1, :Y => 3}}
How can I do this efficiently?
Here's what I have so far (it returns inconsistent values):
r.reduce(Hash.new(Hash.new(0))) do |memo, x|
memo[x.keys.first][x.values.first] += 1
memo
end
Should I first compute the counts of all instances of specific {:cat => :val}s and then create the hash? Should I give a different base-case to reduce and change the body to check for nil cases (and assign zero when nil) instead of always adding 1?
EDIT:
I ended up changing my code and using the below method to have a cleaner way of achieving a nested hash:
r.map do |x|
[x.keys.first, x.values.last]
end.reduce({}) do |memo, x|
memo[x.first] = Hash.new(0) if memo[x.first].nil?
memo[x.first][x.last] += 1
memo
end
The problem of your code is: memo did not hold the value.
Use a variable outside the loop to hold the value would be ok:
memo = Hash.new {|h,k| h[k] = Hash.new {|hh, kk| hh[kk] = 0 } }
r.each do |x|
memo[x.keys.first][x.values.first] += 1
end
p memo
And what's more, it won't work to init a hash nested inside a hash directly like this:
# NOT RIGHT
memo = Hash.new(Hash.new(0))
memo = Hash.new({})
Here is a link for more about the set default value issue:
http://www.themomorohoax.com/2008/12/31/why-setting-the-default-value-of-a-hash-to-be-a-hash-is-wrong
Not sure what "inconsistent values" means, but your problem is the hash you're injecting into is not remembering its results
r.each_with_object(Hash.new { |h, k| h[k] = Hash.new 0 }) do |individual, consolidated|
individual.each do |key, value|
consolidated[key][value] += 1
end
end
But honestly, it would probably be better to just go to wherever you're making this array and change it to aggregate values like this.
Functional approach using some handy abstractions -no need to reinvent the wheel- from facets:
require 'facets'
r.map_by { |h| h.to_a }.mash { |k, vs| [k, vs.frequency] }
#=> {:A=>{:X=>4, :Y=>1, :Z=>1}, :B=>{:X=>2, :Z=>1}, :C=>{:X=>1, :Y=>3}}

How do I recursively define a Hash in Ruby from supplied arguments?

This snippet of code populates an #options hash. values is an Array which contains zero or more heterogeneous items. If you invoke populate with arguments that are Hash entries, it uses the value you specify for each entry to assume a default value.
def populate(*args)
args.each do |a|
values = nil
if (a.kind_of? Hash)
# Converts {:k => "v"} to `a = :k, values = "v"`
a, values = a.to_a.first
end
#options[:"#{a}"] ||= values ||= {}
end
end
What I'd like to do is change populate such that it recursively populates #options. There is a special case: if the values it's about to populate a key with are an Array consisting entirely of (1) Symbols or (2) Hashes whose keys are Symbols (or some combination of the two), then they should be treated as subkeys rather than the values associated with that key, and the same logic used to evaluate the original populate arguments should be recursively re-applied.
That was a little hard to put into words, so I've written some test cases. Here are some test cases and the expected value of #options afterwards:
populate :a
=> #options is {:a => {}}
populate :a => 42
=> #options is {:a => 42}
populate :a, :b, :c
=> #options is {:a => {}, :b => {}, :c => {}}
populate :a, :b => "apples", :c
=> #options is {:a => {}, :b => "apples", :c => {}}
populate :a => :b
=> #options is {:a => :b}
# Because [:b] is an Array consisting entirely of Symbols or
# Hashes whose keys are Symbols, we assume that :b is a subkey
# of #options[:a], rather than the value for #options[:a].
populate :a => [:b]
=> #options is {:a => {:b => {}}}
populate :a => [:b, :c => :d]
=> #options is {:a => {:b => {}, :c => :d}}
populate :a => [:a, :b, :c]
=> #options is {:a => {:a => {}, :b => {}, :c => {}}}
populate :a => [:a, :b, "c"]
=> #options is {:a => [:a, :b, "c"]}
populate :a => [:one], :b => [:two, :three => "four"]
=> #options is {:a => :one, :b => {:two => {}, :three => "four"}}
populate :a => [:one], :b => [:two => {:four => :five}, :three => "four"]
=> #options is {:a => :one,
:b => {
:two => {
:four => :five
}
},
:three => "four"
}
}
It is acceptable if the signature of populate needs to change to accommodate some kind of recursive version. There is no limit to the amount of nesting that could theoretically happen.
Any thoughts on how I might pull this off?
So here's some simple code that works.
def to_value args
ret = {}
# make sure we were given an array
raise unless args.class == Array
args.each do |arg|
case arg
when Symbol
ret[arg] = {}
when Hash
arg.each do |k,v|
# make sure that all the hash keys are symbols
raise unless k.class == Symbol
ret[k] = to_value v
end
else
# make sure all the array elements are symbols or symbol-keyed hashes
raise
end
end
ret
rescue
args
end
def populate *args
#options ||= {}
value = to_value(args)
if value.class == Hash
#options.merge! value
end
end
It does deviate from your test cases:
test case populate :a, :b => "apples", :c is a ruby syntax error. Ruby will assume the final argument to a method is a hash (when not given braces), but not a non-final one, as you assume here. The given code is a syntax error (no matter the definition of populate) since it assumes :c is a hash key, and finds an end of line when it's looking for :c's value. populate :a, {:b => "apples"}, :c works as expected
test case populate :a => [:one], :b => [:two, :three => "four"] returns {:a=>{:one=>{}}, :b=>{:two=>{}, :three=>"four"}}. This is consistent with the test case populate :a => [:b].
Ruby isn't Perl, => works only inside real Hash definition or as final argument in method call. Most things you want will result in a syntax error.
Are you sure that populate limited to cases supported by Ruby syntax is worth it?

Resources