How do I replace all the values in a hash with a new value? - ruby

Let's say I have an arbitrarily deep nested Hash h:
h = {
:foo => { :bar => 1 },
:baz => 10,
:quux => { :swozz => {:muux => 1000}, :grimel => 200 }
# ...
}
And let's say I have a class C defined as:
class C
attr_accessor :dict
end
How do I replace all nested values in h so that they are now C instances with the dict attribute set to that value? For instance, in the above example, I'd expect to have something like:
h = {
:foo => <C #dict={:bar => 1}>,
:baz => 10,
:quux => <C #dict={:swozz => <C #dict={:muux => 1000}>, :grimel => 200}>
# ...
}
where <C #dict = ...> represents a C instance with #dict = .... (Note that as soon as you reach a value which isn't nested, you stop wrapping it in C instances.)

def convert_hash(h)
h.keys.each do |k|
if h[k].is_a? Hash
c = C.new
c.dict = convert_hash(h[k])
h[k] = c
end
end
h
end
If we override inspect in C to give a more friendly output like so:
def inspect
"<C #dict=#{dict.inspect}>"
end
and then run with your example h this gives:
puts convert_hash(h).inspect
{:baz=>10, :quux=><C #dict={:grimel=>200,
:swozz=><C #dict={:muux=>1000}>}>, :foo=><C #dict={:bar=>1}>}
Also, if you add an initialize method to C for setting dict:
def initialize(d=nil)
self.dict = d
end
then you can reduce the 3 lines in the middle of convert_hash to just h[k] = C.new(convert_hash_h[k])

class C
attr_accessor :dict
def initialize(dict)
self.dict = dict
end
end
class Object
def convert_to_dict
C.new(self)
end
end
class Hash
def convert_to_dict
Hash[map {|k, v| [k, v.convert_to_dict] }]
end
end
p h.convert_to_dict
# => {
# => :foo => {
# => :bar => #<C:0x13adc18 #dict=1>
# => },
# => :baz => #<C:0x13adba0 #dict=10>,
# => :quux => {
# => :swozz => {
# => :muux => #<C:0x13adac8 #dict=1000>
# => },
# => :grimel => #<C:0x13ada50 #dict=200>
# => }
# => }

Related

to_json introduces strange character

With this code I implemented a tree
groups = {"al1o0"=>"A1", "al2o2"=>"A10", "al2o3"=>"A11", "al1o1"=>"A2"}
map = {}
arr = []
groups.each_with_index do |group, index|
level = (group.first.split("o")[0].split("al")[1]).to_i - 1
level = level == 0 ? nil : level
order = group.first.split("o")[1]
arr.append({ :id=> index + 1, :order => order, :name => group.last, :parent => level})
end
root = {:id => 0, :name => '', :order => 0, :parent => nil}
arr.each do |e|
map[e[:id]] = e
end
tree = {}
arr.each do |e|
pid = e[:parent]
if pid == nil
(tree[root] ||= []) << e
else
(tree[map[pid]] ||= []) << e
end
end
tree has
=> {{:id=>0, :name=>"", :order=>0, :parent=>nil}=>[{:id=>1, :order=>"0", :name=>"A1", :parent=>nil}, {:id=>4, :order=>"1", :name=>"A2", :parent=>nil}], {:id=>1, :order=>"0", :name=>"A1", :parent=>nil}=>[{:id=>2, :order=>"2", :name=>"A10", :parent=>1}, {:id=>3, :order=>"3", :name=>"A11", :parent=>1}]}
Up to here all right but If I do tree.to_json, the output is
=> "{\"{:id=\\u003e0, :name=\\u003e\\\"\\\", :order=\\u003e0, :parent=\\u003enil}\":[{\"id\":1,\"order\":\"0\",\"name\":\"A1\",\"parent\":null},{\"id\":4,\"order\":\"1\",\"name\":\"A2\",\"parent\":null}],\"{:id=\\u003e1, :order=\\u003e\\\"0\\\", :name=\\u003e\\\"A1\\\", :parent=\\u003enil}\":[{\"id\":2,\"order\":\"2\",\"name\":\"A10\",\"parent\":1},{\"id\":3,\"order\":\"3\",\"name\":\"A11\",\"parent\":1}]}"
Why It changed :id=>0 in :id=\u003e0?
First of all tree looks weird.
{{:id=>0, :name=>"", :order=>0, :parent=>nil}=>[{:id=>1, :order=>"0", :name=>"A1", :parent=>nil}, ...]}}
here is a key
{:id=>0, :name=>"", :order=>0, :parent=>nil}
and
[{:id=>1, :order=>"0", :name=>"A1", :parent=>nil}, ...]
is a value.
Key should not be a hash. How to call it later then.
You might need something like
{"A1" => {name: 'foo', order: '0' }, 'A2' => ...}

