algorithms 0.3.0-jruby

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/History.txt +172 -0
  2. data/Manifest +43 -0
  3. data/README.markdown +93 -0
  4. data/Rakefile +31 -0
  5. data/algorithms.gemspec +33 -0
  6. data/benchmarks/deque.rb +17 -0
  7. data/benchmarks/sorts.rb +34 -0
  8. data/benchmarks/treemaps.rb +51 -0
  9. data/ext/containers/deque/deque.c +247 -0
  10. data/ext/containers/deque/extconf.rb +4 -0
  11. data/ext/containers/rbtree_map/extconf.rb +4 -0
  12. data/ext/containers/rbtree_map/rbtree.c +498 -0
  13. data/ext/containers/splaytree_map/extconf.rb +4 -0
  14. data/ext/containers/splaytree_map/splaytree.c +419 -0
  15. data/lib/algorithms.rb +68 -0
  16. data/lib/algorithms/search.rb +84 -0
  17. data/lib/algorithms/sort.rb +238 -0
  18. data/lib/containers/deque.rb +171 -0
  19. data/lib/containers/heap.rb +486 -0
  20. data/lib/containers/kd_tree.rb +110 -0
  21. data/lib/containers/priority_queue.rb +113 -0
  22. data/lib/containers/queue.rb +68 -0
  23. data/lib/containers/rb_tree_map.rb +398 -0
  24. data/lib/containers/splay_tree_map.rb +269 -0
  25. data/lib/containers/stack.rb +67 -0
  26. data/lib/containers/suffix_array.rb +68 -0
  27. data/lib/containers/trie.rb +182 -0
  28. data/spec/deque_gc_mark_spec.rb +18 -0
  29. data/spec/deque_spec.rb +108 -0
  30. data/spec/heap_spec.rb +126 -0
  31. data/spec/kd_expected_out.txt +10000 -0
  32. data/spec/kd_test_in.txt +10000 -0
  33. data/spec/kd_tree_spec.rb +34 -0
  34. data/spec/map_gc_mark_spec.rb +27 -0
  35. data/spec/priority_queue_spec.rb +75 -0
  36. data/spec/queue_spec.rb +61 -0
  37. data/spec/rb_tree_map_spec.rb +123 -0
  38. data/spec/search_spec.rb +28 -0
  39. data/spec/sort_spec.rb +28 -0
  40. data/spec/splay_tree_map_spec.rb +106 -0
  41. data/spec/stack_spec.rb +60 -0
  42. data/spec/suffix_array_spec.rb +40 -0
  43. data/spec/trie_spec.rb +59 -0
  44. 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