algorithms 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +4 -0
- data/README.markdown +2 -2
- data/algorithms.gemspec +2 -2
- data/lib/containers/heap.rb +1 -9
- data/lib/containers/priority_queue.rb +0 -1
- data/lib/containers/rb_tree_map.rb +2 -2
- data/lib/containers/suffix_array.rb +3 -3
- data/spec/heap_spec.rb +186 -0
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2710897393899fe90ddd5a3046f0fee0dd6d29aec842879ec28654e2767d1626
|
4
|
+
data.tar.gz: 8ccc55227d725e590ea965767765c29bb66211866cc948d81a3ed4997d6db733
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fd77563ddfaecf2ab085f4ff9da6b49e0c63cb65ea666ebf737a3aee175498ddb481908b2135d3ae861ae51f756f9abc9749d27a91d802b4fd8c32965d8a117
|
7
|
+
data.tar.gz: d002eba1aa937be5f7afc70e32155dccb108ef5d71cc874729379b403534e34122311234aab35f05c4c1a2df5b0ecfc7fed32b645c2865d1a59b1b911a3564dd
|
data/CHANGELOG.markdown
CHANGED
data/README.markdown
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# algorithms [](https://github.com/kanwei/algorithms/actions/workflows/ci.yml)
|
2
2
|
|
3
3
|
[API Documentation](http://kanwei.github.io/algorithms/)
|
4
4
|
|
@@ -8,7 +8,7 @@ Started as a [Google Summer of Code 2008](http://code.google.com/soc/2008/ruby/a
|
|
8
8
|
|
9
9
|
Written by [Kanwei Li](http://kanwei.com/), mentored by Austin Ziegler
|
10
10
|
|
11
|
-
|
11
|
+
## Original Proposal: ##
|
12
12
|
|
13
13
|
Using the right data structure or algorithm for the situation is an important
|
14
14
|
aspect of programming. In computer science literature, many data structures
|
data/algorithms.gemspec
CHANGED
@@ -2,12 +2,12 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "algorithms"
|
5
|
-
s.version = "1.
|
5
|
+
s.version = "1.1.0"
|
6
6
|
|
7
7
|
s.authors = ["Kanwei Li"]
|
8
8
|
s.email = "kanwei@gmail.com"
|
9
9
|
s.license = 'MIT'
|
10
|
-
s.date = "
|
10
|
+
s.date = "2023-11-11"
|
11
11
|
s.summary = "Useful algorithms and data structures for Ruby. Optional C extensions."
|
12
12
|
s.description = "Heap, Priority Queue, Deque, Stack, Queue, Red-Black Trees, Splay Trees, sorting algorithms, and more"
|
13
13
|
if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
data/lib/containers/heap.rb
CHANGED
@@ -10,7 +10,6 @@
|
|
10
10
|
This library implements a Fibonacci heap, which allows O(1) complexity for most methods.
|
11
11
|
=end
|
12
12
|
class Containers::Heap
|
13
|
-
include Enumerable
|
14
13
|
|
15
14
|
# call-seq:
|
16
15
|
# size -> int
|
@@ -75,13 +74,6 @@ class Containers::Heap
|
|
75
74
|
end
|
76
75
|
@size += 1
|
77
76
|
|
78
|
-
arr = []
|
79
|
-
w = @next.right
|
80
|
-
until w == @next do
|
81
|
-
arr << w.value
|
82
|
-
w = w.right
|
83
|
-
end
|
84
|
-
arr << @next.value
|
85
77
|
@stored[key] ||= []
|
86
78
|
@stored[key] << node
|
87
79
|
value
|
@@ -376,7 +368,7 @@ class Containers::Heap
|
|
376
368
|
degree += 1
|
377
369
|
end
|
378
370
|
degrees[degree] = root
|
379
|
-
min = root if min.key
|
371
|
+
min = root if !@compare_fn[min.key, root.key] # this fixes a bug with duplicate keys not being in the right order
|
380
372
|
end
|
381
373
|
end
|
382
374
|
@next = min
|
@@ -15,7 +15,7 @@ require 'containers/stack'
|
|
15
15
|
explicitly used as well; their functionality is identical.
|
16
16
|
|
17
17
|
Most methods have O(log n) complexity.
|
18
|
-
|
18
|
+
|
19
19
|
=end
|
20
20
|
class Containers::RubyRBTreeMap
|
21
21
|
include Enumerable
|
@@ -160,7 +160,7 @@ class Containers::RubyRBTreeMap
|
|
160
160
|
result
|
161
161
|
end
|
162
162
|
|
163
|
-
# Deletes the item with the
|
163
|
+
# Deletes the item with the largest key and returns the item. Returns nil
|
164
164
|
# if key is not present.
|
165
165
|
#
|
166
166
|
# Complexity: O(log n)
|
@@ -12,10 +12,10 @@ class Containers::SuffixArray
|
|
12
12
|
#
|
13
13
|
# Complexity: O(n^2 log n)
|
14
14
|
#
|
15
|
-
# s_array = Containers::SuffixArray("abracadabra")
|
15
|
+
# s_array = Containers::SuffixArray.new("abracadabra")
|
16
16
|
# s_array["abra"] #=> true
|
17
17
|
#
|
18
|
-
# number = Containers::SuffixArray(1234567)
|
18
|
+
# number = Containers::SuffixArray.new(1234567)
|
19
19
|
# number[1] #=> true
|
20
20
|
# number[13] #=> false
|
21
21
|
def initialize(string)
|
@@ -65,4 +65,4 @@ class Containers::SuffixArray
|
|
65
65
|
return false
|
66
66
|
end
|
67
67
|
alias_method :[], :has_substring?
|
68
|
-
end
|
68
|
+
end
|
data/spec/heap_spec.rb
CHANGED
@@ -1,10 +1,196 @@
|
|
1
1
|
$: << File.join(File.expand_path(File.dirname(__FILE__)), '..', 'lib')
|
2
2
|
require 'algorithms'
|
3
3
|
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
class Book
|
7
|
+
attr_reader :title, :year_published
|
8
|
+
|
9
|
+
def initialize(title, year_published)
|
10
|
+
@title = title
|
11
|
+
@year_published = year_published
|
12
|
+
end
|
13
|
+
|
14
|
+
def <=>(other)
|
15
|
+
return nil unless other.is_a?(Book)
|
16
|
+
|
17
|
+
# 1. Compare by year first
|
18
|
+
year_comparison = self.year_published <=> other.year_published
|
19
|
+
|
20
|
+
# 2. If years are different, return that comparison result
|
21
|
+
return year_comparison unless year_comparison == 0
|
22
|
+
|
23
|
+
# 3. If years are the same (tie), compare by title alphabetically
|
24
|
+
# (Ensure title comparison returns -1, 0, or 1)
|
25
|
+
self.title <=> other.title
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
"\"#{title}\" (#{year_published})" # Simplified for test output
|
31
|
+
end
|
32
|
+
|
33
|
+
# Add an equality method for clearer test failures if needed,
|
34
|
+
# though <=> returning 0 handles equality for sorting/heap purposes.
|
35
|
+
def ==(other)
|
36
|
+
other.is_a?(Book) &&
|
37
|
+
self.title == other.title &&
|
38
|
+
self.year_published == other.year_published
|
39
|
+
end
|
40
|
+
alias eql? ==
|
41
|
+
|
42
|
+
def hash
|
43
|
+
[title, year_published].hash
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ::Containers::Heap do
|
49
|
+
|
50
|
+
# --- Test Data Setup ---
|
51
|
+
let!(:book_1932) { Book.new("Brave New World", 1932) }
|
52
|
+
let!(:book_1949) { Book.new("1984", 1949) }
|
53
|
+
let!(:book_1951a) { Book.new("Foundation", 1951) }
|
54
|
+
# Add another book from the same year to test handling of equal priority items
|
55
|
+
let!(:book_1951b) { Book.new("The Illustrated Man", 1951) }
|
56
|
+
let!(:book_1965) { Book.new("Dune", 1965) }
|
57
|
+
let!(:book_1989) { Book.new("Hyperion", 1989) }
|
58
|
+
|
59
|
+
# Use shuffle to ensure tests don't depend on insertion order
|
60
|
+
let(:books) { [book_1965, book_1951a, book_1989, book_1949, book_1932, book_1951b].shuffle }
|
61
|
+
|
62
|
+
let(:expected_min_order) { books.sort }
|
63
|
+
|
64
|
+
let(:expected_max_order) { books.sort.reverse }
|
65
|
+
|
66
|
+
context "Min-Heap (using default <=> via Comparable)" do
|
67
|
+
# Initialize heap with books; it should use Book#<=> by default
|
68
|
+
let(:min_heap) { ::Containers::Heap.new(books) }
|
69
|
+
|
70
|
+
it "initializes with the correct size" do
|
71
|
+
expect(min_heap.size).to eq(books.size)
|
72
|
+
expect(min_heap.empty?).to be false
|
73
|
+
end
|
74
|
+
|
75
|
+
it "gets the next minimum element (earliest year) without removing it" do
|
76
|
+
# next should return the element with the smallest year_published
|
77
|
+
expect(min_heap.next).to eq(book_1932)
|
78
|
+
# next should not change the size
|
79
|
+
expect(min_heap.size).to eq(books.size)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "pops elements in ascending order of year_published" do
|
83
|
+
popped_books = []
|
84
|
+
while (book = min_heap.pop)
|
85
|
+
popped_books << book
|
86
|
+
end
|
87
|
+
|
88
|
+
# Verify the popped order matches the expected sorted order
|
89
|
+
expect(popped_books).to eq(expected_min_order)
|
90
|
+
|
91
|
+
# Verify the heap is now empty
|
92
|
+
expect(min_heap.size).to eq(0)
|
93
|
+
expect(min_heap.empty?).to be true
|
94
|
+
expect(min_heap.pop).to be_nil # Popping an empty heap
|
95
|
+
expect(min_heap.next).to be_nil # Getting next from an empty heap
|
96
|
+
end
|
97
|
+
|
98
|
+
it "correctly updates size after popping" do
|
99
|
+
expect(min_heap.size).to eq(books.size)
|
100
|
+
min_heap.pop
|
101
|
+
expect(min_heap.size).to eq(books.size - 1)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
context "Max-Heap (using a custom comparison block)" do
|
107
|
+
# Initialize heap with books and a block for max-heap behavior
|
108
|
+
# The block returns true if x should have higher priority (be "larger") than y
|
109
|
+
let(:max_heap) { ::Containers::Heap.new(books) { |x, y| x > y } } # Use > from Comparable
|
110
|
+
|
111
|
+
it "initializes with the correct size" do
|
112
|
+
expect(max_heap.size).to eq(books.size)
|
113
|
+
expect(max_heap.empty?).to be false
|
114
|
+
end
|
115
|
+
|
116
|
+
it "gets the next maximum element (latest year) without removing it" do
|
117
|
+
# next should return the element with the largest year_published
|
118
|
+
expect(max_heap.next).to eq(book_1989)
|
119
|
+
# next should not change the size
|
120
|
+
expect(max_heap.size).to eq(books.size)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "pops elements in descending order of year_published" do
|
124
|
+
popped_books = []
|
125
|
+
while (book = max_heap.pop)
|
126
|
+
popped_books << book
|
127
|
+
end
|
128
|
+
|
129
|
+
# Verify the popped order matches the expected reversed sorted order
|
130
|
+
expect(popped_books).to eq(expected_max_order)
|
131
|
+
|
132
|
+
# Verify the heap is now empty
|
133
|
+
expect(max_heap.size).to eq(0)
|
134
|
+
expect(max_heap.empty?).to be true
|
135
|
+
expect(max_heap.pop).to be_nil # Popping an empty heap
|
136
|
+
expect(max_heap.next).to be_nil # Getting next from an empty heap
|
137
|
+
end
|
138
|
+
|
139
|
+
it "correctly updates size after popping" do
|
140
|
+
expect(max_heap.size).to eq(books.size)
|
141
|
+
max_heap.pop
|
142
|
+
expect(max_heap.size).to eq(books.size - 1)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "Edge cases" do
|
147
|
+
it "handles an empty initialization" do
|
148
|
+
heap = ::Containers::Heap.new([])
|
149
|
+
expect(heap.size).to eq(0)
|
150
|
+
expect(heap.empty?).to be true
|
151
|
+
expect(heap.pop).to be_nil
|
152
|
+
expect(heap.next).to be_nil
|
153
|
+
end
|
154
|
+
|
155
|
+
it "handles initialization with one element" do
|
156
|
+
heap = ::Containers::Heap.new([book_1965])
|
157
|
+
expect(heap.size).to eq(1)
|
158
|
+
expect(heap.next).to eq(book_1965)
|
159
|
+
expect(heap.pop).to eq(book_1965)
|
160
|
+
expect(heap.empty?).to be true
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
4
166
|
describe Containers::Heap do
|
5
167
|
before(:each) do
|
6
168
|
@heap = Containers::MaxHeap.new
|
7
169
|
end
|
170
|
+
|
171
|
+
it "should run without error when given Objects and a non-ordering comparator" do
|
172
|
+
# Create and store the initial distinct objects
|
173
|
+
initial_objects = 10.times.map { Object.new }
|
174
|
+
initial_object_ids = initial_objects.map(&:object_id) # Store IDs for comparison
|
175
|
+
|
176
|
+
min_heap = ::Containers::Heap.new(initial_objects) { |x, y| (x <=> y) == -1 }
|
177
|
+
|
178
|
+
expect(min_heap.size).to eq(10)
|
179
|
+
|
180
|
+
popped_elements = []
|
181
|
+
expect {
|
182
|
+
while val = min_heap.pop do
|
183
|
+
popped_elements << val
|
184
|
+
end
|
185
|
+
}.not_to raise_error
|
186
|
+
|
187
|
+
expect(min_heap.empty?).to be true
|
188
|
+
expect(popped_elements.size).to eq(10)
|
189
|
+
|
190
|
+
# Assert that exactly the same objects were returned, regardless of order.
|
191
|
+
# Comparing by object_id is the most reliable way for distinct Objects.
|
192
|
+
expect(popped_elements.map(&:object_id)).to contain_exactly(*initial_object_ids)
|
193
|
+
end
|
8
194
|
|
9
195
|
it "should not let you merge with non-heaps" do
|
10
196
|
expect { @heap.merge!(nil) }.to raise_error(ArgumentError)
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: algorithms
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kanwei Li
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2023-11-11 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: Heap, Priority Queue, Deque, Stack, Queue, Red-Black Trees, Splay Trees,
|
14
13
|
sorting algorithms, and more
|
@@ -78,7 +77,6 @@ homepage: https://github.com/kanwei/algorithms
|
|
78
77
|
licenses:
|
79
78
|
- MIT
|
80
79
|
metadata: {}
|
81
|
-
post_install_message:
|
82
80
|
rdoc_options:
|
83
81
|
- "--line-numbers"
|
84
82
|
- "--inline-source"
|
@@ -100,8 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
98
|
- !ruby/object:Gem::Version
|
101
99
|
version: '0'
|
102
100
|
requirements: []
|
103
|
-
rubygems_version: 3.
|
104
|
-
signing_key:
|
101
|
+
rubygems_version: 3.6.9
|
105
102
|
specification_version: 4
|
106
103
|
summary: Useful algorithms and data structures for Ruby. Optional C extensions.
|
107
104
|
test_files: []
|