I want to have a hash whose key is a string and the value is an array. I tried it the following way:
h = Hash.new([]) # => {}
h["one"] # => []
h["two"] # => []
h["one"].push 1 # => [1]
h["one"] # => [1]
h["two"] # => [1] //why did it assign it to h["two"] also??
What is the correct way to do it?
You get this behavior because [] that you passed into new method isn't copied but referenced in all unset hash keys. So h['one'] references the same object as h['two']. So if you modify object referenced by h['one'] (using push method), h['two'] will also be modified.
The correct way to set default value which will be initialized for every single hash key is to use block:
h = Hash.new { |hash, key| hash[key] = [] }
I usually do it like this:
h = Hash.new { |h,k| h[k] = [] }
h['one']
h
# => { 'one' => [] }
h['two'] << 12
h
# => { 'one' => [], 'two' => [12] }
Which is more verbose and (IMO) reads nicer.
Related
I currently have:
#teams ||= {}
#teams[date] ||= {}
#teams[date][team] ||= {}
#teams[date][team][raw_address] ||= {}
#teams[date][team][raw_address]['address'] = address
#teams[date][team][raw_address]['orders'] ||= {}
#teams[date][team][raw_address]['orders'][order.id] = order
Is it possible to "remove" the lines containing ||= {}? I just want to have something like:
#teams[date][team][raw_address]['address'] = address
#teams[date][team][raw_address]['orders'][order.id] = order
You might know that in Ruby, hashes can have default values.
So when initially creating the #team hash, you could set a default value of e.g. empty hash:
#team = Hash.new { |h, k| h[k] = Hash.new }
Which gives you:
#team[:foo] #=> {}
And the ability to set values in those dynamically created hashes, e.g.:
#team[:foo][:bar] = 123
#team[:foo][:baz] = 456
#team
#=> {
# :foo => {
# :bar => 123,
# :baz => 456
# }
# }
But that only works for the first level. To get a hash that can be nested indefinitely, you have to pass along the outer hash's default_proc to the inner hash:
#team = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
Doing so allows you to create deeply nested hashes by just assigning the last element, e.g.:
#team[:foo][:bar][:baz] = 123
#team[:foo][:bar][:qux][:quux] = 456
#team
#=> {
# :foo=> {
# :bar => {
# :baz => 123,
# :qux => {
# :quux => 456
# }
# }
# }
# }
I have:
people=["Bob","Fred","Sam"]
holidays = Hash.new
people.each do |person|
a=Array.new
holidays[person]=a
end
gifts = Hash.new
people.each do |person|
a=Array.new
gifts[person]=a
end
Feels clunky. I can't seem to figure a more streamline way with an initialization block or somesuch thing. Is there an idiomatic approach here?
Ideally, I'd like to keep an array like:
lists["holidays","gifts",...]
... and itterate through it to initialize each element in the lists array.
people = %w|Bob Fred Sam|
data = %w|holidays gifts|
result = data.zip(data.map { people.zip(people.map { [] }).to_h }).to_h
result['holidays']['Bob'] << Date.today
#⇒ {
# "holidays" => {
# "Bob" => [
# [0] #<Date: 2016-11-04 ((2457697j,0s,0n),+0s,2299161j)>
# ],
# "Fred" => [],
# "Sam" => []
# },
# "gifts" => {
# "Bob" => [],
# "Fred" => [],
# "Sam" => []
# }
# }
More sophisticated example would be:
result = data.map do |d|
[d, Hash.new { |h, k| h[k] = [] if people.include?(k) }]
end.to_h
The latter produces the “lazy initialized nested hashes.” It uses the Hash#new with a block constructor for nested hashes.
Play with it to see how it works.
A common way of doing that would be to use Enumerable#each_with_objrect.
holidays = people.each_with_object({}) { |p,h| h[p] = [] }
#=> {"Bob"=>[], "Fred"=>[], "Sam"=>[]}
gifts is the same.
If you only want a number of such hashes then, the following should suffice:
count_of_hashes = 4 // lists.count; 4 is chosen randomly by throwing a fair die
people = ["Bob", "Fred", "Sam"]
lists = count_of_hashes.times.map do
people.map {|person| [person, []]}.to_h
end
This code also ensures the arrays and the hashes all occupy their own memory. As can be verified by the following code:
holidays, gifts, *rest = lists
holidays["Bob"] << "Rome"
And checking the values of all the other hashes:
lists
=> [
{"Bob"=>["Rome"], "Fred"=>[], "Sam"=>[]},
{"Bob"=>[], "Fred"=>[], "Sam"=>[]},
{"Bob"=>[], "Fred"=>[], "Sam"=>[]},
{"Bob"=>[], "Fred"=>[], "Sam"=>[]}
]
This question already has answers here:
Flattening nested hash to a single hash with Ruby/Rails
(6 answers)
Closed 8 years ago.
I fetch a JSON document and need to programmatically "flatten" the keys for another third-party service.
What this means is, if my JSON doc comes back with the following:
{'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
I need to be able to know to create a "flat" key-value pair for a third-party service like this:
first_name = "Joe"
hoffman.patterns = "negativity, self-sabotage"
hoffman.right_road = "happy family"
mbti = "INTJ"
Once I know there's a sub-document, the parsing I think I have figured out just appending the sub-keys with key + '.' + "{subkey}" but right now, don't know which ones are straight key-value and which one's have sub-documents.
Question:
a) How can I parse the JSON to know which keys have sub-documents (additional key-values)?
b) Suggestions on ways to create a string from an array
You could also monkey patch Hash to do this on it's own like so:
class Hash
def flatten_keys(prefix=nil)
each_pair.map do |k,v|
key = [prefix,k].compact.join(".")
v.is_a?(Hash) ? v.flatten_keys(key) : [key,v.is_a?(Array) ? v.join(", ") : v]
end.flatten.each_slice(2).to_a
end
def to_flat_hash
Hash[flatten_keys]
end
end
Then it would be
require 'json'
h = JSON.parse(YOUR_JSON_RESPONSE)
#=> {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Will work with additional nesting too
h = {"first_name"=>"Joe", "hoffman"=>{"patterns"=>["negativity", "self-sabotage"], "right_road"=>"happy family", "wrong_road"=>{"bad_choices"=>["alcohol", "heroin"]}}, "mbti"=>"INTJ"}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "hoffman.wrong_road.bad_choices"=>"alcohol, heroin", "mbti"=>"INTJ"}
Quick and dirty recursive proc:
# assuming you've already `JSON.parse` the incoming json into this hash:
a = {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
# define a recursive proc:
flatten_keys = -> (h, prefix = "") do
#flattened_keys ||= {}
h.each do |key, value|
# Here we check if there's "sub documents" by asking if the value is a Hash
# we also pass in the name of the current prefix and key and append a . to it
if value.is_a? Hash
flatten_keys.call value, "#{prefix}#{key}."
else
# if not we concatenate the key and the prefix and add it to the #flattened_keys hash
#flattened_keys["#{prefix}#{key}"] = value
end
end
#flattened_keys
end
flattened = flatten_keys.call a
# => "first_name"=>"Joe", "hoffman.patterns"=>["negativity", "self-sabotage"], "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
And then, to turn the arrays into strings just join them:
flattened.inject({}) do |hash, (key, value)|
value = value.join(', ') if value.is_a? Array
hash.merge! key => value
end
# => {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Another way, inspired by this post:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
result = {}
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
.each{ |k, v| result[k.join('.')] = v } #=> {"a.b.c"=>1, "a.b.d"=>2, "a.e"=>3, "f"=>4}
This is my code:
def mainFunction
#notes=Hash.new(Array.new)
#notes["First"].push(true)
#notes["First"].push(false)
#notes["First"].push(true)
#notes["Second"].push(true)
output #notes.size.to_s
end
Why is the output 0? It should be 2 since notes has two keys, "First" and "Second".
When initializing Hash values when accessed for the first time (ie. pushing onto key'd values that don't yet exist), you need to set the key on the Hash when it is requested.
#notes = Hash.new {|h, k| h[k] = []}
For reference, see the following result in the ruby repl initializing the Hash as you have
irb(main):063:0> #notes = Hash.new(Array.new)
=> {}
irb(main):064:0> #notes[:foo].push(true)
=> [true]
irb(main):065:0> #notes.has_key?(:foo)
=> false
irb(main):066:0> puts #notes.size
=> 0
and now the proper way.
irb(main):067:0> #notes = Hash.new {|h, k| h[k] = []}
=> {}
irb(main):068:0> #notes[:foo].push(true)
=> [true]
irb(main):069:0> #notes.has_key?(:foo)
=> true
irb(main):070:0> #notes.size
=> 1
The following statement:
#notes=Hash.new(Array.new)
Creates a hash with a default value: Array.new. When you access the hash with an unknown key, you will receive this default value.
Your other statements therefor change that default array:
#notes["First"].push(true)
This adds true to the default (empty) array.
You need to initialize the "First" array first:
#notes["First"] = []
And then you can add values:
#notes["First"].push(true)
Or more "rubyish":
#notes["First"] << true
The size now is:
#notes.size # => 1
#notes = Hash.new(Array.new) sets the default value for all elements to the same array. Since #notes contains no key "First", it returns that default value, and the .push adds to the end of it. But the key has not been added to the hash. The other pushes add to the end of the same default array.
Read this Hash.new(obj). It states:
If obj is specified, this single object will be used for all default
values.
Hence, when you do: #notes=Hash.new(Array.new). The default object will be an array.
#notes.default # => []
#notes['First'].push(true)
#notes.default # => [true]
#notes['First'].push(false)
#notes.default # => [true, false]
But, there won't be any entry as a key in #notes, of course you use access those values by giving any key(which may or may not exist), like this:
#notes['unknown_key'] #=> [true, false]
Hash.new(object) method call form just return object as a default value but does not update the hash.
You are looking for a block form where you can do the update:
#notes=Hash.new {|h, k| h[k] = []}
This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 7 years ago.
hash = Hash.new(Hash.new([]))
hash[1][2] << 3
hash[1][2] # => [3]
hash # => {}
hash.keys # => []
hash.values # => []
What's going on? Ruby's hiding data (1.9.3p125)
What's going on? Ruby's hiding data (1.9.3p125)
Ruby hides neither data nor its docs.
Default value you pass into the Hash constructor is returned whenever the key is not found in the hash. But this default value is never actually stored into the hash on its own.
To get what you want you should use Hash constructor with block and store default value into the hash yourself (on both levels of your nested hash):
hash = Hash.new { |hash, key| hash[key] = Hash.new { |h, k| h[k] = [] } }
hash[1][2] << 3
p hash[1][2] #=> [3]
p hash #=> {1=>{2=>[3]}}
p hash.keys #=> [1]
p hash.values #=> [{2=>[3]}]
It's simple. If you pass an object to a Hash constructor, it'll become a default value for all missing keys in that hash. What's interesting is that this value is mutable. Observe:
hash = Hash.new(Hash.new([]))
# mutate default value for nested hash
hash[1][2] << 3
# default value in action
hash[1][2] # => [3]
# and again
hash[1][3] # => [3]
# and again
hash[1][4] # => [3]
# set a plain hash (without default value)
hash[1] = {}
# what? Where did my [3] go?
hash[1][2] # => nil
# ah, here it is!
hash[2][3] # => [3]
I get a try with this in irb.
Seams Ruby does not tag element as "visible" except affecting value over default explicitly via = for instance.
hash = Hash.new(Hash.new([]))
hash[1] = Hash.new([])
hash[1][2] = [3]
hash
#=> {1=>{2=>[3]}}
May be some setters are missing this "undefaulting" behavior ...