Skip to content

Commit 50061bb

Browse files
authored
Merge pull request #72 from exercism/bst
Add binary-search-tree
2 parents aad4f93 + 1dbbfc4 commit 50061bb

File tree

4 files changed

+320
-0
lines changed

4 files changed

+320
-0
lines changed

config.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,16 @@
113113
"topics": [
114114
]
115115
},
116+
{
117+
"slug": "binary-search-tree",
118+
"difficulty": 1,
119+
"topics": [
120+
"data structures",
121+
"algorithms",
122+
"trees",
123+
"recursion"
124+
]
125+
},
116126
{
117127
"slug": "run-length-encoding",
118128
"difficulty": 1,
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
require "spec"
2+
require "../src/*"
3+
4+
describe "BinarySearchTree" do
5+
it "sets the root node" do
6+
root = Node.new(1)
7+
root.value.should eq(1)
8+
end
9+
10+
describe "#insert" do
11+
pending "inserts smaller values to the left" do
12+
tree = Node.new(4)
13+
tree.insert(2)
14+
15+
left = tree.left.not_nil!
16+
left.value.should eq(2)
17+
18+
tree.right.should be_nil
19+
end
20+
21+
pending "inserts equal values to the left" do
22+
tree = Node.new(4)
23+
tree.insert(4)
24+
25+
left_node = tree.left.not_nil!
26+
left_node.value.should eq(4)
27+
28+
tree.right.should be_nil
29+
end
30+
31+
pending "inserts greater values to the right" do
32+
tree = Node.new(4)
33+
tree.insert(5)
34+
35+
right_node = tree.right.not_nil!
36+
right_node.value.should eq(5)
37+
38+
tree.left.should be_nil
39+
end
40+
end
41+
42+
describe "#search" do
43+
pending "will return a node if a search if successful" do
44+
tree = Node.new(5)
45+
tree.insert(1)
46+
node = tree.search(1).not_nil!
47+
node.value.should eq(1)
48+
end
49+
50+
pending "will return nil if a searched value is not found" do
51+
tree = Node.new(5)
52+
tree.search(4).should be_nil
53+
end
54+
end
55+
56+
describe "#each" do
57+
pending "traverses the tree in order" do
58+
tree = Node.new(5)
59+
tree.insert(1)
60+
tree.insert(6)
61+
tree.insert(7)
62+
tree.insert(3)
63+
test_array = [1, 3, 5, 6, 7]
64+
65+
tree.each do |value|
66+
value.should eq(test_array.shift)
67+
end
68+
end
69+
end
70+
71+
# Deletion from a binary search tree https://en.wikipedia.org/wiki/Binary_search_tree#Deletion
72+
# There are three possible cases to consider:
73+
# 1. Deleting a node with no children
74+
# 2. Deleting a node with one child
75+
# 3. Deleting a node with two children
76+
describe "#delete" do
77+
pending "can remove the root node" do
78+
tree = Node.new(5)
79+
tree.insert(2)
80+
tree.delete(5)
81+
tree.value.should eq(2)
82+
end
83+
84+
pending "removes a node with no children" do
85+
tree = Node.new(5)
86+
tree.insert(2)
87+
tree.delete(2)
88+
tree.left.should be_nil
89+
end
90+
91+
pending "removes a node with one child" do
92+
tree = Node.new(5)
93+
tree.insert(3)
94+
tree.insert(2)
95+
tree.delete(3)
96+
97+
new_values = [2, 5]
98+
tree.each do |value|
99+
value.should eq(new_values.shift)
100+
end
101+
end
102+
103+
pending "removes a node with two children" do
104+
tree = Node.new(5)
105+
tree.insert(3)
106+
tree.insert(2)
107+
tree.insert(4)
108+
tree.delete(3)
109+
110+
new_values = [2, 4, 5]
111+
tree.each do |value|
112+
value.should eq(new_values.shift)
113+
end
114+
end
115+
116+
pending "removes a left node with two child (complex)" do
117+
tree = Node.new(10)
118+
tree.insert(5)
119+
tree.insert(2)
120+
tree.insert(4)
121+
tree.insert(8)
122+
tree.insert(9)
123+
tree.insert(7)
124+
tree.delete(5)
125+
126+
new_values = [2, 4, 7, 8, 9, 10]
127+
tree.each do |value|
128+
value.should eq(new_values.shift)
129+
end
130+
end
131+
132+
pending "removes a right node with two children (complex)" do
133+
tree = Node.new(1)
134+
tree.insert(5)
135+
tree.insert(2)
136+
tree.insert(4)
137+
tree.insert(8)
138+
tree.insert(9)
139+
tree.insert(7)
140+
tree.delete(5)
141+
142+
new_values = [1, 2, 4, 7, 8, 9]
143+
tree.each do |value|
144+
value.should eq(new_values.shift)
145+
end
146+
end
147+
end
148+
149+
# The following tests check for additional features to the Binary Search Tree
150+
# They are not required to implement a complete BST
151+
# Instead they should be used to dive a little deeper into the Crystal language
152+
describe "crystal-lang specific" do
153+
# Make the Binary Search Tree Enumerable
154+
# See https://crystal-lang.org/api/0.20.3/Enumerable.html
155+
pending "is an Enumerable" do
156+
tree = Node.new(1)
157+
tree.insert(5)
158+
tree.insert(2)
159+
tree.should be_a(Enumerable(Int32))
160+
mapped_values = tree.map { |value| value * 10 }
161+
mapped_values.should eq([10, 20, 50])
162+
end
163+
164+
# If no block is provided to the each method return an Iterator
165+
# See https://crystal-lang.org/api/0.20.3/Iterator.html
166+
pending "will return an iterator if no block is provided" do
167+
tree = Node.new(1)
168+
tree.insert(5)
169+
tree.insert(2)
170+
iter = tree.each
171+
iter.next.should eq 1
172+
iter.next.should eq 2
173+
iter.next.should eq 5
174+
end
175+
176+
# Make the Binary Search Tree Iterable
177+
# See https://crystal-lang.org/api/0.20.3/Iterable.html
178+
pending "is Iterable" do
179+
tree = Node.new(100)
180+
tree.insert(50)
181+
tree.insert(20)
182+
tree.insert(30)
183+
tree.should be_a(Iterable(Int32))
184+
iter = tree.each_cons(2)
185+
iter.next.should eq([20, 30])
186+
iter.next.should eq([30, 50])
187+
iter.next.should eq([50, 100])
188+
end
189+
end
190+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Please implement your solution to binary-search-tree in this file
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
class Node(T)
2+
include Enumerable(T)
3+
include Iterable(T)
4+
5+
property value : T
6+
property left : Node(T)?
7+
property right : Node(T)?
8+
9+
def initialize(@value : T)
10+
end
11+
12+
def insert(new_value)
13+
if new_value <= value
14+
if (node = left)
15+
node.insert(new_value)
16+
else
17+
@left = Node(T).new(new_value)
18+
end
19+
else
20+
if (node = right)
21+
node.insert(new_value)
22+
else
23+
@right = Node(T).new(new_value)
24+
end
25+
end
26+
end
27+
28+
def search(search_value)
29+
return self if search_value == value
30+
if search_value < value
31+
if (node = left)
32+
node.search(search_value)
33+
end
34+
else
35+
if (node = right)
36+
node.search(search_value)
37+
end
38+
end
39+
end
40+
41+
def delete(delete_value)
42+
return delete_node if delete_value == value
43+
44+
if delete_value < value
45+
if (node = left)
46+
@left = node.delete(delete_value)
47+
end
48+
else
49+
if (node = right)
50+
@right = node.delete(delete_value)
51+
end
52+
end
53+
54+
self
55+
end
56+
57+
def each
58+
TreeIterator.new(self).each do |v|
59+
yield v
60+
end
61+
end
62+
63+
def each
64+
TreeIterator.new(self)
65+
end
66+
67+
private def delete_node
68+
if one_child?
69+
if left
70+
@value = left.not_nil!.value
71+
@left = nil
72+
else
73+
@value = right.not_nil!.value
74+
@right = nil
75+
end
76+
self
77+
elsif two_children?
78+
node = right.not_nil!
79+
@value = node.each.first
80+
@right = node.delete(value)
81+
self
82+
end
83+
end
84+
85+
private def one_child?
86+
(left && !right) || (right && !left)
87+
end
88+
89+
private def two_children?
90+
left && right
91+
end
92+
93+
private class TreeIterator(T)
94+
include Iterator(T)
95+
96+
def initialize(node : Node(T))
97+
@stack = Array(Node(T)).new
98+
process_left(node)
99+
end
100+
101+
def next
102+
return stop if @stack.empty?
103+
104+
node = @stack.pop
105+
right = node.right
106+
if right
107+
process_left(right)
108+
end
109+
node.value
110+
end
111+
112+
private def process_left(node)
113+
while node
114+
@stack.push(node)
115+
node = node.left
116+
end
117+
end
118+
end
119+
end

0 commit comments

Comments
 (0)