algorithms 0.3.0-jruby
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +172 -0
- data/Manifest +43 -0
- data/README.markdown +93 -0
- data/Rakefile +31 -0
- data/algorithms.gemspec +33 -0
- data/benchmarks/deque.rb +17 -0
- data/benchmarks/sorts.rb +34 -0
- data/benchmarks/treemaps.rb +51 -0
- data/ext/containers/deque/deque.c +247 -0
- data/ext/containers/deque/extconf.rb +4 -0
- data/ext/containers/rbtree_map/extconf.rb +4 -0
- data/ext/containers/rbtree_map/rbtree.c +498 -0
- data/ext/containers/splaytree_map/extconf.rb +4 -0
- data/ext/containers/splaytree_map/splaytree.c +419 -0
- data/lib/algorithms.rb +68 -0
- data/lib/algorithms/search.rb +84 -0
- data/lib/algorithms/sort.rb +238 -0
- data/lib/containers/deque.rb +171 -0
- data/lib/containers/heap.rb +486 -0
- data/lib/containers/kd_tree.rb +110 -0
- data/lib/containers/priority_queue.rb +113 -0
- data/lib/containers/queue.rb +68 -0
- data/lib/containers/rb_tree_map.rb +398 -0
- data/lib/containers/splay_tree_map.rb +269 -0
- data/lib/containers/stack.rb +67 -0
- data/lib/containers/suffix_array.rb +68 -0
- data/lib/containers/trie.rb +182 -0
- data/spec/deque_gc_mark_spec.rb +18 -0
- data/spec/deque_spec.rb +108 -0
- data/spec/heap_spec.rb +126 -0
- data/spec/kd_expected_out.txt +10000 -0
- data/spec/kd_test_in.txt +10000 -0
- data/spec/kd_tree_spec.rb +34 -0
- data/spec/map_gc_mark_spec.rb +27 -0
- data/spec/priority_queue_spec.rb +75 -0
- data/spec/queue_spec.rb +61 -0
- data/spec/rb_tree_map_spec.rb +123 -0
- data/spec/search_spec.rb +28 -0
- data/spec/sort_spec.rb +28 -0
- data/spec/splay_tree_map_spec.rb +106 -0
- data/spec/stack_spec.rb +60 -0
- data/spec/suffix_array_spec.rb +40 -0
- data/spec/trie_spec.rb +59 -0
- metadata +122 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
This module implements search algorithms. Documentation is provided for each algorithm.
|
3
|
+
|
4
|
+
=end
|
5
|
+
module Algorithms::Search
|
6
|
+
# Binary Search: This search finds an item in log(n) time provided that the container is already sorted.
|
7
|
+
# The method returns the item if it is found, or nil if it is not. If there are duplicates, the first one
|
8
|
+
# found is returned, and this is not guaranteed to be the smallest or largest item.
|
9
|
+
#
|
10
|
+
# Complexity: O(lg N)
|
11
|
+
#
|
12
|
+
# Algorithms::Search.binary_search([1, 2, 3], 1) #=> 1
|
13
|
+
# Algorithms::Search.binary_search([1, 2, 3], 4) #=> nil
|
14
|
+
def self.binary_search(container, item)
|
15
|
+
return nil if item.nil?
|
16
|
+
low = 0
|
17
|
+
high = container.size - 1
|
18
|
+
while low <= high
|
19
|
+
mid = (low + high) / 2
|
20
|
+
val = container[mid]
|
21
|
+
if val > item
|
22
|
+
high = mid - 1
|
23
|
+
elsif val < item
|
24
|
+
low = mid + 1
|
25
|
+
else
|
26
|
+
return val
|
27
|
+
end
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Knuth-Morris-Pratt Algorithm substring search algorithm: Efficiently finds the starting position of a
|
33
|
+
# substring in a string. The algorithm calculates the best position to resume searching from if a failure
|
34
|
+
# occurs.
|
35
|
+
#
|
36
|
+
# The method returns the index of the starting position in the string where the substring is found. If there
|
37
|
+
# is no match, nil is returned.
|
38
|
+
#
|
39
|
+
# Complexity: O(n + k), where n is the length of the string and k is the length of the substring.
|
40
|
+
#
|
41
|
+
# Algorithms::Search.kmp_search("ABC ABCDAB ABCDABCDABDE", "ABCDABD") #=> 15
|
42
|
+
# Algorithms::Search.kmp_search("ABC ABCDAB ABCDABCDABDE", "ABCDEF") #=> nil
|
43
|
+
def self.kmp_search(string, substring)
|
44
|
+
return nil if string.nil? or substring.nil?
|
45
|
+
|
46
|
+
# create failure function table
|
47
|
+
pos = 2
|
48
|
+
cnd = 0
|
49
|
+
failure_table = [-1, 0]
|
50
|
+
while pos < substring.length
|
51
|
+
if substring[pos - 1] == substring[cnd]
|
52
|
+
failure_table[pos] = cnd + 1
|
53
|
+
pos += 1
|
54
|
+
cnd += 1
|
55
|
+
elsif cnd > 0
|
56
|
+
cnd = failure_table[cnd]
|
57
|
+
else
|
58
|
+
failure_table[pos] = 0
|
59
|
+
pos += 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
m = i = 0
|
64
|
+
while m + i < string.length
|
65
|
+
if substring[i] == string[m + i]
|
66
|
+
i += 1
|
67
|
+
return m if i == substring.length
|
68
|
+
else
|
69
|
+
m = m + i - failure_table[i]
|
70
|
+
i = failure_table[i] if i > 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Allows kmp_search to be called as an instance method in classes that include the Search module.
|
77
|
+
#
|
78
|
+
# class String; include Algorithms::Search; end
|
79
|
+
# "ABC ABCDAB ABCDABCDABDE".kmp_search("ABCDABD") #=> 15
|
80
|
+
def kmp_search(substring)
|
81
|
+
Algorithms::Search.kmp_search(self, substring)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'containers/heap' # for heapsort
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
This module implements sorting algorithms. Documentation is provided for each algorithm.
|
5
|
+
|
6
|
+
=end
|
7
|
+
module Algorithms::Sort
|
8
|
+
# Bubble sort: A very naive sort that keeps swapping elements until the container is sorted.
|
9
|
+
# Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should
|
10
|
+
# be implemented for the container.
|
11
|
+
# Time Complexity: О(n^2)
|
12
|
+
# Space Complexity: О(n) total, O(1) auxiliary
|
13
|
+
# Stable: Yes
|
14
|
+
#
|
15
|
+
# Algorithms::Sort.bubble_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
16
|
+
def self.bubble_sort(container)
|
17
|
+
loop do
|
18
|
+
swapped = false
|
19
|
+
(container.size-1).times do |i|
|
20
|
+
if (container[i] <=> container[i+1]) == 1
|
21
|
+
container[i], container[i+1] = container[i+1], container[i] # Swap
|
22
|
+
swapped = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
break unless swapped
|
26
|
+
end
|
27
|
+
container
|
28
|
+
end
|
29
|
+
|
30
|
+
# Comb sort: A variation on bubble sort that dramatically improves performance.
|
31
|
+
# Source: http://yagni.com/combsort/
|
32
|
+
# Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should
|
33
|
+
# be implemented for the container.
|
34
|
+
# Time Complexity: О(n^2)
|
35
|
+
# Space Complexity: О(n) total, O(1) auxiliary
|
36
|
+
# Stable: Yes
|
37
|
+
#
|
38
|
+
# Algorithms::Sort.comb_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
39
|
+
def self.comb_sort(container)
|
40
|
+
container
|
41
|
+
gap = container.size
|
42
|
+
loop do
|
43
|
+
gap = gap * 10/13
|
44
|
+
gap = 11 if gap == 9 || gap == 10
|
45
|
+
gap = 1 if gap < 1
|
46
|
+
swapped = false
|
47
|
+
(container.size - gap).times do |i|
|
48
|
+
if (container[i] <=> container[i + gap]) == 1
|
49
|
+
container[i], container[i+gap] = container[i+gap], container[i] # Swap
|
50
|
+
swapped = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
break if !swapped && gap == 1
|
54
|
+
end
|
55
|
+
container
|
56
|
+
end
|
57
|
+
|
58
|
+
# Selection sort: A naive sort that goes through the container and selects the smallest element,
|
59
|
+
# putting it at the beginning. Repeat until the end is reached.
|
60
|
+
# Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should
|
61
|
+
# be implemented for the container.
|
62
|
+
# Time Complexity: О(n^2)
|
63
|
+
# Space Complexity: О(n) total, O(1) auxiliary
|
64
|
+
# Stable: Yes
|
65
|
+
#
|
66
|
+
# Algorithms::Sort.selection_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
67
|
+
def self.selection_sort(container)
|
68
|
+
0.upto(container.size-1) do |i|
|
69
|
+
min = i
|
70
|
+
(i+1).upto(container.size-1) do |j|
|
71
|
+
min = j if (container[j] <=> container[min]) == -1
|
72
|
+
end
|
73
|
+
container[i], container[min] = container[min], container[i] # Swap
|
74
|
+
end
|
75
|
+
container
|
76
|
+
end
|
77
|
+
|
78
|
+
# Heap sort: Uses a heap (implemented by the Containers module) to sort the collection.
|
79
|
+
# Requirements: Needs to be able to compare elements with <=>
|
80
|
+
# Time Complexity: О(n^2)
|
81
|
+
# Space Complexity: О(n) total, O(1) auxiliary
|
82
|
+
# Stable: Yes
|
83
|
+
#
|
84
|
+
# Algorithms::Sort.heapsort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
85
|
+
def self.heapsort(container)
|
86
|
+
heap = Containers::Heap.new(container)
|
87
|
+
ary = []
|
88
|
+
ary << heap.pop until heap.empty?
|
89
|
+
ary
|
90
|
+
end
|
91
|
+
|
92
|
+
# Insertion sort: Elements are inserted sequentially into the right position.
|
93
|
+
# Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should
|
94
|
+
# be implemented for the container.
|
95
|
+
# Time Complexity: О(n^2)
|
96
|
+
# Space Complexity: О(n) total, O(1) auxiliary
|
97
|
+
# Stable: Yes
|
98
|
+
#
|
99
|
+
# Algorithms::Sort.insertion_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
100
|
+
def self.insertion_sort(container)
|
101
|
+
return container if container.size < 2
|
102
|
+
(1..container.size-1).each do |i|
|
103
|
+
value = container[i]
|
104
|
+
j = i-1
|
105
|
+
while j >= 0 and container[j] > value do
|
106
|
+
container[j+1] = container[j]
|
107
|
+
j = j-1
|
108
|
+
end
|
109
|
+
container[j+1] = value
|
110
|
+
end
|
111
|
+
container
|
112
|
+
end
|
113
|
+
|
114
|
+
# Shell sort: Similar approach as insertion sort but slightly better.
|
115
|
+
# Requirements: Needs to be able to compare elements with <=>, and the [] []= methods should
|
116
|
+
# be implemented for the container.
|
117
|
+
# Time Complexity: О(n^2)
|
118
|
+
# Space Complexity: О(n) total, O(1) auxiliary
|
119
|
+
# Stable: Yes
|
120
|
+
#
|
121
|
+
# Algorithms::Sort.shell_sort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
122
|
+
def self.shell_sort(container)
|
123
|
+
increment = container.size/2
|
124
|
+
while increment > 0 do
|
125
|
+
(increment..container.size-1).each do |i|
|
126
|
+
temp = container[i]
|
127
|
+
j = i
|
128
|
+
while j >= increment && container[j - increment] > temp do
|
129
|
+
container[j] = container[j-increment]
|
130
|
+
j -= increment
|
131
|
+
end
|
132
|
+
container[j] = temp
|
133
|
+
end
|
134
|
+
increment = (increment == 2 ? 1 : (increment / 2.2).round)
|
135
|
+
end
|
136
|
+
container
|
137
|
+
end
|
138
|
+
|
139
|
+
# Quicksort: A divide-and-conquer sort that recursively partitions a container until it is sorted.
|
140
|
+
# Requirements: Container should implement #pop and include the Enumerable module.
|
141
|
+
# Time Complexity: О(n log n) average, O(n^2) worst-case
|
142
|
+
# Space Complexity: О(n) auxiliary
|
143
|
+
# Stable: No
|
144
|
+
#
|
145
|
+
# Algorithms::Sort.quicksort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
146
|
+
# def self.quicksort(container)
|
147
|
+
# return [] if container.empty?
|
148
|
+
#
|
149
|
+
# x, *xs = container
|
150
|
+
#
|
151
|
+
# quicksort(xs.select { |i| i < x }) + [x] + quicksort(xs.select { |i| i >= x })
|
152
|
+
# end
|
153
|
+
|
154
|
+
def self.partition(data, left, right)
|
155
|
+
pivot = data[front]
|
156
|
+
left += 1
|
157
|
+
|
158
|
+
while left <= right do
|
159
|
+
if data[frontUnknown] < pivot
|
160
|
+
back += 1
|
161
|
+
data[frontUnknown], data[back] = data[back], data[frontUnknown] # Swap
|
162
|
+
end
|
163
|
+
|
164
|
+
frontUnknown += 1
|
165
|
+
end
|
166
|
+
|
167
|
+
data[front], data[back] = data[back], data[front] # Swap
|
168
|
+
back
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# def self.quicksort(container, left = 0, right = container.size - 1)
|
173
|
+
# if left < right
|
174
|
+
# middle = partition(container, left, right)
|
175
|
+
# quicksort(container, left, middle - 1)
|
176
|
+
# quicksort(container, middle + 1, right)
|
177
|
+
# end
|
178
|
+
# end
|
179
|
+
|
180
|
+
def self.quicksort(container)
|
181
|
+
bottom, top = [], []
|
182
|
+
top[0] = 0
|
183
|
+
bottom[0] = container.size
|
184
|
+
i = 0
|
185
|
+
while i >= 0 do
|
186
|
+
l = top[i]
|
187
|
+
r = bottom[i] - 1;
|
188
|
+
if l < r
|
189
|
+
pivot = container[l]
|
190
|
+
while l < r do
|
191
|
+
r -= 1 while (container[r] >= pivot && l < r)
|
192
|
+
if (l < r)
|
193
|
+
container[l] = container[r]
|
194
|
+
l += 1
|
195
|
+
end
|
196
|
+
l += 1 while (container[l] <= pivot && l < r)
|
197
|
+
if (l < r)
|
198
|
+
container[r] = container[l]
|
199
|
+
r -= 1
|
200
|
+
end
|
201
|
+
end
|
202
|
+
container[l] = pivot
|
203
|
+
top[i+1] = l + 1
|
204
|
+
bottom[i+1] = bottom[i]
|
205
|
+
bottom[i] = l
|
206
|
+
i += 1
|
207
|
+
else
|
208
|
+
i -= 1
|
209
|
+
end
|
210
|
+
end
|
211
|
+
container
|
212
|
+
end
|
213
|
+
|
214
|
+
# Mergesort: A stable divide-and-conquer sort that sorts small chunks of the container and then merges them together.
|
215
|
+
# Returns an array of the sorted elements.
|
216
|
+
# Requirements: Container should implement []
|
217
|
+
# Time Complexity: О(n log n) average and worst-case
|
218
|
+
# Space Complexity: О(n) auxiliary
|
219
|
+
# Stable: Yes
|
220
|
+
#
|
221
|
+
# Algorithms::Sort.mergesort [5, 4, 3, 1, 2] => [1, 2, 3, 4, 5]
|
222
|
+
def self.mergesort(container)
|
223
|
+
return container if container.size <= 1
|
224
|
+
mid = container.size / 2
|
225
|
+
left = container[0...mid]
|
226
|
+
right = container[mid...container.size]
|
227
|
+
merge(mergesort(left), mergesort(right))
|
228
|
+
end
|
229
|
+
|
230
|
+
def self.merge(left, right)
|
231
|
+
sorted = []
|
232
|
+
until left.empty? or right.empty?
|
233
|
+
left.first <= right.first ? sorted << left.shift : sorted << right.shift
|
234
|
+
end
|
235
|
+
sorted + left + right
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
=begin rdoc
|
2
|
+
A Deque is a container that allows items to be added and removed from both the front and back,
|
3
|
+
acting as a combination of a Stack and Queue.
|
4
|
+
|
5
|
+
This implementation uses a doubly-linked list, guaranteeing O(1) complexity for all operations.
|
6
|
+
=end
|
7
|
+
class Containers::RubyDeque
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
Node = Struct.new(:left, :right, :obj)
|
11
|
+
|
12
|
+
# Create a new Deque. Takes an optional array argument to initialize the Deque.
|
13
|
+
#
|
14
|
+
# d = Containers::Deque.new([1, 2, 3])
|
15
|
+
# d.front #=> 1
|
16
|
+
# d.back #=> 3
|
17
|
+
def initialize(ary=[])
|
18
|
+
@front = nil
|
19
|
+
@back = nil
|
20
|
+
@size = 0
|
21
|
+
ary.to_a.each { |obj| push_back(obj) }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if the Deque is empty, false otherwise.
|
25
|
+
def empty?
|
26
|
+
@size == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# Removes all the objects in the Deque.
|
30
|
+
def clear
|
31
|
+
@front = @back = nil
|
32
|
+
@size = 0
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the number of items in the Deque.
|
36
|
+
#
|
37
|
+
# d = Containers::Deque.new([1, 2, 3])
|
38
|
+
# d.size #=> 3
|
39
|
+
def size
|
40
|
+
@size
|
41
|
+
end
|
42
|
+
alias_method :length, :size
|
43
|
+
|
44
|
+
# Returns the object at the front of the Deque but does not remove it.
|
45
|
+
#
|
46
|
+
# d = Containers::Deque.new
|
47
|
+
# d.push_front(1)
|
48
|
+
# d.push_front(2)
|
49
|
+
# d.front #=> 2
|
50
|
+
def front
|
51
|
+
@front && @front.obj
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the object at the back of the Deque but does not remove it.
|
55
|
+
#
|
56
|
+
# d = Containers::Deque.new
|
57
|
+
# d.push_front(1)
|
58
|
+
# d.push_front(2)
|
59
|
+
# d.back #=> 1
|
60
|
+
def back
|
61
|
+
@back && @back.obj
|
62
|
+
end
|
63
|
+
|
64
|
+
# Adds an object at the front of the Deque.
|
65
|
+
#
|
66
|
+
# d = Containers::Deque.new([1, 2, 3])
|
67
|
+
# d.push_front(0)
|
68
|
+
# d.pop_front #=> 0
|
69
|
+
def push_front(obj)
|
70
|
+
node = Node.new(nil, nil, obj)
|
71
|
+
if @front
|
72
|
+
node.right = @front
|
73
|
+
@front.left = node
|
74
|
+
@front = node
|
75
|
+
else
|
76
|
+
@front = @back = node
|
77
|
+
end
|
78
|
+
@size += 1
|
79
|
+
obj
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds an object at the back of the Deque.
|
83
|
+
#
|
84
|
+
# d = Containers::Deque.new([1, 2, 3])
|
85
|
+
# d.push_back(4)
|
86
|
+
# d.pop_back #=> 4
|
87
|
+
def push_back(obj)
|
88
|
+
node = Node.new(nil, nil, obj)
|
89
|
+
if @back
|
90
|
+
node.left = @back
|
91
|
+
@back.right = node
|
92
|
+
@back = node
|
93
|
+
else
|
94
|
+
@front = @back = node
|
95
|
+
end
|
96
|
+
@size += 1
|
97
|
+
obj
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the object at the front of the Deque and removes it.
|
101
|
+
#
|
102
|
+
# d = Containers::Deque.new
|
103
|
+
# d.push_front(1)
|
104
|
+
# d.push_front(2)
|
105
|
+
# d.pop_front #=> 2
|
106
|
+
# d.size #=> 1
|
107
|
+
def pop_front
|
108
|
+
return nil unless @front
|
109
|
+
node = @front
|
110
|
+
if @size == 1
|
111
|
+
clear
|
112
|
+
return node.obj
|
113
|
+
else
|
114
|
+
@front.right.left = nil
|
115
|
+
@front = @front.right
|
116
|
+
end
|
117
|
+
@size -= 1
|
118
|
+
node.obj
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns the object at the back of the Deque and removes it.
|
122
|
+
#
|
123
|
+
# d = Containers::Deque.new
|
124
|
+
# d.push_front(1)
|
125
|
+
# d.push_front(2)
|
126
|
+
# d.pop_back #=> 1
|
127
|
+
# d.size #=> 1
|
128
|
+
def pop_back
|
129
|
+
return nil unless @back
|
130
|
+
node = @back
|
131
|
+
if @size == 1
|
132
|
+
clear
|
133
|
+
return node.obj
|
134
|
+
else
|
135
|
+
@back.left.right = nil
|
136
|
+
@back = @back.left
|
137
|
+
end
|
138
|
+
@size -= 1
|
139
|
+
node.obj
|
140
|
+
end
|
141
|
+
|
142
|
+
# Iterate over the Deque in FIFO order.
|
143
|
+
def each_forward
|
144
|
+
return unless @front
|
145
|
+
node = @front
|
146
|
+
while node
|
147
|
+
yield node.obj
|
148
|
+
node = node.right
|
149
|
+
end
|
150
|
+
end
|
151
|
+
alias_method :each, :each_forward
|
152
|
+
|
153
|
+
# Iterate over the Deque in LIFO order.
|
154
|
+
def each_backward
|
155
|
+
return unless @back
|
156
|
+
node = @back
|
157
|
+
while node
|
158
|
+
yield node.obj
|
159
|
+
node = node.left
|
160
|
+
end
|
161
|
+
end
|
162
|
+
alias_method :reverse_each, :each_backward
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
begin
|
167
|
+
require 'CDeque'
|
168
|
+
Containers::Deque = Containers::CDeque
|
169
|
+
rescue LoadError # C Version could not be found, try ruby version
|
170
|
+
Containers::Deque = Containers::RubyDeque
|
171
|
+
end
|