Skip to content

Commit dc99f88

Browse files
author
Samuel Grave
committed
✨ Add ruby BST
1 parent 3a3b293 commit dc99f88

File tree

2 files changed

+215
-2
lines changed

2 files changed

+215
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,8 +1776,8 @@ In order to achieve greater coverage and encourage more people to contribute to
17761776
</a>
17771777
</td>
17781778
<td> <!-- Ruby -->
1779-
<a href="./CONTRIBUTING.md">
1780-
<img align="center" height="25" src="./logos/github.svg" />
1779+
<a href="./src/ruby/binary_search_tree.rb">
1780+
<img align="center" height="25" src="./logos/ruby.svg" />
17811781
</a>
17821782
</td>
17831783
<td> <!-- JavaScript -->

src/ruby/binary_search_tree.rb

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
class BinarySearchTree
2+
class Node
3+
attr_accessor :left, :right, :value
4+
5+
def initialize(value)
6+
@left = nil
7+
@right = nil
8+
@value = value
9+
end
10+
end
11+
12+
def initialize
13+
@root = nil
14+
end
15+
16+
attr_reader :root
17+
18+
def insert(value)
19+
new_node = Node.new(value)
20+
21+
if @root.nil?
22+
@root = new_node
23+
return self
24+
end
25+
26+
current_node = @root
27+
28+
loop do
29+
if value < current_node.value
30+
if current_node.left.nil?
31+
current_node.left = new_node
32+
return self
33+
end
34+
current_node = current_node.left
35+
else
36+
if current_node.right.nil?
37+
current_node.right = new_node
38+
return self
39+
end
40+
current_node = current_node.right
41+
end
42+
end
43+
44+
self
45+
end
46+
47+
def lookup(value)
48+
return nil if @root.nil?
49+
50+
current_node = @root
51+
52+
until current_node.nil?
53+
if value < current_node.value
54+
current_node = current_node.left
55+
elsif value > current_node.value
56+
current_node = current_node.right
57+
else
58+
return current_node
59+
end
60+
end
61+
62+
nil
63+
end
64+
65+
def remove(value)
66+
return false if @root.nil?
67+
68+
current_node = @root
69+
parent_node = nil
70+
71+
while current_node
72+
if value < current_node.value
73+
parent_node = current_node
74+
current_node = current_node.left
75+
elsif value > current_node.value
76+
parent_node = current_node
77+
current_node = current_node.right
78+
elsif current_node.value == value
79+
# We have a match, get to work!
80+
81+
if current_node.right.nil?
82+
update_parent_link(parent_node, current_node, current_node.left)
83+
elsif current_node.right.left.nil?
84+
current_node.right.left = current_node.left
85+
update_parent_link(parent_node, current_node, current_node.right)
86+
else
87+
leftmost, leftmost_parent = find_leftmost(current_node.right)
88+
leftmost_parent.left = leftmost.right
89+
leftmost.left = current_node.left
90+
leftmost.right = current_node.right
91+
update_parent_link(parent_node, current_node, leftmost)
92+
end
93+
94+
return true
95+
end
96+
end
97+
98+
false
99+
end
100+
101+
def traverse(node = @root)
102+
return nil if node.nil?
103+
104+
tree = { value: node.value }
105+
tree[:left] = traverse(node.left)
106+
tree[:right] = traverse(node.right)
107+
108+
tree
109+
end
110+
111+
private
112+
113+
def update_parent_link(parent_node, current_node, new_node)
114+
if parent_node.nil?
115+
@root = new_node
116+
elsif current_node.value < parent_node.value
117+
parent_node.left = new_node
118+
else
119+
parent_node.right = new_node
120+
end
121+
end
122+
123+
def find_leftmost(node)
124+
leftmost = node.left
125+
leftmost_parent = node
126+
127+
while leftmost.left
128+
leftmost_parent = leftmost
129+
leftmost = leftmost.left
130+
end
131+
132+
[leftmost, leftmost_parent]
133+
end
134+
end
135+
136+
RSpec.describe BinarySearchTree do
137+
let(:bst) { BinarySearchTree.new }
138+
139+
before(:each) do
140+
# Construct the following tree:
141+
#
142+
# 9
143+
# 4 20
144+
# 1 6 15 170
145+
#
146+
bst.insert(9)
147+
bst.insert(4)
148+
bst.insert(6)
149+
bst.insert(20)
150+
bst.insert(170)
151+
bst.insert(15)
152+
bst.insert(1)
153+
end
154+
155+
describe '#insert' do
156+
it 'adds a value to the tree' do
157+
expect(bst.traverse).to eq({ value: 9,
158+
left: { value: 4,
159+
left: { value: 1, left: nil, right: nil },
160+
right: { value: 6, left: nil, right: nil } },
161+
right: { value: 20, left: { value: 15, left: nil, right: nil },
162+
right: { value: 170, left: nil, right: nil } } })
163+
end
164+
end
165+
166+
describe '#lookup' do
167+
it 'finds a value in the tree' do
168+
node = bst.lookup(15)
169+
expect(node.value).to eq(15)
170+
end
171+
172+
it 'returns nil for a value not in the tree' do
173+
node = bst.lookup(100)
174+
expect(node).to be_nil
175+
end
176+
end
177+
178+
describe '#remove' do
179+
it 'removes a leaf node' do
180+
expect(bst.remove(1)).to be true
181+
182+
expect(bst.traverse).to eq({ value: 9,
183+
left: { value: 4, left: nil,
184+
right: { value: 6, left: nil, right: nil } },
185+
right: { value: 20, left: { value: 15, left: nil, right: nil },
186+
right: { value: 170, left: nil, right: nil } } })
187+
end
188+
189+
it 'removes a node with one child' do
190+
expect(bst.remove(20)).to be true
191+
192+
expect(bst.traverse).to eq({ value: 9,
193+
left: { value: 4, left: { value: 1, left: nil, right: nil },
194+
right: { value: 6, left: nil, right: nil } },
195+
right: { value: 170, left: { value: 15, left: nil, right: nil },
196+
right: nil } })
197+
end
198+
199+
it 'removes a node with two children' do
200+
expect(bst.remove(4)).to be true
201+
202+
expect(bst.traverse).to eq({ value: 9,
203+
left: { value: 6, left: { value: 1, left: nil, right: nil },
204+
right: nil },
205+
right: { value: 20, left: { value: 15, left: nil, right: nil },
206+
right: { value: 170, left: nil, right: nil } } })
207+
end
208+
209+
it 'returns false for a value not in the tree' do
210+
expect(bst.remove(100)).to be false
211+
end
212+
end
213+
end

0 commit comments

Comments
 (0)