rb_heap 1.0.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +149 -0
- data/Rakefile +6 -0
- data/bin/console +16 -0
- data/bin/setup +8 -0
- data/lib/rb_heap.rb +5 -0
- data/lib/rb_heap/heap.rb +130 -0
- data/lib/rb_heap/sort.rb +28 -0
- data/lib/rb_heap/version.rb +3 -0
- data/rb_heap.gemspec +26 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 66cfaef0351036198f0b4adb551c58c93a5cbcd5
|
4
|
+
data.tar.gz: 2d15030d700c18b8013d6916bc015942847427f7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 318644b3e21766745cec5396571cde7050a7a63401976e8da001e05e9cdda089be60fb720fac726ae6a36c8e75709b72f9fc1cf31a6f686e70bcf4a2759872f9
|
7
|
+
data.tar.gz: a8ec355b5956b6ca1440bb5a31d7d36a948d870e4ed5ce5ecf21bbf18cd410a275d2f0f95446ce6909c11c989cf4352929292604f885eb4a31a27ab59d53704f
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Florian Hartmann
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# Heap
|
2
|
+
|
3
|
+
This is an heap implementation that can be used for priority queues. Internally
|
4
|
+
it represents the heap as an array. It also contains an in-place heapsort
|
5
|
+
implementation.
|
6
|
+
|
7
|
+
```rb
|
8
|
+
heap = Heap.new(:<)
|
9
|
+
|
10
|
+
# << and .add are aliases
|
11
|
+
heap << 3 << 1 << 2
|
12
|
+
heap.add(4)
|
13
|
+
|
14
|
+
heap.peak # => 4
|
15
|
+
heap.pop # => 4
|
16
|
+
heap.pop # => 3
|
17
|
+
heap.pop # => 2
|
18
|
+
heap.pop # => 1
|
19
|
+
```
|
20
|
+
|
21
|
+
## Different kind of heaps
|
22
|
+
|
23
|
+
The default heap is a min heap, but you can also specifiy it explicitely.
|
24
|
+
```rb
|
25
|
+
minHeap = Heap.new
|
26
|
+
anotherMinHeap = Heap.new(:<)
|
27
|
+
```
|
28
|
+
|
29
|
+
Creating a max heap is straight-forward:
|
30
|
+
```rb
|
31
|
+
maxHeap = Heap.new(:>)
|
32
|
+
```
|
33
|
+
|
34
|
+
You can also pass a custom comparison function using a block:
|
35
|
+
```rb
|
36
|
+
minAbsHeap = Heap.new{|a, b| a.abs < b.abs}
|
37
|
+
minAbsHeap << 3 << 1 << -2
|
38
|
+
minAbsHeap.pop # => 1
|
39
|
+
minAbsHeap.pop # => -2
|
40
|
+
minAbsHeap.pop # => 3
|
41
|
+
```
|
42
|
+
|
43
|
+
You can use this comparison function to compare specific fields:
|
44
|
+
|
45
|
+
```rb
|
46
|
+
Person = Struct.new(:name, :age)
|
47
|
+
|
48
|
+
ageHeap = Heap.new{|a, b| a.age < b.age}
|
49
|
+
ageHeap << Person.new("Richard Hendricks", 26)
|
50
|
+
ageHeap << Person.new("Erlich Bachman", 32)
|
51
|
+
ageHeap << Person.new("Dinesh Chugtai", 30)
|
52
|
+
|
53
|
+
ageHeap.pop.name # => Richard Hendricks
|
54
|
+
ageHeap.pop.name # => Dinesh Chugtai
|
55
|
+
ageHeap.pop.name # => Erlich Bachman
|
56
|
+
|
57
|
+
```
|
58
|
+
|
59
|
+
## `pop` (O(log n)) and `peak` (O(1))
|
60
|
+
|
61
|
+
`pop` removes the top element of the heap and returns it. `peak` returns the top
|
62
|
+
element but doesn't change the heap itself.
|
63
|
+
|
64
|
+
When the heap doesn't have any elements, `nil` is returned. You can also
|
65
|
+
explicitly check if the heap still has elements using the `empty?` method.
|
66
|
+
|
67
|
+
## `add` (O(log n))
|
68
|
+
|
69
|
+
You can add elements to the heap using `.add`. As you already saw `<<` is
|
70
|
+
aliased to `.add`.
|
71
|
+
|
72
|
+
## `replace` (O(log n))
|
73
|
+
|
74
|
+
You can replace the top element with a new one using this method. Of course you
|
75
|
+
could also `pop` and `add` but that would cause two rebalance operations.
|
76
|
+
`.replace` is optimised to only take one.
|
77
|
+
|
78
|
+
```rb
|
79
|
+
heap = Heap.new
|
80
|
+
heap << 1 << 2 << 3
|
81
|
+
heap.replace(4)
|
82
|
+
|
83
|
+
heap.pop # => 2
|
84
|
+
heap.pop # => 3
|
85
|
+
heap.pop # => 4
|
86
|
+
```
|
87
|
+
|
88
|
+
## `offer` (O(log n))
|
89
|
+
|
90
|
+
A lot of times you only want to replace the top element if it's smaller/larger
|
91
|
+
than the potentially new element.
|
92
|
+
|
93
|
+
```rb
|
94
|
+
heap = Heap.new
|
95
|
+
heap << 1
|
96
|
+
|
97
|
+
heap.offer(0) # It's a min heap and 1 > 0, so it's not replaced
|
98
|
+
heap.peak # => 1
|
99
|
+
|
100
|
+
heap.offer(2) # 1 < 2, so it's replaced
|
101
|
+
heap.peak # => 2
|
102
|
+
```
|
103
|
+
|
104
|
+
This method assumes that there's already a top element, so you have to check
|
105
|
+
yourself that you don't call it on an empty heap.
|
106
|
+
|
107
|
+
A potential use case would be finding the largest `k` numbers in a stream of
|
108
|
+
incoming numbers, without actually storing all numbers.
|
109
|
+
|
110
|
+
## Other utilities
|
111
|
+
|
112
|
+
### `size`
|
113
|
+
|
114
|
+
Returns the amount of elements that are in the heap.
|
115
|
+
|
116
|
+
### `empty?`
|
117
|
+
|
118
|
+
Returns a boolean that indicates whether the heap has any elements.
|
119
|
+
|
120
|
+
### `to_a`
|
121
|
+
|
122
|
+
Gives you all the heap contents as an array, but you shouldn't make any assumptions about
|
123
|
+
the order of the elements.
|
124
|
+
|
125
|
+
## Sorting
|
126
|
+
|
127
|
+
Heapsort is pretty light-weight to implement once you have a working heap. For this
|
128
|
+
reason this library also comes with an in-place heapsort function.
|
129
|
+
|
130
|
+
```rb
|
131
|
+
Heap.sort([3,1,2]) # => [1, 2, 3]
|
132
|
+
Heap.sort([3,1,2], :>) # => [3, 2, 1]
|
133
|
+
Heap.sort([3,1,-2]){|a, b| a.abs < b.abs} # => [1, -2, 3]
|
134
|
+
```
|
135
|
+
|
136
|
+
## Developing
|
137
|
+
|
138
|
+
You can run the unit tests using `rake`.
|
139
|
+
|
140
|
+
### Future ideas
|
141
|
+
|
142
|
+
Ideas for functions that could be added:
|
143
|
+
|
144
|
+
- Create a heap from an array. This is possible in O(n). Right now one could
|
145
|
+
repeatly add all the array elements into a new heap, but that'd be O(n log n)
|
146
|
+
- Merging two heaps
|
147
|
+
|
148
|
+
I guess it would also be fun to implement different kind of heaps, but maybe
|
149
|
+
that's beyond the scope if this library.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rb_heap"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
$a = Heap.new
|
14
|
+
|
15
|
+
require "irb"
|
16
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/rb_heap.rb
ADDED
data/lib/rb_heap/heap.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
class Heap
|
2
|
+
def initialize(compare_symbol = :<, storage = [], &compare_fn)
|
3
|
+
@heap = storage
|
4
|
+
@size = 0
|
5
|
+
initialize_compare(compare_symbol, &compare_fn)
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :size
|
9
|
+
|
10
|
+
def empty?
|
11
|
+
size == 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def peak
|
15
|
+
@heap[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
def pop
|
19
|
+
result = peak
|
20
|
+
|
21
|
+
if size > 1
|
22
|
+
@size -= 1
|
23
|
+
@heap[0] = @heap[@size]
|
24
|
+
rebalance_down(0)
|
25
|
+
else
|
26
|
+
@size = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
@heap[@size] = nil
|
30
|
+
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def add(element)
|
35
|
+
@heap[@size] = element
|
36
|
+
@size += 1
|
37
|
+
rebalance_up(size - 1)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
alias :<< :add
|
42
|
+
|
43
|
+
def replace(element)
|
44
|
+
@heap[0] = element
|
45
|
+
rebalance_down(0)
|
46
|
+
end
|
47
|
+
|
48
|
+
def offer(element)
|
49
|
+
if compare(peak, element)
|
50
|
+
result = peak
|
51
|
+
replace(element)
|
52
|
+
result
|
53
|
+
else
|
54
|
+
element
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def clear
|
59
|
+
@heap = []
|
60
|
+
@size = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_a
|
64
|
+
@heap.dup
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def initialize_compare(symbol, &fn)
|
70
|
+
@compare = if block_given?
|
71
|
+
fn
|
72
|
+
elsif symbol == :< or symbol.nil?
|
73
|
+
lambda{|a, b| a < b}
|
74
|
+
elsif symbol == :>
|
75
|
+
lambda{|a, b| a > b}
|
76
|
+
else
|
77
|
+
raise ArgumentError.new("The comparison symbol needs to be either :> or :<")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def compare(a, b)
|
82
|
+
@compare.call(a, b)
|
83
|
+
end
|
84
|
+
|
85
|
+
def rebalance_up(i)
|
86
|
+
parent_i = parent(i)
|
87
|
+
|
88
|
+
if has_parent(i) and compare(@heap[i], @heap[parent_i])
|
89
|
+
@heap[i], @heap[parent_i] = @heap[parent_i], @heap[i]
|
90
|
+
rebalance_up(parent_i)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def rebalance_down(i)
|
95
|
+
left_i = left(i)
|
96
|
+
right_i = right(i)
|
97
|
+
|
98
|
+
if has_left(i) and compare(@heap[left_i], @heap[i]) and (not has_right(i) or compare(@heap[left_i], @heap[right_i]))
|
99
|
+
@heap[i], @heap[left_i] = @heap[left_i], @heap[i]
|
100
|
+
rebalance_down(left_i)
|
101
|
+
elsif has_right(i) and compare(@heap[right_i], @heap[i])
|
102
|
+
@heap[i], @heap[right_i] = @heap[right_i], @heap[i]
|
103
|
+
rebalance_down(right_i)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def has_parent(i)
|
108
|
+
i >= 1
|
109
|
+
end
|
110
|
+
|
111
|
+
def parent(i)
|
112
|
+
((i - 1) / 2).floor
|
113
|
+
end
|
114
|
+
|
115
|
+
def has_left(i)
|
116
|
+
left(i) < size
|
117
|
+
end
|
118
|
+
|
119
|
+
def left(i)
|
120
|
+
i * 2 + 1
|
121
|
+
end
|
122
|
+
|
123
|
+
def has_right(i)
|
124
|
+
right(i) < size
|
125
|
+
end
|
126
|
+
|
127
|
+
def right(i)
|
128
|
+
i * 2 + 2
|
129
|
+
end
|
130
|
+
end
|
data/lib/rb_heap/sort.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Heap
|
2
|
+
def self.sort(array, order = :<, &compare_fn)
|
3
|
+
compare_fn = self.invertComparison(order, &compare_fn)
|
4
|
+
heap = Heap.new(order, array, &compare_fn)
|
5
|
+
|
6
|
+
array.each do |element|
|
7
|
+
heap.add(element)
|
8
|
+
end
|
9
|
+
|
10
|
+
(0...array.size).reverse_each do |i|
|
11
|
+
array[i] = heap.pop
|
12
|
+
end
|
13
|
+
|
14
|
+
heap.to_a
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.invertComparison(order, &compare_fn)
|
18
|
+
if block_given?
|
19
|
+
lambda{|a, b| not compare_fn.call(a, b)}
|
20
|
+
elsif order == :< or order.nil?
|
21
|
+
lambda{|a, b| a > b}
|
22
|
+
elsif order == :>
|
23
|
+
lambda{|a, b| a < b}
|
24
|
+
else
|
25
|
+
raise ArgumentError.new("The comparison symbol needs to be either :> or :<")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/rb_heap.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rb_heap/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rb_heap"
|
8
|
+
spec.version = Heap::VERSION
|
9
|
+
spec.authors = ["Florian Hartmann"]
|
10
|
+
spec.email = ["jscoderr@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Light-weight priority queue implementation using a heap}
|
13
|
+
spec.homepage = "https://github.com/florian/rb_heap"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rb_heap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Florian Hartmann
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- jscoderr@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- .rspec
|
64
|
+
- .travis.yml
|
65
|
+
- Gemfile
|
66
|
+
- LICENSE.txt
|
67
|
+
- README.md
|
68
|
+
- Rakefile
|
69
|
+
- bin/console
|
70
|
+
- bin/setup
|
71
|
+
- lib/rb_heap.rb
|
72
|
+
- lib/rb_heap/heap.rb
|
73
|
+
- lib/rb_heap/sort.rb
|
74
|
+
- lib/rb_heap/version.rb
|
75
|
+
- rb_heap.gemspec
|
76
|
+
homepage: https://github.com/florian/rb_heap
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.0.14.1
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: Light-weight priority queue implementation using a heap
|
100
|
+
test_files: []
|