使用Ruby将嵌入式块转换为嵌入式数组

时间:2014-09-09 15:12:47

标签: ruby dsl

考虑以下code

class Node
  def initialize(name=nil)
    @name = name
    @children = []
  end

  def node(name, &block)
    child = Node.new(name)
    @children.push(child)
    child.instance_exec(&block) if block
  end
end

def tree(name, &block)
  @tree = Node.new(name)
  @tree.instance_exec(&block)
  @tree
end

t = tree("Simpsons family tree") do
  node("gramps") do
    node("homer+marge") do
      node("bart")
      node("lisa")
      node("maggie")
    end
  end
end

puts "tree = " + t.inspect

返回:

tree = #<Node:0x007fca1a103268 @name="Simpsons family tree", @children=[#<Node:0x007fca1a103128 @name="gramps", @children=[#<Node:0x007fca1a102fe8 @name="homer+marge", @children=[#<Node:0x007fca1a102ef8 @name="bart", @children=[]>, #<Node:0x007fca1a102e80 @name="lisa", @children=[]>, #<Node:0x007fca1a102e08 @name="maggie", @children=[]>]>]>]>

我想知道是否有可能进行更新,以便在不使用@children共享阵列的情况下即时返回嵌入式数组阵列。我期待这个结果:

[
  "Simpsons family tree",
  [
    "gramps",
    [
      "homer+marge",
      [
        "bart",
        "lisa",
        "maggie"
      ]
    ]
  ]
]

有可能吗?感谢您的任何建议。

修改 实际上,我想基本上使用相同的代码,但没有任何@children实例。所以我想删除@children

编辑2:

经过一些测试后,这是我最好的结果:

class Node
  def initialize(name=nil)
    @name = name
  end

  def node(name, &block)
    child = Node.new(name)
    @sub = child.instance_exec(&block) if block

    [
      name,
      @sub
    ].compact
  end
end

def tree(name, &block)
  @tree = Node.new(name)

  [
    name,
    @tree.instance_exec(&block)
  ]
end

t = tree("Simpsons family tree") do
  node("gramps") do
    node("homer+marge") do
      node("bart")
      node("lisa")
      node("maggie")
    end
  end
end

puts t.inspect
# => [
#      "Simpsons family tree",
#      [
#        "gramps",
#        [
#          "homer+marge",
#          [
#            "maggie"
#          ]
#        ]
#      ]
#    ]

但是扁平节点仍然存在问题。因为Ruby只返回了最后一个。

1 个答案:

答案 0 :(得分:0)

我的格式并不是你想要的,但这并不是真正重要的部分。

如果允许使用一组子节点初始化节点,则可以让node方法在每次调用时返回一个新节点。

class Node
  def self.new(*args, &block)
    instance = super
    if block
      instance.instance_eval(&block)
    else
      instance
    end
  end

  def initialize(name, children=[])
    @name = name
    @children = children
  end

  attr_reader :children, :name

  def node(name, &block)
    new_node = Node.new(name)
    new_node = new_node.instance_eval(&block) if block
    Node.new(self.name, next_children + [new_node])
  end

  def next_children
    children.map{|child| Node.new(child.name, child.next_children) }
  end

  def inspect
    return %{"#{name}"} if children.empty?
    %{"#{name}", #{children}}
  end
end

t = Node.new("Simpsons family tree") do
  node("gramps") do
    node("homer+marge") do
      node("bart").
      node("lisa").
      node("maggie")
    end
  end
end

puts t.inspect
#=> "Simpsons family tree", ["gramps", ["homer+marge", ["bart", "lisa", "maggie"]]]

通过更改初始化和node方法的行为,您可以在创建节点时累积它们。

我还冒昧地删除了你的tree方法,因为它只是一个初始化节点的包装器,但这可能是一个带回格式化的地方。

我发现这篇文章正在寻找instance_exec my Ruby DSL Handbook instance_exec的好用例,但您可以使用instance_evalnode作为该块,因为您可以使用[t].inspect不要传递任何论据。

编辑:我更新了每次返回新值的方法。更改要求将没有块的节点链接在一起,因为每个{{1}}调用都会返回一个新对象。块的返回值是一个新节点。

为了获得您想要的格式,您需要{{1}}