更改ruby中的树结构以使用嵌套哈希

时间:2012-01-04 21:41:54

标签: ruby tree

我有以下名为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'=> {}}}}

任何有用的想法?

3 个答案:

答案 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