About handling args in block

Hi, guys.

I’m writing a Node class for a Binary Tree.
Coding comes below.

There’s a method remove_leaf.
Now I want to do it this way:
=begin
[@left_node, @right_node].each do |node|
if node.is_leaf?
node = nil
end
end
=end

But it doesn’t work.
Is there a way to do it ?

You can ignore class Tree. I just post it here to make a little sense.

---------- Forwarded message ----------
From: salamond [email protected]
Date: Wed, Oct 13, 2010 at 9:12 AM
Subject: ruby-talk-ask
To: [email protected]

class Node
attr_accessor :value, :left_node, :right_node

def initialize(value=nil, left_node=nil, right_node=nil)
@value = value
@left_node = left_node
@right_node = right_node
end

def to_s
@value.to_s
end

def is_leaf?
return @left_node==nil && @right_node==nil
end

def remove_leaf
if is_leaf?
return
else
=begin
[@left_node, @right_node].each do |node|
if node.is_leaf?
node = nil
end
end
=end
if @left_node.is_leaf?
@left_node = nil
end
if @right_node.is_leaf?
@right_node = nil
end
end
end

end

require ‘test/unit’
require ‘node’

class NodeTest < Test::Unit::TestCase
def setup
@empty = Node.new()
@single = Node.new(“haha”)
@full = Node.new(1, Node.new(2), Node.new(4))
end

def test_default_init()
assert(@empty.value==nil)
assert(@empty.left_node==nil)
assert(@empty.right_node==nil)
end

def test_init_with_value()
assert(@full.left_node.value==2)
assert(@full.right_node.value==4)
assert(@full.value==1)
end

def test_to_s()
assert(@full.to_s == “1”)
assert(@single.to_s == “haha”)
end

def test_is_leaf()
assert(@empty.is_leaf? == true)
assert(@single.is_leaf? == true)
assert(@full.is_leaf? == false)
end

def test_remove_leaf()
assert(@full.is_leaf? == false)
@full.remove_leaf()
assert(@full.is_leaf? == true)
end

end

require “node”

class Tree

attr_accessor :root, :nodes, :sorted_nodes

def initialize( root=nil )
@root = root
@nodes = Array.new()
if @root!= nil
@nodes.push( @root )
end
@sorted_nodes = Array.new()
@targets = Array.new()
end

def insert( node )
@nodes.push( node )
@root = insert_at( @root, node )
end

TODO: to prevent multiple travel in the same way, add state check

def travel(order = :inorder)
@sorted_nodes = Array.new()
travel_at( @root, order )
return @sorted_nodes
end

def to_s
@data.to_s
end

def cut_all_leaves()
@nodes.each do |node|
node.cut_leaf
end
end

def n_step_from_leaf( n )
(1…n).each do |times|
cut_all_leaves
end
nodes = Array.new()
travel().each do |node|
if node.is_leaf?
nodes.push(node)
end
end
return nodes
end

private
def insert_at(subroot, node)
if subroot == nil
subroot = node
else
if subroot.value >= node.value
subroot.left_node = insert_at(subroot.left_node, node)
else
subroot.right_node = insert_at(subroot.right_node, node)
end
end
return subroot
end

def travel_at(subroot, order)
if subroot == nil
return
end
case order
when :preorder
@sorted_nodes.push( subroot )
travel_at( subroot.left_node, order )
travel_at( subroot.right_node, order )
when :inorder
travel_at( subroot.left_node, order)
@sorted_nodes.push( subroot )
travel_at( subroot.right_node, order )
when :postorder
travel_at( subroot.left_node, order )
travel_at( subroot.right_node, order)
@sorted_nodes.push( subroot )
end
end

end

On 10/20/2010 9:16 AM, salamond wrote:

                                   node = nil
                           end
                   end

=end

But it doesn’t work.
Is there a way to do it ?

In your example, node is just a reference to the same objects referenced
by @left_node and @right_node. Assigning nil to node only discards the
reference held by node. It looks like you’re trying to do this too much
the C/C++ way, where you would have a pointer to a pointer that you
could then nullify.

The alternative code you’re already using (where you unrolled this loop)
is probably the easiest and most straightforward way to deal with this
issue.

-Jeremy

On 20.10.2010 16:35, Jeremy B. wrote:

                            if node.is_leaf?

reference held by node. It looks like you’re trying to do this too much
the C/C++ way, where you would have a pointer to a pointer that you
could then nullify.

The alternative code you’re already using (where you unrolled this loop)
is probably the easiest and most straightforward way to deal with this
issue.

OP, you can somewhat simplify the code

Node = Struct.new :value, :left, :right do

We usually do not use prefix “is” which

you might be used to in Java.

def leaf?
@left.nil? && @right.nil?
end

