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 <jarodzz@gmail.com>
Date: Wed, Oct 13, 2010 at 9:12 AM
Subject: ruby-talk-ask
To: jarodzz@gmail.com

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 &amp;&amp; @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 == &quot;1&quot;\)
           assert\(@single\.to\_s == &quot;haha&quot;\)
   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

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 10/20/2010 9:16 AM, salamond wrote:

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 ?

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 20.10.2010 16:35, Jeremy Bopp wrote:

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

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 ?

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.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Will that work? Or would it have to be

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

···

On Oct 20, 11:41 am, Robert Klemme <shortcut...@googlemail.com> 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

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 6:10 AM, w_a_x_man <w_a_x_man@yahoo.com> wrote:

On Oct 20, 11:41 am, Robert Klemme <shortcut...@googlemail.com> 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?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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 Klemme <shortcutter@googlemail.com> wrote:

On Thu, Oct 21, 2010 at 6:10 AM, w_a_x_man <w_a_x_man@yahoo.com> wrote:

On Oct 20, 11:41 am, Robert Klemme <shortcut...@googlemail.com> 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?

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

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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.

···

On Thu, Oct 21, 2010 at 12:38 PM, salamond <jarodzz@gmail.com> 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.

Please do not top post.

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 12:38 PM, salamond <jarodzz@gmail.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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 Klemme <shortcutter@googlemail.com> wrote:

Please do not top post.

On Thu, Oct 21, 2010 at 12:38 PM, salamond <jarodzz@gmail.com> 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

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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++.

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.

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

robert

···

On Thu, Oct 21, 2010 at 4:42 PM, salamond <jarodzz@gmail.com> wrote:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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.

···

On Thu, Oct 21, 2010 at 4:42 PM, salamond <jarodzz@gmail.com> 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.

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.