Using Comparable Mixin in Binary Search Tree Node Methods - ruby

I am implementing a Binary Search Tree using Ruby. I implemented a comparable mixin to conveniently compare Node objects within the tree (compares the data attribute), however I am finding a NoMethodError when the "leaf?" method is being called:
<=>': undefined method 'data' for nil:NilClass (NoMethodError)
Here is my implementation:
# Node Class
class BstNode
include Comparable
attr_accessor :left
attr_accessor :right
attr_reader :data
def initialize(val)
#data = val
#left = nil
#right = nil
end
def <=>(node)
return self.data <=> node.data
end
def leaf?
return self.left == nil && self.right == nil
end
end
# Tree Class
class BstTree
attr_accessor :root
def initialize(arr)
#root = build_tree(arr)
end
def build_tree(arr)
arr.each.with_index do |val, i|
if i == 0
self.root = BstNode.new(arr[i])
else
insert_node(val)
end
end
return self.root
end
def insert_node(val, node = self.root)
curr = node
left = val < curr.data ? true : false
if curr.leaf?
if left
curr.left = BstNode.new(val)
else
curr.right = BstNode.new(val)
end
elsif curr.left == nil && left
curr.left = BstNode.new(val)
elsif curr.right == nil && !left
curr.right = BstNode.new(val)
else
return left ? insert_node(val, curr.left) : insert_node(val, curr.right)
end
return true
end
end
tree = BstTree.new([4])
puts tree.insert_node(6)
puts tree.insert_node(8)
Any comments or suggestions are appreciated.

