algorithms 0.3.0-jruby
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.
- 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,34 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
describe Containers::KDTree do
|
5
|
+
it "should work for a documented example" do
|
6
|
+
kdtree = Containers::KDTree.new( {0 => [4, 3], 1 => [3, 0], 2 => [-1, 2], 3 => [6, 4],
|
7
|
+
4 => [3, -5], 5 => [-2, -5] })
|
8
|
+
closest_2 = kdtree.find_nearest([0, 0], 2)
|
9
|
+
closest_2.should eql([[5, 2], [9, 1]])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should work for real-life example from facebook puzzle" do
|
13
|
+
points = {}
|
14
|
+
input = File.open(File.join(File.dirname(__FILE__), 'kd_test_in.txt'), 'r')
|
15
|
+
|
16
|
+
# Populate points hash
|
17
|
+
input.each_line do |line|
|
18
|
+
break if line.empty?
|
19
|
+
n, x, y = line.split(/\s+/)
|
20
|
+
points[n.to_i] = [x.to_f, y.to_f]
|
21
|
+
end
|
22
|
+
|
23
|
+
out = ""
|
24
|
+
kdtree = Containers::KDTree.new(points)
|
25
|
+
points.sort{ |(k1, v1), (k2, v2)| k1 <=> k2 }.each { |id, point|
|
26
|
+
nearest_4 = kdtree.find_nearest(point, 4)
|
27
|
+
out << "#{id} #{nearest_4[1..-1].collect{ |n| n[1] }.join(',')}\n"
|
28
|
+
}
|
29
|
+
|
30
|
+
expected = File.read(File.join(File.dirname(__FILE__), 'kd_expected_out.txt'))
|
31
|
+
expected.should eql(out)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
describe "map gc mark test" do
|
5
|
+
it "should mark ruby object references" do
|
6
|
+
anon_key_class = Class.new do
|
7
|
+
attr :value
|
8
|
+
def initialize(x); @value = x; end
|
9
|
+
def <=>(other); value <=> other.value; end
|
10
|
+
end
|
11
|
+
anon_val_class = Class.new
|
12
|
+
@rbtree = Containers::CRBTreeMap.new
|
13
|
+
@splaytree = Containers::CSplayTreeMap.new
|
14
|
+
100.times { |x|
|
15
|
+
@rbtree[anon_key_class.new(x)] = anon_val_class.new
|
16
|
+
@splaytree[anon_key_class.new(x)] = anon_val_class.new
|
17
|
+
}
|
18
|
+
# Mark and sweep
|
19
|
+
ObjectSpace.garbage_collect
|
20
|
+
# Check if any instances were swept
|
21
|
+
count = 0
|
22
|
+
ObjectSpace.each_object(anon_key_class) { |x| count += 1 }
|
23
|
+
count.should eql(200)
|
24
|
+
ObjectSpace.each_object(anon_val_class) { |x| count += 1 }
|
25
|
+
count.should eql(400)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
describe Containers::PriorityQueue do
|
5
|
+
before(:each) do
|
6
|
+
@q = Containers::PriorityQueue.new
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "(empty priority queue)" do
|
10
|
+
it "should return 0 for size and be empty" do
|
11
|
+
@q.size.should eql(0)
|
12
|
+
@q.should be_empty
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not return anything" do
|
16
|
+
@q.next.should be_nil
|
17
|
+
@q.pop.should be_nil
|
18
|
+
@q.delete(1).should be_nil
|
19
|
+
@q.has_priority?(1).should be_false
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should give the correct size when adding items" do
|
23
|
+
20.times do |i|
|
24
|
+
@q.size.should eql(i)
|
25
|
+
@q.push(i, i)
|
26
|
+
end
|
27
|
+
10.times do |i|
|
28
|
+
@q.size.should eql(20-i)
|
29
|
+
@q.pop
|
30
|
+
end
|
31
|
+
10.times do |i|
|
32
|
+
@q.size.should eql(i+10)
|
33
|
+
@q.push(i, i)
|
34
|
+
end
|
35
|
+
@q.delete(5)
|
36
|
+
@q.size.should eql(19)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "(non-empty priority queue)" do
|
41
|
+
before(:each) do
|
42
|
+
@q.push("Alaska", 50)
|
43
|
+
@q.push("Delaware", 30)
|
44
|
+
@q.push("Georgia", 35)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should next/pop the highest priority" do
|
48
|
+
@q.next.should eql("Alaska")
|
49
|
+
@q.size.should eql(3)
|
50
|
+
@q.pop.should eql("Alaska")
|
51
|
+
@q.size.should eql(2)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should not be empty" do
|
55
|
+
@q.should_not be_empty
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should has_priority? priorities it has" do
|
59
|
+
@q.has_priority?(50).should be_true
|
60
|
+
@q.has_priority?(10).should be_false
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return nil after popping everything" do
|
64
|
+
3.times do
|
65
|
+
@q.pop
|
66
|
+
end
|
67
|
+
@q.pop.should be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should delete things it has and not things it doesn't" do
|
71
|
+
@q.delete(50).should eql("Alaska")
|
72
|
+
@q.delete(10).should eql(nil)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/spec/queue_spec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
describe "empty queue" do
|
5
|
+
before(:each) do
|
6
|
+
@queue = Containers::Queue.new
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
it "should return nil when sent #pop" do
|
11
|
+
@queue.pop.should be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return a size of 1 when sent #push" do
|
15
|
+
@queue.push(1)
|
16
|
+
@queue.size.should eql(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return nil when sent #next" do
|
20
|
+
@queue.next.should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return empty?" do
|
24
|
+
@queue.empty?.should be_true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "non-empty queue" do
|
29
|
+
before(:each) do
|
30
|
+
@queue = Containers::Queue.new
|
31
|
+
@queue.push(10)
|
32
|
+
@queue.push("10")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return first pushed object" do
|
36
|
+
@queue.pop.should eql(10)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return the size" do
|
40
|
+
@queue.size.should eql(2)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should not return empty?" do
|
44
|
+
@queue.empty?.should be_false
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should iterate in FIFO order" do
|
48
|
+
arr = []
|
49
|
+
@queue.each { |obj| arr << obj }
|
50
|
+
arr.should eql([10, "10"])
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return nil after all gets" do
|
54
|
+
2.times do
|
55
|
+
@queue.pop
|
56
|
+
end
|
57
|
+
@queue.pop.should be_nil
|
58
|
+
@queue.next.should be_nil
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
describe "empty rbtree", :shared => true do
|
5
|
+
it "should let you push stuff in" do
|
6
|
+
100.times { |x| @tree[x] = x }
|
7
|
+
@tree.size.should eql(100)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should be empty?" do
|
11
|
+
@tree.empty?.should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return 0 for height" do
|
15
|
+
@tree.height.should eql(0)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return 0 for size" do
|
19
|
+
@tree.size.should eql(0)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should return nil for max and min" do
|
23
|
+
@tree.min.should be_nil
|
24
|
+
@tree.max.should be_nil
|
25
|
+
@tree.min_key.should be_nil
|
26
|
+
@tree.max_key.should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not delete" do
|
30
|
+
@tree.delete(:non_existing).should be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "non-empty rbtree", :shared => true do
|
35
|
+
before(:each) do
|
36
|
+
@num_items = 1000
|
37
|
+
@random_array = Array.new(@num_items) { rand(@num_items) }
|
38
|
+
@random_array.each { |x| @tree[x] = x }
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should return correct size (uniqify items first)" do
|
42
|
+
@tree.should_not be_empty
|
43
|
+
@tree.size.should eql(@random_array.uniq.size)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return correct max and min" do
|
47
|
+
@tree.min_key.should eql(@random_array.min)
|
48
|
+
@tree.max_key.should eql(@random_array.max)
|
49
|
+
@tree.min[0].should eql(@random_array.min)
|
50
|
+
@tree.max[0].should eql(@random_array.max)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not #has_key? keys it doesn't have" do
|
54
|
+
@tree.has_key?(100000).should be_false
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should #has_key? keys it does have" do
|
58
|
+
@tree.has_key?(@random_array[0]).should be_true
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should remove all keys" do
|
62
|
+
ordered = []
|
63
|
+
@random_array.uniq.each do |key|
|
64
|
+
@tree.has_key?(key).should eql(true)
|
65
|
+
ordered << @tree.delete(key)
|
66
|
+
@tree.has_key?(key).should eql(false)
|
67
|
+
end
|
68
|
+
ordered.should eql(@random_array.uniq)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should delete_min keys correctly" do
|
72
|
+
ascending = []
|
73
|
+
ascending << @tree.delete_min until @tree.empty?
|
74
|
+
ascending.should eql(@random_array.uniq.sort)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should delete_max keys correctly" do
|
78
|
+
descending = []
|
79
|
+
descending << @tree.delete_max until @tree.empty?
|
80
|
+
descending.should eql(@random_array.uniq.sort.reverse)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should let you iterate with #each" do
|
84
|
+
counter = 0
|
85
|
+
sorted_array = @random_array.uniq.sort
|
86
|
+
@tree.each do |key, val|
|
87
|
+
key.should eql(sorted_array[counter])
|
88
|
+
counter += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "empty rbtreemap" do
|
94
|
+
before(:each) do
|
95
|
+
@tree = Containers::RubyRBTreeMap.new
|
96
|
+
end
|
97
|
+
it_should_behave_like "empty rbtree"
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "full rbtreemap" do
|
101
|
+
before(:each) do
|
102
|
+
@tree = Containers::RubyRBTreeMap.new
|
103
|
+
end
|
104
|
+
it_should_behave_like "non-empty rbtree"
|
105
|
+
end
|
106
|
+
|
107
|
+
begin
|
108
|
+
Containers::CRBTreeMap
|
109
|
+
describe "empty crbtreemap" do
|
110
|
+
before(:each) do
|
111
|
+
@tree = Containers::CRBTreeMap.new
|
112
|
+
end
|
113
|
+
it_should_behave_like "empty rbtree"
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "full crbtreemap" do
|
117
|
+
before(:each) do
|
118
|
+
@tree = Containers::CRBTreeMap.new
|
119
|
+
end
|
120
|
+
it_should_behave_like "non-empty rbtree"
|
121
|
+
end
|
122
|
+
rescue Exception
|
123
|
+
end
|
data/spec/search_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
class String; include Algorithms::Search; end
|
5
|
+
|
6
|
+
describe "search algorithms" do
|
7
|
+
it "should binary search sorted arrays" do
|
8
|
+
n = 1000
|
9
|
+
@rand_array = Array.new(n) { rand(n) }.sort
|
10
|
+
|
11
|
+
Algorithms::Search.binary_search(@rand_array, @rand_array.first).should eql(@rand_array.first)
|
12
|
+
Algorithms::Search.binary_search(@rand_array, 999999).should be_nil
|
13
|
+
Algorithms::Search.binary_search(@rand_array, nil).should be_nil
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should use kmp_search to find substrings it has" do
|
17
|
+
string = "ABC ABCDAB ABCDABCDABDE"
|
18
|
+
Algorithms::Search.kmp_search(string, "ABCDABD").should eql(15)
|
19
|
+
Algorithms::Search.kmp_search(string, "ABCDEF").should be_nil
|
20
|
+
Algorithms::Search.kmp_search(string, nil).should be_nil
|
21
|
+
Algorithms::Search.kmp_search(string, "").should be_nil
|
22
|
+
Algorithms::Search.kmp_search(nil, "ABCD").should be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should let you include Search in String to enable instance methods" do
|
26
|
+
"ABC ABCDAB ABCDABCDABDE".kmp_search("ABCDABD").should eql(15)
|
27
|
+
end
|
28
|
+
end
|
data/spec/sort_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
include Algorithms
|
4
|
+
|
5
|
+
describe "sort algorithms" do
|
6
|
+
before(:each) do
|
7
|
+
@sorts = %w(bubble_sort comb_sort selection_sort heapsort insertion_sort shell_sort quicksort mergesort)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should work for empty containers" do
|
11
|
+
empty_array = []
|
12
|
+
@sorts.each { |sort| Sort.send(sort, empty_array).should eql([]) }
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should work for a container of size 1" do
|
16
|
+
one_array = [1]
|
17
|
+
@sorts.each { |sort| Sort.send(sort, one_array).should eql(one_array) }
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should work for random arrays of numbers" do
|
21
|
+
n = 500
|
22
|
+
rand_array = Array.new(n) { rand(n) }
|
23
|
+
sorted_array = rand_array.sort
|
24
|
+
|
25
|
+
@sorts.each { |sort| Sort.send(sort, rand_array.dup).should eql(sorted_array) }
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
|
+
require 'algorithms'
|
3
|
+
|
4
|
+
describe "empty splaytree", :shared => true do
|
5
|
+
it "should let you push stuff in" do
|
6
|
+
100.times { |x| @tree[x] = x }
|
7
|
+
@tree.size.should eql(100)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return 0 for size" do
|
11
|
+
@tree.size.should eql(0)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return nil for #min and #max" do
|
15
|
+
@tree.min.should be_nil
|
16
|
+
@tree.max.should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should return nil for #delete" do
|
20
|
+
@tree.delete(:non_existing).should be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return nil for #get" do
|
24
|
+
@tree[4235].should be_nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "non-empty splaytree", :shared => true do
|
29
|
+
before(:each) do
|
30
|
+
@num_items = 100
|
31
|
+
@random_array = []
|
32
|
+
@num_items.times { @random_array << rand(@num_items) }
|
33
|
+
@random_array.each { |x| @tree[x] = x }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return correct size (uniqify items first)" do
|
37
|
+
@tree.size.should eql(@random_array.uniq.size)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should have correct height (worst case is when items are inserted in order, and height = num items inserted)" do
|
41
|
+
@tree.clear
|
42
|
+
10.times { |x| @tree[x] = x }
|
43
|
+
@tree.height.should eql(10)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return correct max and min keys" do
|
47
|
+
@tree.min[0].should eql(@random_array.min)
|
48
|
+
@tree.max[0].should eql(@random_array.max)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not #has_key? keys it doesn't have" do
|
52
|
+
@tree.has_key?(10000).should be_false
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should #has_key? keys it does have" do
|
56
|
+
@tree.has_key?(@random_array[0]).should be_true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should remove any key" do
|
60
|
+
random_key = @random_array[rand(@num_items)]
|
61
|
+
@tree.has_key?(random_key).should be_true
|
62
|
+
@tree.delete(random_key).should eql(random_key)
|
63
|
+
@tree.has_key?(random_key).should be_false
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should let you iterate with #each" do
|
67
|
+
counter = 0
|
68
|
+
sorted_array = @random_array.uniq.sort
|
69
|
+
@tree.each do |key, val|
|
70
|
+
key.should eql(sorted_array[counter])
|
71
|
+
counter += 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "empty splaytreemap" do
|
77
|
+
before(:each) do
|
78
|
+
@tree = Containers::RubySplayTreeMap.new
|
79
|
+
end
|
80
|
+
it_should_behave_like "empty splaytree"
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "full splaytreemap" do
|
84
|
+
before(:each) do
|
85
|
+
@tree = Containers::RubySplayTreeMap.new
|
86
|
+
end
|
87
|
+
it_should_behave_like "non-empty splaytree"
|
88
|
+
end
|
89
|
+
|
90
|
+
begin
|
91
|
+
Containers::CSplayTreeMap
|
92
|
+
describe "empty csplaytreemap" do
|
93
|
+
before(:each) do
|
94
|
+
@tree = Containers::CSplayTreeMap.new
|
95
|
+
end
|
96
|
+
it_should_behave_like "empty splaytree"
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "full csplaytreemap" do
|
100
|
+
before(:each) do
|
101
|
+
@tree = Containers::CSplayTreeMap.new
|
102
|
+
end
|
103
|
+
it_should_behave_like "non-empty splaytree"
|
104
|
+
end
|
105
|
+
rescue Exception
|
106
|
+
end
|