monthly code challenge with the idea of trying to get everyone involved and share some Ruby skills. To get the ball rolling I'd like to take 5 mins to present my solution to the exercism binary search tree challenge. Binary Search Tree What it is? How to create one How to read one h"p:/ /exercism.io/exercises/ruby/binary-search-tree/readme Binary Trees
Each node has three attributes, a data value, a left pointer and a right pointer. A Binary Tree Is a data structure comprising of nodes. Each node holds three a6ributes - A data value - A pointer to a le; child node - A pointer to a right child node Binary Trees
first node of the tree is the root node. Nodes connected to another node from above are called child nodes. A node that has null pointers or no children is called a leaf node. Representa)on of a Binary Tree 5 # root node / \ / \ 3 8 # child of 5. parent of 10 / \ / \ 4 6 10 # leaf Binary Trees
location where they're inserted is based on their relation to the data value of the parent node. Values less than or equal to the data value are inserted to the left and values greater than the data value are inserted to the right. Adding values to the tree Values less than or equal to the node value are inserted to the le1 Values greater than the node value are inserted to the right. Binary Trees
the root node. When we insert 3, being less than 5 it becomes the left child node. When we insert 4, starting at the root it's less then the 5 so we move down the left and hit 3. It's greater than the 3 so 4 becomes the data value of the right child with 3 as it's parent. Adding values to the tree Values less than or equal to the node value are inserted to the le1 Values greater than the node value are inserted to the right. 5 / \ / \ 3 8 \ 4 Binary Trees
a bit small to read clearly so lets dive into the methods used to insert values into the tree first. class Bst attr_reader :data, :left, :right def initialize(value) @data = value end def insert(value) # -- INSERT if value <= data @left = create_node(value, @left) else @right = create_node(value, @right) end self end def each(&block) # -- READ return to_enum(&:each) unless block_given? left.each(&block) unless left.nil? yield data right.each(&block) unless right.nil? self end private def create_node(value, node) if node.nil? Bst.new(value) else node.insert(value) end end end Binary Trees
test provided in the challenge are looking for a method called insert that takes one argument which is the value to insert. Inser&ng def insert(value) if value <= data @left = create_node(value, @left) else @right = create_node(value, @right) end self end def create_node(value, node) if node.nil? Bst.new(value) else node.insert(value) end end Binary Trees
value is less than or equal to the current value for the data attribute. If it is, it sets the value for the left child to the result of the second method called create node. def insert(value) if value <= data @left = create_node(value, @left) else @right = create_node(value, @right) end self end def create_node(value, node) if node.nil? Bst.new(value) else node.insert(value) end end Binary Trees
the return value for the create node method. def insert(value) if value <= data @left = create_node(value, @left) else @right = create_node(value, @right) end self end def create_node(value, node) if node.nil? Bst.new(value) else node.insert(value) end end Binary Trees
and the node being inserting it into. It first checks if the node is exists, which it won't if we're already at a leaf of the tree. This is where the concept of recursion enters. If the node is nil, we create a new instance of the Bst class and pass the value. def insert(value) if value <= data @left = create_node(value, @left) else @right = create_node(value, @right) end self end def create_node(value, node) if node.nil? Bst.new(value) else node.insert(value) end end Binary Trees
this. At the top there's a Bst instance that has an attribute called left, that could contain another Bst instance, that again has and attribute called left that could contain another Bst instance. ------- | Bst | ------- | @left | ------- -------> | Bst | ------- | @left | ------- -------> | Bst | ------- Binary Trees
create a new nested instance of Bst we're giving it the data value. This recursive insertion method is how the tree is generated. ------- | Bst | data: 7 ------- | @left | ------- -------> | Bst | data: 4 ------- | @left | ------- -------> | Bst | data: 3 ------- Binary Trees
method. If there is a node, which would be the case if there's already a left node with a value less than the parent, we call the insert method of that node and... def insert(value) if value <= data @left = create_node(value, @left) else @right = create_node(value, @right) end self end def create_node(value, node) if node.nil? Bst.new(value) else node.insert(value) end end Binary Trees
on our tree. So that's inserting. ------- | Bst | data: 7 ------- | @left -- not nil, there's alreay a left branch. | ------- -------> | Bst | data: 4 ------- | #insert(value) # is the value <= data? ... Calling #insert on the node decides whether to create a le2 or right. Binary Trees
focus on what's really happening. Firstly notice the method declares the block as an argument. Ruby methods take a block implicitly but here we need to define it as an argument so it can passed around. The first action this method takes is to check if there's a left branch of the current node and if so, call it's each method. This is another example of recursion. #each def each(&block) return to_enum(&:each) unless block_given? left.each(&block) unless left.nil? yield data right.each(&block) unless right.nil? self end Binary Trees
follows the same process of calling each on the left branch if one exists until we finally reach the leaf. When this happens, the method yields the value of data to the block. Let's have a look at how the test use the each method. ------- | Bst | data: 7 ------- | @left -- not nil, so call each. | ------- -------> | Bst | data: 4 ------- | if there is no left, yield the data value to the block Binary Trees
values yielded by each and puts them in an array. def each(&block) return to_enum(&:each) unless block_given? left.each(&block) unless left.nil? yield data right.each(&block) unless right.nil? self end --- def record_all_data(bst) all_data = [] bst.each { |data| all_data << data } all_data end Binary Trees
the right branch and the cycle is repeated all over again. Traversing the tree using this method returns the values inorder so the array created should contain all the values of the tree in order numerically. def each(&block) return to_enum(&:each) unless block_given? left.each(&block) unless left.nil? yield data right.each(&block) unless right.nil? self end --- def record_all_data(bst) all_data = [] bst.each { |data| all_data << data } all_data end Binary Trees
you're interested in more challenges like this check out exercism.io and make sure you join the Ruby on Rails Oceania Sydney team. If you're interested in more challenges like this check out www.exercism.io and join the Ruby on Rails Oceania Sydney team Binary Trees