How to insert a hash inside a hash ruby

I was wondering how I could insert a hash into another hash. For example, in:
{"abcd"=>{}, "hgfe"=>34567}
I want to put "hgfe" => 34567 into the "abcd" key.
output:
{"abcd"=>{"hgfe" => 34567}}
im wanting to convert this hash
"##### RUBY HASH ####
(1)
INPUT
{
'abcd.hgfe' => 34567,
'abcd.efgh.hijk' => 12345,
'abcd.efgh.ijkl' => 56789,
'wxyz.abcd' => 9876,
'wxyz.uvwx.abcd' => 23456,
}
(1)
OUTPUT
{
'abcd' => {
'efgh' => {
'hijk' => 12345,
'ijkl' => 56789
},
'hgfe' => 34567,
},
'wxyz' => {
'abcd' => 9876,
'uvwx' => {'abcd' => 23456}
}
}
"
my currrent code:
def method1(hash)
result = {}
array2 = []
hash.each_pair do|k, v|
array1 = k.split('.')
count = array1.length
hash2 = {}
array1.each_with_index do |str, index|
if (index + 1) == count
hash2[str] = v
else
hash2[str] = {}
end
end
puts hash2.inspect
puts "--------------"
end
result
end
hash_result = method1(h2c)
Do as below
hash = {"abcd"=>{}, "hgfe"=>34567}
hash['abcd']['hgfe'] = hash.delete('hgfe')
hash # => {"abcd"=>{"hgfe"=>34567}}
You can write something like below :
def delete_key_and_add_to_another_key(hash, update_key, del_key)
hash[update_key][del_key] = hash.delete(del_key)
hash
end
hash = {"abcd"=>{}, "hgfe"=>34567}
delete_key_and_add_to_another_key(hash, 'abcd', 'hgfe')
h = {"abcd"=>{}, "hgfe"=>34567}
f, l = h.partition { |_,v| v =={} }.flatten(1)
{ f.first=> { l.first => l.last } }
#=> {"abcd"=>{"hgfe"=>34567}}

Merge values in a Hash

nodes = {
:node1 => {
:inherits => nil,
:variables => { :foo => 1, :bar => 2 }
},
:node2 => {
:inherits => :node1,
:variables => { :foo => 9, :baz => 4 }
}
}
I've been trying to understand how to return a new nodes hash where each node's :variables hash is merged with :variables from the node specified in :inheritance. In other words, node1 would be left as is while node2 to end up with :variabes => { :foo => 9, :bar => 2, :baz => 4 }
I've been swimming through docs involving Enumerable#inject, Hash#merge with a block, and more and figure it's time to ask for help.
UPDATE:
Figured I'd provide an update. This code is certainly not the solution but it might be heading in the right direction...
nodes = {
:node1 => { :inherits => nil, :variables => { :foo => 1, :bar => 2 } },
:node2 => { :inherits => :node1, :variables => { :foo => 9, :baz => 4 } }
}
new = nodes.inject({}) do |result, (k, v)|
result.merge k => v.merge({ :variables => { :a => 6, :b => 7 } })
end
returns
{:node2=>{:inherits=>:node1, :variables=>{:a=>6, :b=>7}}, :node1=>{:inherits=>nil, :variables=>{:a=>6, :b=>7}}}
So that v.merge is not working as intended...
You want Hash#merge:
merged_variables = nodes[:node1][:variables].merge(nodes[:node2][:variables])
nodes[:node2][:variables].replace(
nodes[:node1][:variables]
.merge(nodes[:node2][:variables])
)
merged_nodes = {}
nodes.each do |name, node|
merged_nodes[name] = node.dup
merged_nodes[name][:variables] = if node[:inherits]
nodes[node[:inherits]][:variables].merge node[:variables]
else
node[:variables].dup
end
end
will give you
{
:node1=>{:inherits=>nil, :variables=>{:foo=>1, :bar=>2}},
:node2=>{:inherits=>:node1, :variables=>{:foo=>9, :bar=>2, :baz=>4}}
}
but it won't handle deeper nesting i.e. if :node3 then inherits :node2... if you need anything as complicated as that, this hash-based approach is going to get pretty clunky.
A little bit shorter by merging in place:
nodes[:node2][:variables].merge!(nodes[:node1][:variables])
nodes #=> {:node1=>{:inherits=>nil, :variables=>{:foo=>1, :bar=>2}},
# :node2=>{:inherits=>:node1, :variables=>{:foo=>1, :baz=>4, :bar=>2}}}
new = {}
nodes.each do |e, v|
v.each do |attribute, value|
if attribute == :inherits
new[e] = value.nil? ? v : nodes[value][:variables].merge( v[:variables] )
end
end
end
p new #=> {:node1=>{:inherits=>nil, :variables=>{:foo=>1, :bar=>2}}, :node2=>{:foo=>9, :bar=>2, :baz=>4}}

