algorithms 0.0.1 → 0.1.0

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.
Files changed (54) hide show
  1. data/History.txt +139 -2
  2. data/Manifest +31 -8
  3. data/README +90 -0
  4. data/Rakefile +22 -9
  5. data/algorithms.gemspec +28 -101
  6. data/benchmark.rb +29 -27
  7. data/benchmarks/rbench.rb +16 -0
  8. data/benchmarks/rbench/column.rb +26 -0
  9. data/benchmarks/rbench/group.rb +43 -0
  10. data/benchmarks/rbench/report.rb +53 -0
  11. data/benchmarks/rbench/runner.rb +109 -0
  12. data/benchmarks/rbench/summary.rb +51 -0
  13. data/benchmarks/sorts.rb +33 -0
  14. data/ext/containers/bst/bst.c +205 -0
  15. data/ext/containers/{priority_queue → bst}/extconf.rb +1 -1
  16. data/ext/containers/deque/deque.c +233 -0
  17. data/ext/containers/deque/extconf.rb +4 -0
  18. data/ext/containers/tree_map/extconf.rb +1 -1
  19. data/ext/containers/tree_map/rbtree.c +73 -25
  20. data/lib/algorithms.rb +65 -6
  21. data/lib/algorithms/search.rb +84 -0
  22. data/lib/algorithms/sort.rb +238 -0
  23. data/lib/containers/deque.rb +176 -0
  24. data/lib/containers/heap.rb +451 -111
  25. data/lib/containers/kd_tree.rb +87 -0
  26. data/lib/containers/priority_queue.rb +107 -508
  27. data/lib/containers/queue.rb +62 -23
  28. data/lib/containers/rb_tree_map.rb +398 -0
  29. data/lib/containers/splay_tree_map.rb +274 -0
  30. data/lib/containers/stack.rb +59 -21
  31. data/lib/containers/suffix_array.rb +68 -0
  32. data/lib/containers/trie.rb +182 -0
  33. data/lib/graphs/graph.rb +25 -0
  34. data/spec/bst_spec.rb +31 -0
  35. data/spec/deque_spec.rb +108 -0
  36. data/spec/heap_spec.rb +111 -66
  37. data/spec/kd_tree_spec.rb +89 -0
  38. data/spec/priority_queue_spec.rb +71 -27
  39. data/spec/queue_spec.rb +53 -45
  40. data/spec/rb_tree_map_spec.rb +123 -0
  41. data/spec/search_spec.rb +28 -0
  42. data/spec/sort_spec.rb +28 -0
  43. data/spec/splay_tree_map_spec.rb +102 -0
  44. data/spec/stack_spec.rb +56 -49
  45. data/spec/suffix_array_spec.rb +40 -0
  46. data/spec/trie_spec.rb +59 -0
  47. metadata +54 -32
  48. data/README.txt +0 -58
  49. data/ext/containers/priority_queue/priority_queue.c +0 -948
  50. data/ext/containers/tree_map/Rakefile +0 -4
  51. data/lib/containers/hash.rb +0 -0
  52. data/lib/containers/tree_map.rb +0 -265
  53. data/spec/priority_queue_test.rb +0 -371
  54. data/spec/tree_map_spec.rb +0 -99
@@ -1,7 +1,66 @@
1
- %w(heap stack queue priority_queue tree_map).each { |file| require "#{File.dirname(__FILE__)}/containers/#{file}" }
2
- begin
3
- require 'CTreeMap'
4
- Containers::TreeMap = Containers::CTreeMap
5
- rescue LoadError # C Version could not be found, try ruby version
6
- Containers::TreeMap = Containers::RubyTreeMap
1
+ =begin rdoc
2
+ The 'Algorithms and Containers' library is an effort to provide a set of commonly used
3
+ algorithms and containers to Ruby programmers.
4
+
5
+ This is a Google Summer of Code 2008 project
6
+
7
+ Written by Kanwei Li, mentored by Austin Ziegler
8
+
9
+ To avoid typing Containers::xxx to initialize containers, include the Containers module.
10
+
11
+ require 'algorithms'
12
+ include Containers
13
+
14
+ tree = RBTreeMap.new
15
+
16
+ instead of:
17
+
18
+ require 'algorithms'
19
+
20
+ tree = Containers::RBTreeMap.new
21
+
22
+ Done so far:
23
+ * Heaps - Containers::Heap, Containers::MaxHeap, Containers::MinHeap
24
+ * Priority Queue - Containers::PriorityQueue
25
+ * Stack - Containers::Stack
26
+ * Queue - Containers::Queue
27
+ * Deque - Containers::Deque, Containers::CDeque (C extension), Containers::RubyDeque
28
+ * Red-Black Trees - Containers::RBTreeMap, Containers::CRBTreeMap (C extension), Containers::RubyRBTreeMap
29
+ * Splay Trees - Containers::SplayTreeMap
30
+ * Tries - Containers::Trie
31
+ * Suffix Array - Containers::SuffixArray
32
+
33
+ * Search algorithms
34
+ - Binary Search - Algorithms::Search.binary_search
35
+ - Knuth-Morris-Pratt - Algorithms::Search.kmp_search
36
+ * Sort algorithms
37
+ - Bubble sort - Algorithms::Sort.bubble_sort
38
+ - Comb sort - Algorithms::Sort.comb_sort
39
+ - Selection sort - Algorithms::Sort.selection_sort
40
+ - Heapsort - Algorithms::Sort.heapsort
41
+ - Insertion sort - Algorithms::Sort.insertion_sort
42
+ - Shell sort - Algorithms::Sort.shell_sort
43
+ - Quicksort - Algorithms::Sort.quicksort
44
+ - Mergesort - Algorithms::Sort.mergesort
45
+ =end
46
+
47
+ module Algorithms; end
48
+ module Containers
49
+ dir = File.dirname(__FILE__)
50
+ autoload :CBst, File.join(dir,"..","ext","containers","bst","CBst")
51
+ autoload :CDeque, File.join(dir,"..","ext","containers","deque","CDeque")
52
+ autoload :CRBTreeMap, File.join(dir,"..","ext","containers","tree_map","CRBTreeMap")
7
53
  end
