我有以下名为Tree的类,它构建了一个简单的树
class Tree
attr_accessor :children, :node_name
def initialize(name, children=[])
@children = children
@node_name = name
end
def visit_all(&block)
visit &block
children.each {|c| c.visit_all &block}
end
def visit(&block)
block.call self
end
end
ruby_tree = Tree.new("grandpa",
[Tree.new("dad", [Tree.new("child1"), Tree.new("child2")]),
Tree.new("uncle", [Tree.new("child3"), Tree.new("child4")])])
puts "Visiting a node"
ruby_tree.visit {|node| puts node.node_name}
puts
puts "visiting entire tree"
ruby_tree.visit_all {|node| puts node.node_name}
现在我要做的是能够创建一个树作为嵌套哈希。例如,对于这个,这将是:
{'grandpa'=> {'dad'=> {'child 1'=> {},'child 2'=> {}},'uncle'=> {'child 3' => {},'child 4'=> {}}}}
任何有用的想法?
答案 0 :(得分:3)
它正在融化我的大脑所以我为它编写了一个规范:
# encoding: UTF-8
require 'rspec' # testing/behaviour description framework
require_relative "../tree.rb" # pull in the actual code
# Everything in the `describe` block is rspec "tests"
describe :to_h do
# contexts are useful for describing what happens under certain conditions, in the first case, when there is only the top of the tree passed to to_h
context "One level deep" do
# a let is a way of declaring a variable in rspec (that keeps it useful)
let(:ruby_tree) { Tree.new "grandpa" }
let(:expected) { {"grandpa" => {} } }
subject { ruby_tree.to_h } # this the behaviour you're testing
it { should == expected } # it should equal what's in expected above
end
# The next two contexts are just testing deeper trees. I thought that each depth up to 3 should be tested, as past 3 levels it would be the same as 3.
context "Two levels deep" do
let(:ruby_tree) {
Tree.new( "grandpa",
[Tree.new("dad"), Tree.new("uncle") ]
)
}
let(:expected) do
{"grandpa" => {
"dad" => {}, "uncle" => {}
}
}
end
subject { ruby_tree.to_h }
it { should == expected }
end
context "grandchildren" do
let(:ruby_tree){
ruby_tree = Tree.new("grandpa",
[Tree.new("dad", [Tree.new("child1"), Tree.new("child2")]),
Tree.new("uncle", [Tree.new("child3"), Tree.new("child4")])])
}
let(:expected) {
{'grandpa'=>{'dad'=>{'child1'=>{},'child2'=>{}}, 'uncle'=>{'child3'=>{}, 'child4'=>{}}}}
}
subject { ruby_tree.to_h }
it { should == expected }
end
end
class Tree
def to_h
hash ={} # create a hash
# `reduce` is a synonym for `inject`, see the other answer for a link to the docs,
# but it's a type of fold
# http://en.wikipedia.org/wiki/Fold_(higher-order_function),
# which will take a list of several objects and
# fold them into one (or fewer, but generally one) through application of a function.
# It reduces the list through injecting a function, hence the synonyms.
# Here, the current node's list of children is folded into one hash by
# applying Hash#merge to each child (once the child has been been made
# into a one key hash, possibly with children too), and then assigned as
# the current node's hash value, with the node_name as the key.
hash[@node_name] = children.reduce({}){|mem,c| mem.merge c.to_h}
hash # return the hash
end
end
我确信这可以做得更好,但它至少可以起作用。
顺便说一句,你提供的哈希有一些额外的空格,我认为不应该存在?例如“孩子1”应该是“child1”,除非你真的想要加入吗?
答案 1 :(得分:1)
class Tree
def to_hash
{ @node_name => @children.inject({}) { |acum, child| acum.merge(child.to_hash) } }
end
end
p ruby_tree.to_hash
请参阅注入here
的文档答案 2 :(得分:1)
将其分解为更简单的子问题并使用递归:
def make_node(name,subhash)
Tree.new(name,subhash.keys.collect{|k|make_node(k,subhash[k])})
end
def make_root(hash)
make_node(hash.keys[0],hash[hash.keys[0]])
end
然后证明它有效:
tree_like_this = make_root({'grandpa' => { 'dad' => {'child 1' => {}, 'child 2' => {} },
'uncle' => {'child 3' => {}, 'child 4' => {} } } })
puts 'tree like this'
tree_like_this.visit_all{|n|puts n.node_name}
这是Seven Languages In Seven Weeks的练习。原来的练习说要把它全部放在initialize
。