Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Binary Search Tree - Exercism Solution

Rob Cornish
November 14, 2017

Binary Search Tree - Exercism Solution

From the Ruby On Rails Oceania Sydney meetup November 2017

Rob Cornish

November 14, 2017
Tweet

Other Decks in Programming

Transcript

  1. Last month I announced that we'd like to start a

    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
  2. A binary tree is a data structure comprised of nodes.

    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
  3. This is a simple representation of a binary tree. The

    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
  4. When we add values to a Binary Search Tree the

    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
  5. In the previous example 5 is the data value of

    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
  6. Here's my entire solution to the challenge. It might be

    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
  7. I used two methods to insert into the tree. The

    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
  8. The first line of the insert method checks if the

    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
  9. If it's not then the right child is set to

    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
  10. The create_node method takes two arguments, the value being inserting

    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
  11. To better understand recursion, it might look like something like

    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
  12. In the context of the binary tree, each time we

    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
  13. Going back to the else condition of the create node

    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
  14. the process starts again but this time, one branch lower

    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
  15. The exercism tests ask to you define an each method

    that traverses the tree and yields the values of each node. What about reading? Binary Trees
  16. This is my implementation of the each method however... #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
  17. let's ignore a couple of lines for now and just

    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
  18. Again this goes down one level of the tree and

    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
  19. The tests implement a method called record_all_data that collects the

    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
  20. Once the data value is yielded each is called on

    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
  21. That's my solution to the Binary Search Tree challenge. If

    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