Skip to content

Commit e6ae910

Browse files
authored
Merge pull request #334 from itsmegrave/main
✨ Add ruby BST
2 parents 9b7e6d2 + 2227ce5 commit e6ae910

File tree

2 files changed

+217
-2
lines changed

2 files changed

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

0 commit comments

Comments
 (0)