Actually this will remove all leaves hence the

changed name.

def remove_leaves
self.left = nil if left && left.leaf?
self.right = nil if right && right.leaf?
self
end

def to_s; value.to_s end

Useful for recursive algorithms.

def visit(val = nil, &b)
val = @left.visit(val, &b) if @left
val = b[self, val]
val = @right.visit(val, &b) if @right
val
end
end

:slight_smile:

Have fun!

Btw, you original implementation had a flaw because it would break if a
Node had only one child. For the other the test “.is_leaf?” would fail
because NilClass does not have that method.

Kind regards

robert

On Oct 20, 11:41am, Robert K. [email protected] wrote:

Node = Struct.new :value, :left, :right do

We usually do not use prefix “is” which

you might be used to in Java.

def leaf?
@left.nil? && @right.nil?
end

Will that work? Or would it have to be

self.left.nil? && self.right.nil?

Thanks Jeremy, wax, and robert.

I wanner do this because this tree may not be a binary tree.
What if a node could link to a dozen nodes.

we have to something like this.
nodes.each do |node|
node = nil
end

On Thu, Oct 21, 2010 at 4:00 PM, Robert K.

On Thu, Oct 21, 2010 at 6:10 AM, w_a_x_man [email protected] wrote:

Will that work? Or would it have to be

self.left.nil? && self.right.nil?

No, my code won’t work. But this is sufficient:

left.nil? && right.nil?

It also needs to be

def visit(val = nil, &b)
val = left.visit(val, &b) if left
val = b[self, val]
val = right.visit(val, &b) if right
val
end

Thanks for catching this!

Kind regards

robert

On Thu, Oct 21, 2010 at 12:38 PM, salamond [email protected] wrote:

Thanks Jeremy, wax, and robert.

I wanner do this because this tree may not be a binary tree.
What if a node could link to a dozen nodes.

In that case it’s probable that you have another level of indirection,
and not have each child node directly as an instance variable. More
likely something like:

class Node
attr_reader :children

def initialize
@children = []
end

def add_children node
@children << children
end

def remove_leaves
@children.delete_if {|node| node.leaf?}
end
end

Missing some pieces, but you get the idea…

Jesus.

Let’s see if I get this.
While I’m passing @left_node, and @right_node into this block,
I’m passing the reference of these 2 objects into a block.
Inside block, I can use these 2 references to modify the objects,
but I can’t change the reference themselves.

Now here comes an update, is there a way to have the class destroy
itself?

like
class Node
#snip
def destroy
self = nil
end
end

[@left_node, @right_node].each do |node|
node.destroy
end

Is it possible to have something like this work?
When I try this, it said can’t change the value of self.

On Thu, Oct 21, 2010 at 7:09 PM, Robert K.

On Thu, Oct 21, 2010 at 4:42 PM, salamond [email protected] wrote:

Let’s see if I get this.
While I’m passing @left_node, and @right_node into this block,
I’m passing the reference of these 2 objects into a block.
Inside block, I can use these 2 references to modify the objects,
but I can’t change the reference themselves.

Exactly.

Now here comes an update, is there a way to have the class destroy itself?

No. Please note Ruby != C++.

end

Is it possible to have something like this work?
When I try this, it said can’t change the value of self.

Which is the truth. Please read on object finalization in Ruby.

robert

Please do not top post.

On Thu, Oct 21, 2010 at 12:38 PM, salamond [email protected] wrote:

I wanner do this because this tree may not be a binary tree.
What if a node could link to a dozen nodes.

In that case you would have a single collection (e.g. an Array) which
stores child nodes! For that use Jesus’s solution.

we have to something like this.
nodes.each do |node|
node = nil
end

This won’t work because you only assign to the block parameter and
leave “nodes” unchanged.

Cheers

robert

On Thu, Oct 21, 2010 at 4:42 PM, salamond [email protected] wrote:

#snip
When I try this, it said can’t change the value of self.
I think you are confused about how variables and objects work in Ruby.
Variables (local or instance) point to objects. You use a variable to
send messages to the object it references. You can have many variables
pointing to the same object, and an object doesn’t know which
variables pointing to itself exist. So, @left_node and node are two
such variables that, in the first iteration of the loop, are pointing
to the same object. You can use the variable node to send messages to
the object, but that can never affect which is object is referenced by
@left, it can only modify the internal state of the object.

An object cannot destroy itself. The only way to destroy an object is
to remove all references to it, and eventually the garbage collector
will take care of it. So, what you need to do here is to change the
@left variable, making it point to nil, so that the original object is
not pointed to anymore.

If you have a collection of child nodes, then do as a I explained
before: delete from the array the chosen objects so they are not
pointed from the array anymore.

Jesus.