54
+
55
+ require 'algorithms/search'
56
+ require 'algorithms/sort'
57
+ require 'containers/heap'
58
+ require 'containers/stack'
59
+ require 'containers/deque'
60
+ require 'containers/queue'
61
+ require 'containers/priority_queue'
62
+ require 'containers/rb_tree_map'
63
+ require 'containers/splay_tree_map'
64
+ require 'containers/suffix_array'
65
+ require 'containers/trie'
66
+ require 'containers/kd_tree'
@@ -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,176 @@
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
+ # Create a new Deque. Takes an optional array argument to initialize the Deque.
10
+ #
11
+ # d = Containers::Deque.new([1, 2, 3])
12
+ # d.front #=> 1
13
+ # d.back #=> 3
14
+ def initialize(ary=[])
15
+ @front = nil
16
+ @back = nil
17
+ @size = 0
18
+ ary.to_a.each { |obj| push_back(obj) }
19
+ end
20
+
21
+ # Returns true if the Deque is empty, false otherwise.
22
+ def empty?
23
+ @size == 0
24
+ end
25
+
26
+ # Removes all the objects in the Deque.
27
+ def clear
28
+ @front = @back = nil
29
+ @size = 0
30
+ end
31
+
32
+ # Return the number of items in the Deque.
33
+ #
34
+ # d = Containers::Deque.new([1, 2, 3])
35
+ # d.size #=> 3
36
+ def size
37
+ @size
38
+ end
39
+ alias_method :length, :size
40
+
41
+ # Returns the object at the front of the Deque but does not remove it.
42
+ #
43
+ # d = Containers::Deque.new
44
+ # d.push_front(1)
45
+ # d.push_front(2)
46
+ # d.front #=> 2
47
+ def front
48
+ @front && @front.obj
49
+ end
50
+
51
+ # Returns the object at the back of the Deque but does not remove it.
52
+ #
53
+ # d = Containers::Deque.new
54
+ # d.push_front(1)
55
+ # d.push_front(2)
56
+ # d.back #=> 1
57
+ def back
58
+ @back && @back.obj
59
+ end
60
+
61
+ # Adds an object at the front of the Deque.
62
+ #
63
+ # d = Containers::Deque.new([1, 2, 3])
64
+ # d.push_front(0)
65
+ # d.pop_front #=> 0
66
+ def push_front(obj)
67
+ node = Node.new(obj)
68
+ if @front
69
+ node.right = @front
70
+ @front.left = node
71
+ @front = node
72
+ else
73
+ @front = @back = node
74
+ end
75
+ @size += 1
76
+ obj
77
+ end
78
+
79
+ # Adds an object at the back of the Deque.
80
+ #
81
+ # d = Containers::Deque.new([1, 2, 3])
82
+ # d.push_back(4)
83
+ # d.pop_back #=> 4
84
+ def push_back(obj)
85
+ node = Node.new(obj)
86
+ if @back
87
+ node.left = @back
88
+ @back.right = node
89
+ @back = node
90
+ else
91
+ @front = @back = node
92
+ end
93
+ @size += 1
94
+ obj
95
+ end
96
+
97
+ # Returns the object at the front of the Deque and removes it.
98
+ #
99
+ # d = Containers::Deque.new
100
+ # d.push_front(1)
101
+ # d.push_front(2)
102
+ # d.pop_front #=> 2
103
+ # d.size #=> 1
104
+ def pop_front
105
+ return nil unless @front
106
+ node = @front
107
+ if @size == 1
108
+ clear
109
+ return node.obj
110
+ else
111
+ @front.right.left = nil
112
+ @front = @front.right
113
+ end
114
+ @size -= 1
115
+ node.obj
116
+ end
117
+
118
+ # Returns the object at the back of the Deque and removes it.
119
+ #
120
+ # d = Containers::Deque.new
121
+ # d.push_front(1)
122
+ # d.push_front(2)
123
+ # d.pop_back #=> 1
124
+ # d.size #=> 1
125
+ def pop_back
126
+ return nil unless @back
127
+ node = @back
128
+ if @size == 1
129
+ clear
130
+ return node.obj
131
+ else
132
+ @back.left.right = nil
133
+ @back = @back.left
134
+ end
135
+ @size -= 1
136
+ node.obj
137
+ end
138
+
139
+ # Iterate over the Deque in FIFO order.
140
+ def each_forward
141
+ return unless @front
142
+ node = @front
143
+ while node
144
+ yield node.obj
145
+ node = node.right
146
+ end
147
+ end
148
+ alias_method :each, :each_forward
149
+
150
+ # Iterate over the Deque in LIFO order.
151
+ def each_backward
152
+ return unless @back
153
+ node = @back
154
+ while node
155
+ yield node.obj
156
+ node = node.left
157
+ end
158
+ end
159
+ alias_method :reverse_each, :each_backward
160
+
161
+ class Node # :nodoc: all
162
+ attr_accessor :left, :right, :obj
163
+
164
+ def initialize(obj)
165
+ @left = @right = nil
166
+ @obj = obj
167
+ end
168
+ end
169
+ end
170
+
171
+ begin
172
+ require 'CDeque'
173
+ Containers::Deque = Containers::CDeque
174
+ rescue LoadError # C Version could not be found, try ruby version
175
+ Containers::Deque = Containers::RubyDeque
176
+ end