Basically Comparable module implements methods like <, <=, ==, >, >=, between? on the object(full list here https://docs.ruby-lang.org/en/2.5.0/Comparable.html) and all of them are basing their functionality of the <=>(space-ship operator).
So when you compare BstNode with nil like BstNode.new(8) == nil the == method will call space-ship operator to resolve the comparison. Because your implementation of it tries to call data method on whatever is passed, it will try to do so even on the nil (equivalent of calling nil.data).
Just add safe navigator(&.) to the space-ship like this, and if think you will be good.
def <=>(node)
return self.data <=> node&.data
end

Related

Ruby Binary Search Tree insert method stack level too deep

I am trying to write a recursive insert method for a binary search tree but keep getting stack level too deep What is going on that it keeps giving me the error?
my node class
class Node
attr_accessor :value, :left, :right
def initialize(value)
#value = value
left = nil
right = nil
end
end
binary search tree class
class Bst
attr_accessor :root, :size
def initialize
#root = nil
#size = 0
end
def insert(value, root=nil)
if #root == nil
#root = Node.new(value)
end
if value < #root.value
if #root.left == nil
#root.left = Node.new(value)
else
return insert(value, #root.left)
end
return root
end
if value > #root.value
if #root.right == nil
#root.right = Node.new(value)
else
return insert(value, #root.right)
end
end
return root
end
It happens once I try to add 4 to the tree
tree = Bst.new
tree.insert(10)
tree.insert(6)
tree.insert(19)
tree.insert(4)
When you recurse and provide new root, you are still comparing the value with #root.value.
Since 4 is still less than 10, you recurse, and pass #root.left as root. However, root is never used; you are again comparing #root.value, and recursing with #root.left, and those never change; thus, you have infinite recursion (or at least infinite till you blow the stack).
You want to be comparing to root.value, and recursing with root.left instead.
Having #root and root be different things is confusing, and leads to logic errors. Better variable naming would likely have prevented this error.
EDIT:
class Node
attr_accessor :value, :left, :right
def initialize(value)
#value = value
#left = nil
#right = nil
end
end
class Bst
attr_accessor :root, :size
def initialize
#root = nil
#size = 0
end
def insert(value, node=nil)
unless #root
#root = Node.new(value)
return
end
node ||= #root
if value < node.value
if node.left
return insert(value, node.left)
else
node.left = Node.new(value)
end
elsif value > node.value
if node.right
return insert(value, node.right)
else
node.right = Node.new(value)
end
end
#size += 1
return node
end
end
tree = Bst.new
tree.insert(10)
tree.insert(6)
tree.insert(19)
tree.insert(4)
p tree

inserting in linked lists with ruby

Hi I'm trying to insert a value at a specific index in a linked list in ruby.
Here is my code thus far:
class Node
attr_accessor :data, :pointer, :next
def initialize(data, pointer = nil)
#data = data
#pointer = pointer
end
def next
#data = #pointer
end
end
class LinkedList
attr_accessor :head, :data, :pointer
def initialize(data)
#head = Node.new(data, pointer)
end
def index_at(value_of_index)
current = head
value_of_index.times do
if current.pointer == nil
current = Node.new(nil, nil)
return current = current.data
else
current = current.next
end
end
current.data
end
def insert_at_index(index, value)
current = head
index.times do
current.next
end
current = Node.new(value)
end
end
The problem I've having is with the def insert_at_index method... I can't seem to figure out how to place the new node at the index and value. Any help you can give me would be greatly appreciated.
Say your linked list looks like this:
a -> b -> d -> e
To insert c into the 3rd index, you would move to the second index to get b, create a new node, set b's next to the new node c, and set c's next to the old third index item, d.
This will give you:
a -> b -> c -> d -> e
That said, the code should look like this:
def insert_at_index(index, value)
current = head
# make current b. You may want to put this in a function node_at_index
(index - 1).times do
raise "List not long enough" if current.nil?
current = current.next
end
new_node = Node.new(value) # new node c
new_node.next = current.next # c's next is b's next, d
current.next = new_node # b's next is c
end
From the looks of it there seem to be other issues with your code as well. You're overwriting #data in your next function (you probably meant to just return #pointer), LinkedList#initialize doesn't have pointer defined, etc
You could simplify your Node to look like this:
class Node
attr_accessor :data, :next
def initialize(data, next=nil)
#data = data
#next = next
end
end
Which should work for you.
Here is one solution that worked for me:
class Node
attr_accessor :data, :pointer
alias_method :next, :pointer
def initialize(data, pointer = nil)
#data = data
#pointer = pointer
end
def next
#pointer
end
end
class LinkedList
attr_accessor :head, :data, :pointer
def initialize(data)
#head = Node.new(data, pointer)
end
def index_at(value_of_index)
current = head
value_of_index.times do
if current.pointer == nil
current = Node.new(nil, nil)
return current = current.data
else
current = current.next
end
end
current.data
end
def insert_at_index(index, value)
current = head
(index - 1).times do
if current.pointer != nil
current = current.next
end
end
new_node = Node.new(value)
if current.pointer != nil
new_node.pointer = current.pointer
end
current.pointer = new_node
end
end

Can't break out of simple while loop

I'm writing a search method for my DoublyLinkedList class as such:
def search(val)
current = #head
while current != nil
if current.node_id == val
return current
else
current = current.prev_node
end
end
return nil
end
However when I try to use this search method I seem to be stuck in the while loop.
Here are my DoublyLinkedList and Node classes for reference:
class Node
attr_accessor :node_id, :next_node, :prev_node
def initialize(node_id)
#node_id = node_id
#prev_node = nil
#next_node = nil
end
end
class DoublyLinkedList
attr_accessor :head, :size
def initialize
#size = 0
#head = nil
end
def add(node)
if #head == nil
#head = node
else
node.prev_node = #head
#head.next_node = node
#head = node
end
#size += 1
end
def search(val)
current = #head
while current != nil
if current.node_id == val
return current
break
else
current = current.prev_node
end
end
return nil
end
end
Here's how I'm testing my method:
linked_list = DoublyLinkedList.new
node1 = Node.new '1'
linked_list.add(node1)
puts linked_list.search(node1.node_id)
Sorry if for such verbosity(?) for such a simple question but I just can't see why my while loop won't break - it should return the found node's node_id!
Try break current instead of return current to get out of a loop.

Ruby Singly Linked List Implementation

I'd like to write an exist function that returns true if a value exists as a node in a linkedlist and returns false otherwise. So far, I have the following code which always returns true. Any help is appreciated:
class SinglyLinkedList
attr_accessor :head, :tail, :count
def initialize
#head = nil
#tail = nil
#count = 0
end
def insert_front(value)
node = Node.new(value)
if #head.nil?
#head = node
#tail = node
else
node.next = #head
#head = node
end
#count +=1
end
def print_first()
puts head.data
end
def print_last()
puts tail.data
end
def exist(value)
walker = #head
until walker.nil?
if walker.data == value
return true
end
walker = walker.next
end
false
end
def size()
count
end
end
class Node
attr_accessor :data, :next
def initialize(data)
#next = nil
#data = data
end
end
This is my test code:
list = SinglyLinkedList.new
list.insert_front(1)
list.exist(2)
which returns true.
Before solution just consider that singly linked list can insert only via append to list. You try to insert nodes in wrong direction.
The problem with you code is that you use reserved method name next, just refactor you code and write simple:
class SinglyLinkedList
class Node
attr_reader :value
attr_accessor :pointer
def initialize(value, pointer = nil)
#value = value
#pointer = pointer
#count = 0
end
end
attr_reader :head, :tail
def initialize
#head = nil
#tail = nil
end
def insert(value)
node = Node.new(value)
#count += 1
if #head.nil?
#head = node
#tail = node
else
#tail.pointer = node
#tail = node
end
end
def inspect
return [] unless #head
values = []
node = #head
begin
values << node.value
node = node.pointer
end while node != nil
values
end
def exists?(value)
return false unless #head
node = #head
begin
node = node.pointer
end while node != nil && node.value != value
node.nil?
end
end

Matching end tags in Ruby

To learn Ruby, I'm implementing different data structures starting with nodes and a simple stack. If I matching each def with a corresponding end, there are lots of error about expecting $end (EOF) but getting end. So I could fix it by stacking some ends at the end of the class, but obviously I don't know why that works.
require "Node"
class Stack
attr_accessor :top
def size
#size
end
def push(node)
if node && node.next
node.next = top
top = node
end
size++
def pop()
if top != nil
top = top.next
end
size--
def to_s
if top != nil
temp = top
while temp != nil
puts temp.value
temp = temp.next
end
else
puts "The stack is empty"
end
end
end
end
end
The node class is very simple and shouldn't cause any problems:
class Node
attr_accessor :next
def initialize(value)
#value = value
end
end
Everything works fine on that Frankenstein Stack, except pushing a node results in NoMethodError: undefined method +#' for nil:NilClass. Not sure if that is related, but I'm mostly concerned with the syntax of method/class declaration and using end
You get an error because ruby does not have ++ and -- operators.
Ruby understand the following constructs
size++
def pop()
# and
size--
def to_s()
like
size + +def pop()
# and
size - -def to_s()
Ruby syntax is expression-oriented and method definition is expression in Ruby. Method definition expressions (def pop() and def to_s()) are evaluated to nil (in your code you actually define method pop inside push method body and to_s inside pop method body). And this is why you get NoMethodError: undefined method +#' for nil:NilClass error - it evaluates expression size + +nil and nil does not define unary plus operator. In this expression first + is an Fixnum addition operator (size is Fixnum), and second + is unary plus operator of nil (result of def pop() expression).
Use += 1 and -= 1 instead of ++ and --. Your code should look like this:
class Stack
attr_accessor :top
def size
#size
end
def push(node)
if node && node.next
node.next = top
top = node
end
#size += 1 # #size, not `size` because you have `size` getter and you cannot modify size with getter method
end
def pop()
if top != nil
top = top.next
end
#size -= 1
end
def to_s
if top != nil
temp = top
while temp != nil
puts temp.value
temp = temp.next
end
else
puts "The stack is empty"
end
end
end
Your defs don’t have a matching end. Also, Ruby does not have a ++ operator; you’ll have to use += 1 instead.

Resources