DSA 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/DSA.gemspec +24 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +23 -0
- data/README.md +127 -0
- data/Rakefile +2 -0
- data/lib/DSA.rb +10 -0
- data/lib/DSA/algorithm.rb +69 -0
- data/lib/DSA/binary_search_tree.rb +710 -0
- data/lib/DSA/list.rb +183 -0
- data/lib/DSA/priority_queue.rb +70 -0
- data/lib/DSA/stack_and_queue.rb +151 -0
- data/lib/DSA/version.rb +3 -0
- data/test/algorithms_test.rb +91 -0
- data/test/binary_search_tree_test.rb +287 -0
- data/test/list_test.rb +84 -0
- data/test/priority_queue_test.rb +49 -0
- data/test/stack_and_queue_test.rb +96 -0
- metadata +96 -0
data/lib/DSA/list.rb
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
module DSA
|
2
|
+
# Exception
|
3
|
+
class ListInsertError < StandardError
|
4
|
+
end
|
5
|
+
class ListRemovalError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Node
|
9
|
+
class ListNode
|
10
|
+
attr_accessor :element, :prev, :next
|
11
|
+
def initialize(e)
|
12
|
+
@element = e
|
13
|
+
@prev = nil
|
14
|
+
@next = nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# ListIterator provides iteration on the list and constant time insertion and removal
|
19
|
+
class ListIterator
|
20
|
+
def initialize(node, list)
|
21
|
+
@node = node
|
22
|
+
@list = list
|
23
|
+
end
|
24
|
+
|
25
|
+
def next
|
26
|
+
@node = @node.next
|
27
|
+
raise StopIteration if @node == @list.tail || !@node
|
28
|
+
@node.element
|
29
|
+
end
|
30
|
+
|
31
|
+
def previous
|
32
|
+
@node = @node.prev
|
33
|
+
raise StopIteration if @node == @list.head || !@node
|
34
|
+
@node.element
|
35
|
+
end
|
36
|
+
|
37
|
+
def insert(e)
|
38
|
+
@list.insert_node_between @node.prev, @node, ListNode.new(e)
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove
|
42
|
+
node = @list.remove_node(@node)
|
43
|
+
node.element
|
44
|
+
end
|
45
|
+
|
46
|
+
def update(e)
|
47
|
+
@node.element = e
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# doubly linked list
|
54
|
+
class List
|
55
|
+
attr_reader :length
|
56
|
+
attr_reader :head, :tail # head and tail are two empty guards
|
57
|
+
include Enumerable
|
58
|
+
|
59
|
+
def initialize
|
60
|
+
@head = ListNode.new nil
|
61
|
+
@tail = ListNode.new nil
|
62
|
+
@head.next = @tail
|
63
|
+
@tail.prev = @head
|
64
|
+
@length = 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def push(e)
|
68
|
+
insert_node_between @tail.prev, @tail, ListNode.new(e)
|
69
|
+
end
|
70
|
+
|
71
|
+
def pop
|
72
|
+
node = remove_node @tail.prev
|
73
|
+
node.element
|
74
|
+
end
|
75
|
+
|
76
|
+
def unshift(e)
|
77
|
+
insert_node_between @head, @head.next, ListNode.new(e)
|
78
|
+
end
|
79
|
+
|
80
|
+
def shift
|
81
|
+
node = remove_node @head.next
|
82
|
+
node.element
|
83
|
+
end
|
84
|
+
|
85
|
+
def first
|
86
|
+
self[0]
|
87
|
+
end
|
88
|
+
|
89
|
+
def last
|
90
|
+
self[@length-1]
|
91
|
+
end
|
92
|
+
|
93
|
+
def empty?
|
94
|
+
@length == 0
|
95
|
+
end
|
96
|
+
|
97
|
+
# insert an element at position index(0 based), linear time cost, use with caution, iterator preferred
|
98
|
+
def insert_at(index, e)
|
99
|
+
push e if index > @length - 1
|
100
|
+
node = get_node index
|
101
|
+
insert_node_between node.prev, node, ListNode.new(e)
|
102
|
+
end
|
103
|
+
|
104
|
+
# remove an element at position index(0 based), linear time cost, use with caution, iterator preferred
|
105
|
+
def remove_at(index)
|
106
|
+
node = get_node index
|
107
|
+
remove_node node
|
108
|
+
node.element
|
109
|
+
end
|
110
|
+
|
111
|
+
# access an element at position index(0 based), linear time cost, use with caution
|
112
|
+
def [](index)
|
113
|
+
get_node(index).element
|
114
|
+
end
|
115
|
+
|
116
|
+
def each
|
117
|
+
node = @head.next
|
118
|
+
while 1
|
119
|
+
break if node == @tail
|
120
|
+
yield node.element
|
121
|
+
node = node.next
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# an iterator starting from head
|
126
|
+
def begin_iterator
|
127
|
+
ListIterator.new @head, self
|
128
|
+
end
|
129
|
+
|
130
|
+
# an iterator starting from end
|
131
|
+
def end_iterator
|
132
|
+
ListIterator.new @tail, self
|
133
|
+
end
|
134
|
+
|
135
|
+
# the following methods are used to process nodes, user usually does not need to use them
|
136
|
+
# insert node between two nodes
|
137
|
+
def insert_node_between(a, b, new_one)
|
138
|
+
a.next = new_one
|
139
|
+
new_one.prev = a
|
140
|
+
new_one.next = b
|
141
|
+
b.prev = new_one
|
142
|
+
@length += 1
|
143
|
+
end
|
144
|
+
|
145
|
+
# remove a node
|
146
|
+
def remove_node(node)
|
147
|
+
raise( ListRemovalError, 'List is empty' ) if @length == 0
|
148
|
+
before = node.prev
|
149
|
+
after = node.next
|
150
|
+
before.next = after
|
151
|
+
after.prev = before
|
152
|
+
@length -= 1
|
153
|
+
node
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
def get_node(index)
|
158
|
+
index += @length if index < 0
|
159
|
+
raise(ListInsertError, 'Index out of bound: ' + index.to_s) if index > @length - 1 || index < 0
|
160
|
+
if index > @length / 2
|
161
|
+
node = @tail.prev
|
162
|
+
current_index = @length - 1
|
163
|
+
while 1
|
164
|
+
return node if index == current_index
|
165
|
+
node = node.prev
|
166
|
+
current_index -= 1
|
167
|
+
end
|
168
|
+
else
|
169
|
+
node = @head.next
|
170
|
+
current_index = 0
|
171
|
+
while 1
|
172
|
+
return node if index == current_index
|
173
|
+
node = node.next
|
174
|
+
current_index += 1
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module DSA
|
2
|
+
class PriorityQueueNode
|
3
|
+
attr_accessor :priority, :element
|
4
|
+
def initialize( priority, element )
|
5
|
+
@priority = priority
|
6
|
+
@element = element
|
7
|
+
end
|
8
|
+
end
|
9
|
+
# priority queue built on top of array based heap
|
10
|
+
class PriorityQueue
|
11
|
+
def initialize
|
12
|
+
@data = Array.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def length
|
16
|
+
@data.length
|
17
|
+
end
|
18
|
+
|
19
|
+
# get the value of the top without removing it
|
20
|
+
def top
|
21
|
+
return nil if length == 0
|
22
|
+
@data.first.element
|
23
|
+
end
|
24
|
+
|
25
|
+
# priority is a number, the smaller it is, higher it has a priority
|
26
|
+
def add(priority, element)
|
27
|
+
@data.push PriorityQueueNode.new(priority,element)
|
28
|
+
up_heap_bubbling length-1
|
29
|
+
end
|
30
|
+
|
31
|
+
# remove the top and return the element
|
32
|
+
def pop
|
33
|
+
value = top
|
34
|
+
@data[0] = @data.pop # move the rightmost node to the top
|
35
|
+
down_heap_sinking 0
|
36
|
+
value
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def up_heap_bubbling(index)
|
41
|
+
parent_index = (index - 1) / 2
|
42
|
+
return if parent_index < 0
|
43
|
+
if @data[index].priority < @data[parent_index].priority
|
44
|
+
swap index, parent_index
|
45
|
+
up_heap_bubbling parent_index
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def swap(index_a, index_b)
|
50
|
+
@data[index_a], @data[index_b] = @data[index_b], @data[index_a]
|
51
|
+
end
|
52
|
+
|
53
|
+
def down_heap_sinking(index)
|
54
|
+
left_child_index = 2*index + 1
|
55
|
+
right_child_index = 2*index + 2
|
56
|
+
small_child_index = index
|
57
|
+
if left_child_index < length
|
58
|
+
small_child_index = left_child_index if @data[left_child_index].priority < @data[small_child_index].priority
|
59
|
+
end
|
60
|
+
if right_child_index < length
|
61
|
+
small_child_index = right_child_index if @data[right_child_index].priority < @data[small_child_index].priority
|
62
|
+
end
|
63
|
+
if small_child_index != index
|
64
|
+
swap small_child_index, index
|
65
|
+
down_heap_sinking small_child_index
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require_relative 'list'
|
2
|
+
|
3
|
+
module DSA
|
4
|
+
# The stack data structure
|
5
|
+
# Delegate the job to ruby Array, no need to worry about its growing and shrinking.
|
6
|
+
class ArrayStack
|
7
|
+
def initialize
|
8
|
+
@data = Array.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def push(e)
|
12
|
+
@data.push e
|
13
|
+
end
|
14
|
+
|
15
|
+
def pop
|
16
|
+
@data.pop
|
17
|
+
end
|
18
|
+
|
19
|
+
def top
|
20
|
+
@data.last
|
21
|
+
end
|
22
|
+
|
23
|
+
def empty?
|
24
|
+
@data.empty?
|
25
|
+
end
|
26
|
+
|
27
|
+
def length
|
28
|
+
@data.length
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# Stack built on top of list
|
34
|
+
class ListStack
|
35
|
+
def initialize
|
36
|
+
@data = DSA::List.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def push(e)
|
40
|
+
@data.push e
|
41
|
+
end
|
42
|
+
|
43
|
+
def pop
|
44
|
+
@data.pop
|
45
|
+
end
|
46
|
+
|
47
|
+
def top
|
48
|
+
@data.last
|
49
|
+
end
|
50
|
+
|
51
|
+
def empty?
|
52
|
+
@data.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def length
|
56
|
+
@data.length
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
# Typically, array is not a good idea to implement a queue, as it requires linear time to shift other elements after dequeue
|
62
|
+
# We can try to avoid the shifting by keep track of the index of the front queue element and make the array circular
|
63
|
+
class ArrayQueue
|
64
|
+
attr_reader :length
|
65
|
+
|
66
|
+
def initialize(initial_capacity = 10)
|
67
|
+
@data = Array.new initial_capacity
|
68
|
+
@front_index = 0
|
69
|
+
@length = 0
|
70
|
+
end
|
71
|
+
|
72
|
+
def enqueue(e)
|
73
|
+
grow if @length+1 == capacity
|
74
|
+
@length += 1
|
75
|
+
index = (@front_index + @length - 1 ) % capacity
|
76
|
+
@data[index] = e
|
77
|
+
end
|
78
|
+
|
79
|
+
def dequeue
|
80
|
+
shrink if @length - 1 < capacity / 2 and capacity / 2 >= 10
|
81
|
+
return nil if @length == 0
|
82
|
+
next_front = (@front_index + 1 ) % capacity
|
83
|
+
value = @data[@front_index]
|
84
|
+
@data[@front_index] = nil
|
85
|
+
@front_index = next_front
|
86
|
+
@length -= 1
|
87
|
+
value
|
88
|
+
end
|
89
|
+
|
90
|
+
def first
|
91
|
+
@data[@front_index]
|
92
|
+
end
|
93
|
+
|
94
|
+
def empty?
|
95
|
+
@length == 0
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def capacity
|
100
|
+
@data.length
|
101
|
+
end
|
102
|
+
|
103
|
+
def grow
|
104
|
+
new_data = Array.new capacity * 2
|
105
|
+
copy_to new_data
|
106
|
+
end
|
107
|
+
|
108
|
+
def shrink
|
109
|
+
new_data = Array.new capacity / 2
|
110
|
+
copy_to new_data
|
111
|
+
end
|
112
|
+
|
113
|
+
def copy_to(new_data)
|
114
|
+
(0..@length-1).each do |i|
|
115
|
+
old_index = (@front_index + i) % capacity
|
116
|
+
new_data[i] = @data[old_index]
|
117
|
+
end
|
118
|
+
@data = new_data
|
119
|
+
@front_index = 0
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
# FIFO queue built on top of linked list
|
125
|
+
class ListQueue
|
126
|
+
def initialize
|
127
|
+
@data = DSA::List.new
|
128
|
+
end
|
129
|
+
|
130
|
+
def enqueue(e)
|
131
|
+
@data.push e
|
132
|
+
end
|
133
|
+
|
134
|
+
def dequeue
|
135
|
+
@data.shift
|
136
|
+
end
|
137
|
+
|
138
|
+
def first
|
139
|
+
@data.first
|
140
|
+
end
|
141
|
+
|
142
|
+
def empty?
|
143
|
+
@data.empty?
|
144
|
+
end
|
145
|
+
|
146
|
+
def length
|
147
|
+
@data.length
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
data/lib/DSA/version.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'benchmark'
|
3
|
+
require 'DSA'
|
4
|
+
|
5
|
+
class MyTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
# Called before every test method runs. Can be used
|
8
|
+
# to set up fixture information.
|
9
|
+
def setup
|
10
|
+
# Do nothing
|
11
|
+
end
|
12
|
+
|
13
|
+
# Called after every test method runs. Can be used to tear
|
14
|
+
# down fixture information.
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
# Do nothing
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_function
|
21
|
+
assert_equal 120, DSA::Algorithm::factorial(5), 'factorial function is wrong'
|
22
|
+
assert DSA::Algorithm::binary_search((1..9).to_a, 2, 0, 8), 'binary search failed'
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_performance
|
26
|
+
puts
|
27
|
+
|
28
|
+
value = 10**7
|
29
|
+
target = value/3
|
30
|
+
data = (0..value).to_a
|
31
|
+
puts 'testing performance'
|
32
|
+
Benchmark.bm(20) do |x|
|
33
|
+
x.report('binary search') { DSA::Algorithm::binary_search(data, target, 0, value-1) }
|
34
|
+
x.report('linear search') { data.each do |a| break if a == target end }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def test_sort
|
40
|
+
original = [3,6,7,8,2,4,7,8,1,5,9,6]
|
41
|
+
expect = [1,2,3,4,5,6,6,7,7,8,8,9]
|
42
|
+
DSA::Algorithm::insertion_sort!(original)
|
43
|
+
assert_equal expect, original, 'sort failed'
|
44
|
+
|
45
|
+
original = [3,6,7,8,2,4,7,8,1,5,9,6]
|
46
|
+
expect = [1,2,3,4,5,6,6,7,7,8,8,9]
|
47
|
+
DSA::Algorithm::quick_sort!(original, 0, original.length-1)
|
48
|
+
assert_equal expect, original, 'sort failed'
|
49
|
+
|
50
|
+
puts
|
51
|
+
puts 'insertion sort vs built in'
|
52
|
+
value = 10**3
|
53
|
+
sorted_a = (0..value).to_a
|
54
|
+
sorted_b = (0..value).to_a
|
55
|
+
sorted_c = (0..value).to_a
|
56
|
+
|
57
|
+
|
58
|
+
puts 'sort on already sorted'
|
59
|
+
Benchmark.bm(20) do |x|
|
60
|
+
x.report('insertion sort') { DSA::Algorithm::insertion_sort! sorted_a }
|
61
|
+
x.report('quick sort') { DSA::Algorithm::quick_sort! sorted_c, 0, sorted_c.length-1 }
|
62
|
+
x.report('built in sort') { sorted_b.sort! }
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
puts 'sort on not sorted'
|
67
|
+
value = 10**4
|
68
|
+
not_sorted_a = value.times.map { Random.rand(value) }
|
69
|
+
not_sorted_b = value.times.map { Random.rand(value) }
|
70
|
+
not_sorted_c = value.times.map { Random.rand(value) }
|
71
|
+
Benchmark.bm(20) do |x|
|
72
|
+
x.report('insertion sort') { DSA::Algorithm::insertion_sort! not_sorted_a }
|
73
|
+
x.report('quick sort') { DSA::Algorithm::quick_sort! not_sorted_c, 0, not_sorted_c.length-1 }
|
74
|
+
x.report('built in sort') { not_sorted_b.sort! }
|
75
|
+
end
|
76
|
+
|
77
|
+
puts 'sort on not sorted'
|
78
|
+
value = 10**6
|
79
|
+
not_sorted_b = value.times.map { Random.rand(value) }
|
80
|
+
not_sorted_c = value.times.map { Random.rand(value) }
|
81
|
+
Benchmark.bm(20) do |x|
|
82
|
+
x.report('quick sort') { DSA::Algorithm::quick_sort! not_sorted_c, 0, not_sorted_c.length-1 }
|
83
|
+
x.report('built in sort') { not_sorted_b.sort! }
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
end
|