Is there a more elegant way to write this code?
def create_a_hash_from_a_collection
my_hash = {}
collection_of_hashes.each do |key, value|
my_hash[key] = {} if my_hash[key].nil?
my_hash[key] = value
end
my_hash
end
The line that seems clumsy to me is this:
my_hash[key] = {} if my_hash[key].nil?
Is there a shorthand way of expressing it?
If you want to have a brand new hash for each key in your initial hash you need to initialise it with a block:
hash = Hash.new { |hash, key| hash[key] = {} }
hash[:foo].object_id == hash[:bar].object_id #=> false
Otherwise, if you do this, It will be always the same default hash
hash = Hash.new({})
hash[:foo].object_id == hash[:bar].object_id #=> true
You can use ||= operator which does exactly what you want
my_hash[key] ||= {}
The rest of my answer here is because I'm not sure what a "collection of hashes" is, so my best guess is that it would be an array of hashes. If I'm wrong, let me know and disregard the rest of this answer.
It seems that the rest of your method may not do what it sounds like you're trying to do. Consider:
#collection_of_hashes = [{foo: 'bar'}, {baz: 'qux'}]
def create_a_hash_from_a_collection
my_hash = {}
#collection_of_hashes.each do |key, value|
# this is not actually doing anything here and returns same with or
# without the following line
# my_hash[key] ||= {}
my_hash[key] = value
end
my_hash
end
#=> {{:foo=>"bar"}=>nil, {:baz=>"qux"}=>nil}
But what you probably want is
def create_a_hash_from_a_collection
my_hash = {}
#collection_of_hashes.each do |hash|
hash.keys.each do |k|
my_hash[k] = hash[k]
end
end
my_hash
end
#=> {:foo=>"bar", :baz=>"qux"}
But also keep in mind, if any of your "collection of hashes" which we would tend to assume would be an array of hashes, contain the same key, which one wins? This code, it would be the last item in the array's key value. What is the actual goal of your method?
I guess what you want is to initialise your my_hash with a default value, so then you don't need to check if it's nil or not.
That can be done using the Hash.new constructor, compare:
my_hash = {}
puts my_hash['no_existing_key'] #=> nil
my_hash = Hash.new({})
puts my_hash['no_existing_key'] #=> {}
You then can reduce your code to:
def create_a_hash_from_a_collection
my_hash = Hash.new({})
collection_of_hashes.each do |key, value|
my_hash[key] = value
end
my_hash
end
Since you are assigning value anyway, maybe you could use my_hash[key] = value || {}?
So, if value to assign is nil, the value of that key becomes {}.
Related
I have a nested hash, to which I need to add more deeply nested property/value pairs.
Sample A:
a = {}
a['x']['y']['z'] << 8
Normally I'd have to do this:
Sample B:
a = {}
a['x'] ||= a['x'] = {}
a['x']['y'] ||= a['x']['y'] = {}
a['x']['y']['z'] ||= a['x']['y']['z'] = []
Otherwise, I will get undefined method '<<' for nil:NillClass.
Is there some type of shorthand or function along the lines of code A instead of code B?
The most elegant solution for the deep-nested hash of any depth would be:
hash = Hash.new { |h, k| h[k] = h.dup.clear }
or, even better (credits to #Stefan)
hash = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
That way one might access any level:
hash[:a1][:a2][:a3][:a4] = :foo
#⇒ {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
The idea is to clone the default_proc within the hash itself.
To give a little backgroud, there's a method Hash#dig which is present since ruby 2.3. With this you can safely attempt to read any number of keys:
{a: {b: {c: 1}}}.dig :a, :b, :c # => 1
{}.dig :a, :b, :c # => nil
of course, this doesn't solve your problem. You're looking for a write version. This has been proposed but rejected in Ruby core in the form of Hash#bury.
This method does almost exactly what you are looking for, however it can only set nested hash values and not append to nested arrays:
# start with empty hash
hash = {}
# define the inner array
hash.bury :x, :y, :z, []
# add to the inner array
hash[:x][:y][:z] << :some_val
You can get this method through the ruby-bury gem, or alternatively you can take their implementation from their source code
Here are a couple of ways that could be done.
arr = [:a1, :a2, :a3, :a4, :foo]
Use Enumerable#reduce (aka inject)
def hashify(arr)
arr[0..-3].reverse_each.reduce(arr[-2]=>arr[-1]) { |h,x| { x=>h } }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
Use recursion
def hashify(arr)
first, *rest = arr
rest.size == 1 ? { first=>rest.first } : { first=>hashify(rest) }
end
hashify(arr)
#=> {:a1=>{:a2=>{:a3=>{:a4=>:foo}}}}
You can consider using the get and set methods from the rodash gem in order to set a deeply nested value into a hash with a default value.
require 'rodash'
a = {}
key = ['x', 'y', 'z']
default_value = []
value = 8
current_value = Rodash.get(a, key, default_value)
Rodash.set(a, key, current_value << value)
a
# => {"x"=>{"y"=>{"z"=>[8]}}}
I use the following to deeply set/initialize arrays/hashes using a list of keys. If they keys are ints, it assumes indexing into an array, otherwise it assumes a hash:
def deep_set(target, path, value)
key = path.shift
return target[key] = value if path.empty?
child = target[key]
return deep_set(child, path, value) if child
deep_set(
target[key] = path[0].is_a?(Integer) ? [] : {},
path,
value,
)
end
Here is an example of using it:
target = {first: [:old_data]}
deep_set(target, [:first, 1, 1, :lol], 'foo')
puts target # {:first=>[:old_data, [nil, {:lol=>"foo"}]]}
It uses the fact that ruby allows you to set-and-expand arrays on the fly, which is a bit wonky, but works nicely here.
Here is my problem. I like Andrea Pavoni's way of allowing a nested hash to be used to initialize a class.
require 'ostruct'
class DeepStruct < OpenStruct
def initialize(hash=nil)
#table = {}
#hash_table = {}
if hash
hash.each do |k,v|
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def to_h
#hash_table
end
end
But I can't find a way to include a hash (in the class) with specific default values, so that the behavior would be as follows:
Original behavior without default (with above code):
input_hash = {a: {b: 1}}
new_object = DeepStruct.new hash
new_object.a # => #<DeepStruct b=1>
new_object.a.b # => 1
new_object.a.to_h # => {b: 1}
With the following default_h defined inside the class:
default_h = {a: {dc: 2}, dd: {de: 4}}
input_hash and default_h should merge as follows
(actually using deep_merge for nested hash)
{:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}}
The behavior with default hash should be:
new_object = DeepStruct.new hash
new_object.a.b # => 1
new_object.a.dc # => 2
new_object.a.to_h # => {:dc=>2, :b=>1}
I can't find a way to implement this behavior inside the class. I would really appreciate any help in this matter.
Edit: Now trying to use David's code in a class:
class CompMedia
require 'ostruct'
attr_accessor :merged_h
def initialize(hash)
defaults = {a: {dc: 2}, dd: {de: 4}}
#merged_h = {}
deep_update(merged_h, defaults)
deep_update(merged_h, hash)
#merged_h
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def deep_open_struct(hash)
result = OpenStruct.new
hash.each do |key, value|
if value.is_a?(Hash)
result[key] = deep_open_struct(value)
else
result[key] = value
end
end
result
end
end # class CompMedia
input_hash = {a: {b: 1}}
cm = CompMedia.new(input_hash)
object = cm.deep_open_struct(cm.merged_h)
p object.marshal_dump # {:a=>#<OpenStruct dc=2, b=1>, :dd=>#<OpenStruct de=4>}
p object.a # <OpenStruct dc=2, b=1>
p object.a.marshal_dump # {:dc=>2, :b=>1}
p object.a.b # 1
p object.a.dc # 2
p object.dd # <OpenStruct de=4>
Obviously, I haven't found a way to retrieve in a simple fashion the nested hash elements from the openstruct object.
My objective is to create a class that would be initialized with a default (nested) hash contained in the class, and a (nested) input hash. In addition, I want to be able to add methods that would process the hash inside the class. I am not there yet.
On the other hand, I could just use the merged hash and this would work albeit with slightly more cumbersome notations:
class CompMedia
attr_accessor :merged_h
def initialize(hash)
defaults = {a: {dc: 2}, dd: {de: 4}}
#merged_h = {}
deep_update(merged_h, defaults)
deep_update(merged_h, hash)
#merged_h
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def multiply_by(k)
merged_h[:a][:dc] * k
end
end
input_hash = {a: {b: 1}}
cm = CompMedia.new(input_hash)
p cm.merged_h # {:a=>{:dc=>2, :b=>1}, :dd=>{:de=>4}}
p cm.merged_h[:a] # {:dc=>2, :b=>1}
p cm.merged_h[:a][:dc] # 2
p cm.merged_h[:dd] # {:de=>4}
p cm.multiply_by(10) # 20
I will consider the last version as my solution unless someone can make the code with OpenStruct work, which I would prefer.
Here is some code that does what you want, except I threw away the idea of subclassing OpenStruct because I wasn't sure if it was a good idea. Also, I implemented deep_merge myself because it was pretty easy to do, but you could try using the version from ActiveSupport if you wanted.
require 'ostruct'
# Merges two hashes that could have hashes inside them. Default
# values/procs of the input hashes are ignored. The output hash will
# not contain any references to any of the input hashes, so you don't
# have to worry that mutating the output will affect the inputs.
def deep_merge(h1, h2)
result = {}
deep_update(result, h1)
deep_update(result, h2)
result
end
def deep_update(dest, src)
src.each do |key, value|
if value.is_a?(Hash)
dest[key] = {} if !dest[key].is_a?(Hash)
deep_update(dest[key], value)
else
dest[key] = value
end
end
end
def deep_open_struct(hash)
result = OpenStruct.new
hash.each do |key, value|
if value.is_a?(Hash)
result[key] = deep_open_struct(value)
else
result[key] = value
end
end
result
end
input_hash = {a: {b: 1}}
defaults = {a: {dc: 2}, dd: {de: 4}}
object = deep_open_struct(deep_merge(defaults, input_hash))
p object.a.b
p object.a.dc
p object.a.to_h
Let's say I have a Hash like this:
my_hash = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
And I want to convert every element inside the hash that is also a hash to be placed inside of an Array.
For example, I want the finished Hash to look like this:
{"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
Here is what I've tried so far, but I need it to work recursively and I'm not quite sure how to make that work:
my_hash.each do |k,v|
if v.class == Hash
my_hash[k] = [] << v
end
end
=> {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>{"c2"=>"c3"}}]}
You need to wrap your code into a method and call it recursively.
my_hash = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
def process(hash)
hash.each do |k,v|
if v.class == Hash
hash[k] = [] << process(v)
end
end
end
p process(my_hash)
#=> {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
Recurring proc is another way around:
h = {"a"=>{"a1"=>"b1"}, "b"=>"b", "c"=>{"c1"=>{"c2"=>"c3"}}}
h.map(&(p = proc{|k,v| {k => v.is_a?(Hash) ? [p[*v]] : v}}))
.reduce({}, &:merge)
# => {"a"=>[{"a1"=>"b1"}], "b"=>"b", "c"=>[{"c1"=>[{"c2"=>"c3"}]}]}
It can be done with single reduce, but that way things get even more obfuscated.
I have some simple_hash:
old_hash = {"New"=>"0"}
I wan to convert it to new format:
new_hash = old_hash.keys.each do |key|
hash = Hash.new
hash[key] = {count: old_hash[key]}
hash
end
but this code returns me:
["New"]
instead of:
{"New"=>{:count=>"0"}}
And the question is why?
You are confusing the syntax of block with that of a method. In your code, new_hash gets the value of old_hash.keys, which is not what you want.
A little modification works:
new_hash = Hash.new
old_hash.keys.each do |key|
new_hash[key] = {count: old_hash[key]}
end
Do this:
hash = Hash.new
new_hash = old_hash.keys.each do |key|
hash[key] = {count: old_hash[key]}
hash
end
hash
# => {"New"=>{:count=>"0"}}
Since you placed hash = Hash.new inside the loop, you are creating a new hash every time.
irb> pp config
[{"file"=>"/var/tmp"},
{"size"=>"1024"},
{"modified"=>"03/28/2012"}]
=> nil
In the code,
config.each do |d|
# then how to break d into (k, v)???
end
config.each do |items|
items.each do |key, value|
# e.g. key="file", value="/var/tmp", etc.
end
end
Just do
config.each do |hash|
(k,v),_ = *hash
end
Inspired by #Arup's answer, here's a solution that doesn't require a extra, unused variable in the parallel assignment:
config.each do |hash|
key, value = hash.to_a[0]
end
to_a converts the hash into the same kind of array that you would get by using splat *hash, but you can actually index the first element of the array (i.e. the first key/value pair) with [0] this way, while trying to do so with splat (*hash) generates a syntax error (at least in Ruby version 2.1.1):
>> k,v = (*hash)[0]
SyntaxError: (irb):4: syntax error, unexpected ')', expecting '='
k,v = (*x)[0]
^
from c:/RailsInstaller/Ruby1.9.3/bin/irb:12:in `<main>'
>>
Of course, depending on what you're going to do with the key and value variables, it might make your code shorter and more readable to use one of these standard block constructs:
config.each do |hash|
hash.each { |key,value| puts "#{key}: #{value}" }
end
# or
config.each do |hash|
hash.each do |key,value|
puts "#{key}: #{value}"
end
end