Recursive DFS Ruby method

I have a YAML file of groups that I would like to get into a MongoDB collection called groups with documents like {"name" => "golf", "parent" => "sports"} (Top level groups, like sports, would just be {"name" => "sports"} without a parent.)
We are trying to traverse the nested hash, but I'm not sure if it's working correctly. I'd prefer to use a recursive method than a lambda proc. What should we change to make it work?
Thanks!
Matt
Here's the working code:
require 'mongo'
require 'yaml'
conn = Mongo::Connection.new
db = conn.db("acani")
interests = db.collection("interests")
##interest_id = 0
interests_hash = YAML::load_file('interests.yml')
def interests.insert_interest(interest, parent=nil)
interest_id = ##interest_id.to_s(36)
if interest.is_a? String # base case
insert({:_id => interest_id, :n => interest, :p => parent})
##interest_id += 1
else # it's a hash
interest = interest.first # get key-value pair in hash
interest_name = interest[0]
insert({:_id => interest_id, :n => interest_name, :p => parent})
##interest_id += 1
interest[1].each do |i|
insert_interest(i, interest_name)
end
end
end
interests.insert_interest interests_hash
View the Interests YAML.
View the acani source.
Your question is just how to convert this code:
insert_enumerable = lambda {|obj, collection|
# obj = {:value => obj} if !obj.kind_of? Enumerable
if(obj.kind_of? Array or obj.kind_of? Hash)
obj.each do |k, v|
v = (v.nil?) ? k : v
insert_enumerable.call({:value => v, :parent => obj}, collection)
end
else
obj = {:value => obj}
end
# collection.insert({name => obj[:value], :parent => obj[:parent]})
pp({name => obj[:value], :parent => obj[:parent]})
}
...to use a method rather than a lambda? If so, then:
def insert_enumerable( obj, collection )
# obj = {:value => obj} if !obj.kind_of? Enumerable
if(obj.kind_of? Array or obj.kind_of? Hash)
obj.each do |k, v|
v = (v.nil?) ? k : v
insert_enumerable({:value => v, :parent => obj}, collection)
end
else
obj = {:value => obj}
end
# collection.insert({name => obj[:value], :parent => obj[:parent]})
pp({name => obj[:value], :parent => obj[:parent]})
end
If that's not what you're asking, please help clarify.

How to change format of nested hashes

I'm looking for a solution how to write the format function which will take a string or nested hash as an argument and return the flatten version of it with the path as a key.
arg = "foo"
format(arg) # => { "hash[keys]" => "foo" }
arg = {:a => "foo", :b => { :c => "bar", :d => "baz" }}
format(arg) # => { "hash[keys][a]" => "foo", "hash[keys][b][c]" => "bar", "hash[keys][b][d]" => "baz" }
def hash_flatten h
h.inject({}) do |a,(k,v)|
if v.is_a?(Hash)
hash_flatten(v).each do |sk, sv|
a[[k]+sk] = sv
end
else
k = k ? [k] : []
a[k] = v
end
a
end
end
def format h
if h.is_a?(Hash)
a = hash_flatten(h).map do |k,v|
key = k.map{|e| "[#{e}]"}.join
"\"event[actor]#{key}\" => \"#{v}\""
end.join(', ')
else
format({nil => h})
end
end
arg = "sth"
puts format(arg)
# => "event[actor]" => "sth"
arg = {:a => "sth", :b => { :c => "sth else", :d => "trololo" }}
puts format(arg)
# => "event[actor][a]" => "sth", "event[actor][b][c]" => "sth else", "event[actor][b][d]" => "trololo"

Resources