0

So im going through the OdinProject and starting on the "BinaryTree" portion. I've written a Binary Tree but only in C++ using pointers. So without pointers it's a little more confusing to me at how the nodes connect:

class Node

  attr_accessor :value, :parent, :left, :right
  @@nodes = 0

  def self.nodes
    @@nodes
  end

  def initialize(value, parent=nil)
    @value = value
    @parent = parent
    @left = nil
    @right = nil
    @@nodes += 1
  end

end


class BinaryTree

  def initialize
    @root = nil
  end

  def build_tree(arr)
    arr.each {|el| add_child(el,@root)}
    @root
  end

  def add_child(value,node)
    if @root.nil?
      @root = Node.new(value)
    else
      if value < node.value
        node.left.nil? ? node.left = Node.new(value,node) : add_child(value,node.left)
      elsif value > node.value
        node.right.nil? ? node.right = Node.new(value,node) : add_child(value,node.right)
      end
    end
    return node
  end

end

I've done some basic print functions, and it SEEMS to be working correctly, but I was wondering if this looks correct to others that understand ruby better. (Sidenote: I realize this does not handle duplicate numbers). Am I missing something with the "building" portion of the tree.

The reason im concerned is a see a lot of different "build" implementations. One that starts at the midpoint for instance, or is this just for "Sorted" arrays?

5
  • 1
    Binary tree is a data structure. But what you were trying to do is building then sorting a binary tree. Which one do you want? If it's the latter, I think your code is pretty clear and straightforward. But a little error, your Binary#build_tree method accepts an array, if this array contains many node objects, then the code should be arr.each {|el| add_child(el.value,@root)}, cause you can't compare an object with an integer. Commented May 16, 2018 at 15:30
  • @Caven Im wanting to Build a binary tree, the sorting is less important to me (Well technically the build should be sorting them correctly as a binary tree is built). Also the "input" is supposed to just simply be an array of numbers never node objects. Commented May 16, 2018 at 18:55
  • 1
    People who start with a sorted array and pick the midpoint as the first root might want to generate a balanced binary tree. Commented May 16, 2018 at 20:14
  • Thats probably what im thinking of. Typically BST's are expected unsorted arrays right? Commented May 17, 2018 at 15:03
  • It depends on your main purpose. If you just wanna construct a binary tree data structure, then you needn't to consider too much about other things. Commented May 17, 2018 at 16:06

1 Answer 1

3

welcome to the ruby world! Beware you might really like it and never come back to another language :)

Here are some comments I can give you:

Testing

First thing when you wonder if your code is correct is to test it with different inputs. You can do it by hand, in the console, or you can write tests that will follow you during the development.

By printing

A quick way of testing what you do is to define the to_s method on your Node class. For instance, this will allow to check your tree is ordered:

class Node
  …
  def to_s
     "#{left} #{value} #{right}".strip
  end
end 

to_s is the default ruby method used when converting an object to a string. You can for instance do:

#> puts BinaryTree.new.build_tree([5, 9, 1, 6])
1 5 6 9

By testing

For your peace of mind, you can describe some tests that will allow you to write code, and modify it, and check it is still working. I would suggest minitest for doing it easily.

require 'minitest/autorun'

describe BinaryTree do
  before do
    @constructor = BinaryTree.new
  end

  describe 'when given an array' do
    it 'sorts it' do
      @constructor.build_tree([1, 3, 2]).to_s.must_equal '1 2 3'
    end
  end

  describe 'when given an array with duplicates' do
    it 'builds the correct tree' do
      @constructor.build_tree([1, 3, 2, 3]).to_s.must_equal '1 2 3 3'
    end
  end
end

You can then run it with ruby -Ilib:test binary_tree.rb if binary_tree.rb is the file where you put your code.

You will see there, as you mentioned, that the duplicate code doesn’t work. You can make it work by removing a condition in the if…elsif block.

Then you wan work on the code and run the test as often as you like so that you are confident you didn’t break anything.

Each time you have an edge case you are not sure your code handles, just put that in a test.

Counting the nodes

Your Node.nodes method is probably not what you want if it is supposed to count the number of nodes in your binary tree. It should be in the instance of a Node, not in the class itself: if you have several trees, all nodes are taken into account for each tree.

Ruby things

Some of the things you write could be expressed differently in ruby:

Nil is not necessary

attr_accessor parent is syntactic sugar for

def parent
  @parent
end

def parent=(other)
  @parent = other
end

In ruby if your instance variable @parent is not declared, it has the value nil by default, and calling node.parent will return nil too.

You don’t need these 2 lines in the initializer:

@left = nil
@right = nil

Return is not necessary

When your last instruction in a method is a return, you don’t need the return keyword. That is what I did with the to_s method above.

Named parameters

I think it is cleaner to have named parameters when they are not obvious. For instance, calling Node.new(value, node) doesn’t really help understanding what are these 2 parameters for. I get that a node should have a value, but what is this second parameter?

You could define:

def initialize(value, parent: nil)
  # Same as before
end

and call it with Node.new(value, parent: node), or Node.new(value)

OO things

Moving methods where they belong

Your BinaryTree#add_child method should be on a node. You are adding a child to the node. Move it there, so you don’t need your second parameter: add_child(value, node.right) becomes node.right.add_child(value)

class Node
  ...
  def add_child(other_value)
    if other_value < value
      left.nil? ? self.left = Node.new(other_value, parent: self) : left.add_child(other_value)
    else
      right.nil? ? self.right = Node.new(other_value, parent: self) : right.add_child(other_value)
    end
  end
end

class BinaryTree
  ...
  def add_child(value)
    if @root.nil?
      @root = Node.new(value)
    else
      @root.add_child(value)
    end
    @root
  end
end

While you are there you could also get rid of build_tree in the BinaryTree class, and move it to the Node.

I hope it will help you in your Ruby Journey.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks! So it sounds like at least my tree building solution was on the right track? these are helpful comments though. Im a bit confused how the to_s correct prints the nodes in order however?
What a lovely answer : )
Also Im a bit confused at what your saying with defining @parent as well.
About your first comment: your solution was definitely on the right track. Not sure what confuses you about to_s; this is what binary trees are about: sorting values, smaller on the left, larger on the right. We print all nodes on the left (by construction only nodes with a value smaller than the root are there), and we finish with the right and larger values. Repeat in sub-trees.
And about your comment on @parent, not sure either what confuses you. My only point is to tell you an empty instance variable like @parent exists event the first time you access its value, and is equal to nil.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.