Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@
"topics": [
]
},
{
"slug": "binary-search-tree",
"difficulty": 1,
"topics": [
"data structures",
"algorithms",
"trees",
"recursion"
]
},
{
"slug": "run-length-encoding",
"difficulty": 1,
Expand Down
190 changes: 190 additions & 0 deletions exercises/binary-search-tree/spec/binary_search_tree_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
require "spec"
require "../src/*"

describe "BinarySearchTree" do
it "sets the root node" do
root = Node.new(1)
root.value.should eq(1)
end

describe "#insert" do
pending "inserts smaller values to the left" do
tree = Node.new(4)
tree.insert(2)

left = tree.left.not_nil!
left.value.should eq(2)

tree.right.should be_nil
end

pending "inserts equal values to the left" do
tree = Node.new(4)
tree.insert(4)

left_node = tree.left.not_nil!
left_node.value.should eq(4)

tree.right.should be_nil
end

pending "inserts greater values to the right" do
tree = Node.new(4)
tree.insert(5)

right_node = tree.right.not_nil!
right_node.value.should eq(5)

tree.left.should be_nil
end
end

describe "#search" do
pending "will return a node if a search if successful" do
tree = Node.new(5)
tree.insert(1)
node = tree.search(1).not_nil!
node.value.should eq(1)
end

pending "will return nil if a searched value is not found" do
tree = Node.new(5)
tree.search(4).should be_nil
end
end

describe "#each" do
pending "traverses the tree in order" do
tree = Node.new(5)
tree.insert(1)
tree.insert(6)
tree.insert(7)
tree.insert(3)
test_array = [1, 3, 5, 6, 7]

tree.each do |value|
value.should eq(test_array.shift)
end
end
end

# Deletion from a binary search tree https://en.wikipedia.org/wiki/Binary_search_tree#Deletion
# There are three possible cases to consider:
# 1. Deleting a node with no children
# 2. Deleting a node with one child
# 3. Deleting a node with two children
describe "#delete" do
pending "can remove the root node" do
tree = Node.new(5)
tree.insert(2)
tree.delete(5)
tree.value.should eq(2)
end

pending "removes a node with no children" do
tree = Node.new(5)
tree.insert(2)
tree.delete(2)
tree.left.should be_nil
end

pending "removes a node with one child" do
tree = Node.new(5)
tree.insert(3)
tree.insert(2)
tree.delete(3)

new_values = [2, 5]
tree.each do |value|
value.should eq(new_values.shift)
end
end

pending "removes a node with two children" do
tree = Node.new(5)
tree.insert(3)
tree.insert(2)
tree.insert(4)
tree.delete(3)

new_values = [2, 4, 5]
tree.each do |value|
value.should eq(new_values.shift)
end
end

pending "removes a left node with two child (complex)" do
tree = Node.new(10)
tree.insert(5)
tree.insert(2)
tree.insert(4)
tree.insert(8)
tree.insert(9)
tree.insert(7)
tree.delete(5)

new_values = [2, 4, 7, 8, 9, 10]
tree.each do |value|
value.should eq(new_values.shift)
end
end

pending "removes a right node with two children (complex)" do
tree = Node.new(1)
tree.insert(5)
tree.insert(2)
tree.insert(4)
tree.insert(8)
tree.insert(9)
tree.insert(7)
tree.delete(5)

new_values = [1, 2, 4, 7, 8, 9]
tree.each do |value|
value.should eq(new_values.shift)
end
end
end

# The following tests check for additional features to the Binary Search Tree
# They are not required to implement a complete BST
# Instead they should be used to dive a little deeper into the Crystal language
describe "crystal-lang specific" do
# Make the Binary Search Tree Enumerable
# See https://crystal-lang.org/api/0.20.3/Enumerable.html
pending "is an Enumerable" do
tree = Node.new(1)
tree.insert(5)
tree.insert(2)
tree.should be_a(Enumerable(Int32))
mapped_values = tree.map { |value| value * 10 }
mapped_values.should eq([10, 20, 50])
end

# If no block is provided to the each method return an Iterator
# See https://crystal-lang.org/api/0.20.3/Iterator.html
pending "will return an iterator if no block is provided" do
tree = Node.new(1)
tree.insert(5)
tree.insert(2)
iter = tree.each
iter.next.should eq 1
iter.next.should eq 2
iter.next.should eq 5
end

# Make the Binary Search Tree Iterable
# See https://crystal-lang.org/api/0.20.3/Iterable.html
pending "is Iterable" do
tree = Node.new(100)
tree.insert(50)
tree.insert(20)
tree.insert(30)
tree.should be_a(Iterable(Int32))
iter = tree.each_cons(2)
iter.next.should eq([20, 30])
iter.next.should eq([30, 50])
iter.next.should eq([50, 100])
end
end
end
1 change: 1 addition & 0 deletions exercises/binary-search-tree/src/binary_search_tree.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Please implement your solution to binary-search-tree in this file
119 changes: 119 additions & 0 deletions exercises/binary-search-tree/src/example.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
class Node(T)
include Enumerable(T)
include Iterable(T)

property value : T
property left : Node(T)?
property right : Node(T)?

def initialize(@value : T)
end

def insert(new_value)
if new_value <= value
if (node = left)
node.insert(new_value)
else
@left = Node(T).new(new_value)
end
else
if (node = right)
node.insert(new_value)
else
@right = Node(T).new(new_value)
end
end
end

def search(search_value)
return self if search_value == value
if search_value < value
if (node = left)
node.search(search_value)
end
else
if (node = right)
node.search(search_value)
end
end
end

def delete(delete_value)
return delete_node if delete_value == value

if delete_value < value
if (node = left)
@left = node.delete(delete_value)
end
else
if (node = right)
@right = node.delete(delete_value)
end
end

self
end

def each
TreeIterator.new(self).each do |v|
yield v
end
end

def each
TreeIterator.new(self)
end

private def delete_node
if one_child?
if left
@value = left.not_nil!.value
@left = nil
else
@value = right.not_nil!.value
@right = nil
end
self
elsif two_children?
node = right.not_nil!
@value = node.each.first
@right = node.delete(value)
self
end
end

private def one_child?
(left && !right) || (right && !left)
end

private def two_children?
left && right
end

private class TreeIterator(T)
include Iterator(T)

def initialize(node : Node(T))
@stack = Array(Node(T)).new
process_left(node)
end

def next
return stop if @stack.empty?

node = @stack.pop
right = node.right
if right
process_left(right)
end
node.value
end

private def process_left(node)
while node
@stack.push(node)
node = node.left
end
end
end
end