Reputation: 55
Considering the following 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
Which is returning:
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=[]>]>]>]>
I would like to know if it was possible to make an update in order to return on-the-fly an embedded array of arrays, without using the @children
shared array. I would expect this result:
[
"Simpsons family tree",
[
"gramps",
[
"homer+marge",
[
"bart",
"lisa",
"maggie"
]
]
]
]
Is it possible? Thanks for any suggestions.
Edit:
Actually, I would like to do so with basically the same code, but without any @children
instance. So I want to remove @children
.
Edit 2:
Here is my best result, after some tests:
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"
# ]
# ]
# ]
# ]
But there's still a trouble with the flatten nodes. Because only the last one is returned by Ruby.
Upvotes: 1
Views: 144
Reputation: 1488
The formatting of mine isn't exactly what you want, but that's not really the important part.
If you allow your nodes to be initialized with a collection of children, you can have your node
method return a new node each time it is called.
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"]]]
By changing the behavior of initialization and of the node
method you can accumulate the nodes as they are created.
I also took the liberty to remove your tree
method since it was just a wrapper for initializing the nodes, but that might be a place to bring back formatting.
I came across this post looking for examples of good use of instance_exec
for my Ruby DSL Handbook but you can either use instance_exec
or instance_eval
for the block since you're not passing any arguments to it.
EDIT: I updated the approach to return new values each time. The change required that nodes without blocks be chained together because each node
call return a new object. The return value for the block is a new node.
In order to get the formatting you want, you'd need to do [t].inspect
Upvotes: 0