Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit dc8654c

Browse files
Add AVL implementation (#4)
* update index on remove * add basic avl implementation * add basic avl tests * export avl useful methods * add traverse method to avl structure * add find method to avl implementation * add capability to update node value in the tree
1 parent f55c828 commit dc8654c

File tree

11 files changed

+292
-2
lines changed

11 files changed

+292
-2
lines changed

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,4 @@ build-iPhoneSimulator/
5656
# .rubocop-https?--*
5757

5858
*.db
59+
notes/

‎lib/amnesia/memtable.rb‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module Amnesia
2+
class Memtable
3+
attr_accessor :status
4+
5+
def initialize
6+
@store = Amnesia::Support::AVL.new
7+
@status = :active
8+
end
9+
10+
def read(key)
11+
@store.find(key)
12+
end
13+
14+
def write(key, value)
15+
@store.insert(key, value)
16+
end
17+
18+
def flush(segment_handler)
19+
# TODO: Write a whole block
20+
@store.traverse { |node| segment_handler.store({ key: node.key, value: node.value }) }
21+
end
22+
end
23+
end

‎lib/amnesia/memtable_handler.rb‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
module Amnesia
2+
class MemtableHandler
3+
def initialize(segment_handler)
4+
@segment_handler = segment_handler
5+
@memtables = [Amnesia::Memtable.new]
6+
end
7+
8+
def read(key)
9+
memtable.read(key)
10+
end
11+
12+
def write(key, value)
13+
memtable.write(key, value)
14+
end
15+
16+
def flush
17+
memtable.status = :flushing
18+
memtable.flush(segment_handler)
19+
memtable.status = :finished_flusing
20+
end
21+
22+
private
23+
24+
def memtable
25+
@memtables.first
26+
end
27+
end
28+
end

‎lib/amnesia/segment.rb‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ def retrieve(key)
1515
end
1616

1717
def remove(key)
18-
@index_structure.remove(key)
19-
@storage.delete(key)
18+
offset = @storage.delete(key)
19+
@index_structure.add(key, [@storage.size, @storage.size + offset - 1])
20+
1
2021
end
2122

2223
def store(hash_input)

‎lib/amnesia/support/avl.rb‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
module Amnesia::Support
2+
class AVL
3+
attr_reader :root
4+
5+
def initialize
6+
@root = nil
7+
end
8+
9+
def insert(key, value = nil)
10+
@root = insert_node(@root, key, value)
11+
end
12+
13+
def traverse(&block)
14+
traverse_node(@root, block)
15+
end
16+
17+
def find(key)
18+
find_node(@root, key)
19+
end
20+
21+
private
22+
23+
def find_node(node, key)
24+
return node if node.nil? || node.key == key
25+
26+
return find_node(node.left, key) if key < node.key
27+
28+
return find_node(node.right, key) if key >= node.key
29+
end
30+
31+
def traverse_node(node, block)
32+
return :noop if node.nil?
33+
34+
traverse_node(node.left, block)
35+
block.call({ key: node.key, value: node.value })
36+
traverse_node(node.right, block)
37+
end
38+
39+
def height(node)
40+
return -1 if node.nil?
41+
42+
node.height
43+
end
44+
45+
def update_node_value(node, value)
46+
return if node.nil?
47+
48+
node.value = value
49+
50+
node
51+
end
52+
53+
def insert_node(root, key, value)
54+
return Node.new(key, value) if root.nil?
55+
56+
return update_node_value(root, value) if key == root.key
57+
58+
if key < root.key
59+
root.left = insert_node(root.left, key, value)
60+
else
61+
root.right = insert_node(root.right, key, value)
62+
end
63+
64+
update_height(root)
65+
66+
node_balance_factor = balance_factor(root)
67+
68+
if node_balance_factor > 1
69+
root.left = rotate_left(root.left) if key > root.left.key
70+
71+
return rotate_right(root)
72+
elsif node_balance_factor < -1
73+
root.right = rotate_right(root.right) if key < root.right.key
74+
75+
return rotate_left(root)
76+
end
77+
78+
root
79+
end
80+
81+
def rotate_right(x)
82+
a = x.left
83+
84+
x.left = a.right
85+
a.right = x
86+
87+
update_height(x)
88+
update_height(a)
89+
90+
a
91+
end
92+
93+
def rotate_left(a)
94+
x = a.right
95+
96+
a.right = x.left
97+
x.left = a
98+
99+
update_height(a)
100+
update_height(x)
101+
102+
x
103+
end
104+
105+
def update_height(node)
106+
node.height = [height(node.left), height(node.right)].max + 1
107+
end
108+
109+
def balance_factor(node)
110+
height(node.left) - height(node.right)
111+
end
112+
end
113+
114+
# TODO: This can be a simple Struct
115+
class Node
116+
attr_accessor :key, :value, :left, :right, :height
117+
118+
def initialize(key, value = nil, left = nil, right = nil)
119+
@key = key
120+
@value = value
121+
@left = left
122+
@right = right
123+
@height = 0
124+
end
125+
end
126+
end

‎spec/amnesia/memtable_handler_spec.rb‎

Whitespace-only changes.

‎spec/amnesia/memtable_spec.rb‎

Whitespace-only changes.

‎spec/amnesia/segment_handler_spec.rb‎

Whitespace-only changes.

‎spec/amnesia/segment_spec.rb‎

Whitespace-only changes.

‎spec/amnesia/support/avl_spec.rb‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
RSpec.describe Amnesia::Support::AVL do
2+
subject { described_class.new }
3+
4+
describe '#insert' do
5+
context 'when the tree is empty' do
6+
it 'add the node as the root of the tree' do
7+
subject.insert(10)
8+
9+
expect(subject.root.key).to eq(10)
10+
end
11+
end
12+
13+
context 'when the tree already contains nodes' do
14+
context 'and the insertion makes it unbalanced' do
15+
it 'adds nodes and balances the tree with single rotations to left' do
16+
subject.insert(19)
17+
subject.insert(20)
18+
subject.insert(21)
19+
20+
expect(subject.root.key).to eq(20)
21+
expect(subject.root.left.key).to eq(19)
22+
expect(subject.root.right.key).to eq(21)
23+
end
24+
25+
it 'adds nodes and balances the tree with single rotations to right' do
26+
subject.insert(21)
27+
subject.insert(20)
28+
subject.insert(19)
29+
30+
expect(subject.root.key).to eq(20)
31+
expect(subject.root.left.key).to eq(19)
32+
expect(subject.root.right.key).to eq(21)
33+
end
34+
35+
it 'adds nodes and balances the tree with double rotations to left' do
36+
subject.insert(19)
37+
subject.insert(25)
38+
subject.insert(21)
39+
40+
expect(subject.root.key).to eq(21)
41+
expect(subject.root.left.key).to eq(19)
42+
expect(subject.root.right.key).to eq(25)
43+
end
44+
45+
it 'adds nodes and balances the tree with double rotations to left' do
46+
subject.insert(25)
47+
subject.insert(19)
48+
subject.insert(21)
49+
50+
expect(subject.root.key).to eq(21)
51+
expect(subject.root.left.key).to eq(19)
52+
expect(subject.root.right.key).to eq(25)
53+
end
54+
end
55+
end
56+
57+
context 'when the inserted key already exists in the tree' do
58+
it 'updates the node value' do
59+
subject.insert(10, 'a')
60+
subject.insert(11, 'b')
61+
subject.insert(12, 'c')
62+
63+
expect(subject.root.key).to eq(11)
64+
expect(subject.root.right.key).to eq(12)
65+
expect(subject.root.right.value).to eq('c')
66+
67+
subject.insert(12, 'd')
68+
69+
expect(subject.root.key).to eq(11)
70+
expect(subject.root.right.key).to eq(12)
71+
expect(subject.root.right.value).to eq('d')
72+
end
73+
end
74+
end
75+
76+
describe '#traverse' do
77+
it 'executes the given block for each node' do
78+
subject.insert(10, 'abc')
79+
subject.insert(11, 'def')
80+
subject.insert(12, 'ghi')
81+
82+
expect do |block|
83+
subject.traverse(&block)
84+
end.to yield_successive_args({ key: 10, value: 'abc' }, { key: 11, value: 'def' },
85+
{ key: 12, value: 'ghi' })
86+
end
87+
end
88+
89+
describe '#find' do
90+
context 'when the key exists in the tree' do
91+
it 'returns the found node' do
92+
subject.insert(10, 'abc')
93+
94+
found = subject.find(10)
95+
96+
expect(found.key).to eq(10)
97+
end
98+
end
99+
100+
context 'when the key does not exist in the tree' do
101+
it 'returns nil' do
102+
subject.insert(10, 'abc')
103+
104+
found = subject.find(1)
105+
106+
expect(found).to be_nil
107+
end
108+
end
109+
end
110+
end

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /