Jones Jones - 1 month ago 6
Ruby Question

Is Ruby turning my hash into an Array, if so why?

So I'm trying to make a tree data structure, which can be instantiated by a nested hash.

My code is as follows, and should just recursively make nodes out of keys, and children out of their values.

class Tree
attr_accessor :children, :node_name
#def initialize(name, children=[])
# @children = children
# @node_name = name
# end
def initialize(hashTree)
@node_name = hashTree.keys[0]
@children = []
p node_name
hashTree[node_name].each do |hash|
children << Tree.new(hash)
end
end
#...
end

p = {'grandpa' => { 'dad' => {'child 1' => {}, 'child2' => {} }, 'uncle' => {'child 3' => {}, 'child 4' => {} } } }
p p
p Tree.new(p)


When I try to run that code I get the following:

{"grandpa"=>{"dad"=>{"child 1"=>{}, "child2"=>{}}, "uncle"=>{"child 3"=>{}, "child 4"=>{}}}}
"grandpa"
/Users/Matt/sw/sevenLang/week1/hw-tree.rb:8:in `initialize': undefined method `keys' for ["dad", {"child 1"=>{}, "child2"=>{}}]:Array (NoMethodError)
from /Users/Matt/sw/sevenLang/week1/hw-tree.rb:12:in `new'
from /Users/Matt/sw/sevenLang/week1/hw-tree.rb:12:in `block in initialize'
from /Users/Matt/sw/sevenLang/week1/hw-tree.rb:11:in `each'
from /Users/Matt/sw/sevenLang/week1/hw-tree.rb:11:in `initialize'
from /Users/Matt/sw/sevenLang/week1/hw-tree.rb:26:in `new'
from /Users/Matt/sw/sevenLang/week1/hw-tree.rb:26:in `<main>'
[Finished in 0.1s with exit code 1]


It looks like each is turning the nested hash into an array, where the key is the first element, and the value is the second element.

Answer

hashTree[node_name] is p["grandpa"], and is a Hash:

{"dad"=>{"child 1"=>{}, "child2"=>{}}, "uncle"=>{"child 3"=>{}, "child 4"=>{}}}

Hash#each will yield a two-element array: a key and a value. So if you write

hashTree[node_name].each do |hash|

and hashTree[node_name] is a Hash, hash will always be a two-element array. Due to a trick in its grammar, Ruby will auto-splat an array argument if there are multiple formal parameters, so you can also write:

hashTree[node_name].each do |name, hash|

This will not result in an error. (You do actually still have an unrelated error in logic, as you're skipping a level.)

An error-free version:

class Tree
    attr_accessor :children, :node_name
    def initialize(name, hashTree)
        @node_name = name
        @children = []
        hashTree.each do |child_name, hash|
            children << Tree.new(child_name, hash)
        end
    end
end

p = {'grandpa' => { 'dad' => {'child 1' => {}, 'child2' => {} }, 'uncle' => {'child 3' => {}, 'child 4' => {} } } }
p Tree.new("Family", p)

This can be shortened by using map:

class Tree
    attr_accessor :children, :node_name
    def initialize(name, hashTree)
        @node_name = name
        @children = hashTree.map do |child_name, hash|
            Tree.new(child_name, hash)
        end
    end
end