min_max 0.1.4 → 0.1.6
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/Cargo.lock +2 -2
- data/Cargo.toml +2 -1
- data/README.md +15 -4
- data/benchmarks/benchmarks.rb +85 -0
- data/ext/min_max/src/lib.rs +3 -3
- data/lib/min_max/version.rb +1 -1
- data/lib/min_max.rb +38 -43
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 012d1e5c570db138f41f8ac8556dde89598878d15fc9176a7006bdd33e5deaf5
|
4
|
+
data.tar.gz: 70a8c3a7bb6afdaf23260ccd56c87acd1bd85e49d40d9231fbb316bf6beb4917
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc3d399e2eae53e244c0bb3a5d502cf725e3b4d4b0d941e1c96d9f0ceeee367a4688ec191116c75d94bed5bbc4470c59626e53da13c85afb24fd44df1f6668bc
|
7
|
+
data.tar.gz: d98bef37722feef1534e85f709f95dcaf314dc5f9013b32e10d667c653b97c3274e8f210fa42d2fdeb6f1fdb63c9f4a62da2d1cfd44ae2178b1439d79c56e8a8
|
data/Cargo.lock
CHANGED
@@ -270,9 +270,9 @@ checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
|
270
270
|
|
271
271
|
[[package]]
|
272
272
|
name = "shlex"
|
273
|
-
version = "1.
|
273
|
+
version = "1.3.0"
|
274
274
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
275
|
-
checksum = "
|
275
|
+
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
276
276
|
|
277
277
|
[[package]]
|
278
278
|
name = "syn"
|
data/Cargo.toml
CHANGED
data/README.md
CHANGED
@@ -24,10 +24,6 @@ Add this line to your application's Gemfile:
|
|
24
24
|
|
25
25
|
```ruby
|
26
26
|
gem 'min_max'
|
27
|
-
|
28
|
-
# or manually specify target . E.g.
|
29
|
-
|
30
|
-
CARGO_BUILD_TARGET=x86_64-apple-darwin gem install min_max
|
31
27
|
```
|
32
28
|
|
33
29
|
|
@@ -40,6 +36,10 @@ bundle install
|
|
40
36
|
Or install it yourself as:
|
41
37
|
```bash
|
42
38
|
gem install min_max
|
39
|
+
|
40
|
+
# or manually specify target . E.g.
|
41
|
+
|
42
|
+
CARGO_BUILD_TARGET=x86_64-apple-darwin gem install min_max
|
43
43
|
```
|
44
44
|
|
45
45
|
## Usage
|
@@ -100,3 +100,14 @@ heap.count(item)
|
|
100
100
|
heap.contains?(item)
|
101
101
|
```
|
102
102
|
|
103
|
+
## Performance
|
104
|
+
You can run the `benchmarks/benchmarks.rb` file inside this repository for comparison to other popular heap libraries:
|
105
|
+
* [rb_heap](https://github.com/florian/rb_heap)
|
106
|
+
* [algorithms](https://github.com/kanwei/algorithms)
|
107
|
+
* [ruby-heap](https://github.com/general-CbIC/ruby-heap)
|
108
|
+
* [pqueue](https://github.com/rubyworks/pqueue)
|
109
|
+
|
110
|
+
min-max should be the fastest to pop from a large heap, often by a significant margin, while also offering both min and max operations from a single heap.
|
111
|
+
Some options are faster at pushing individual items, but the difference is within the same order of magnitude.
|
112
|
+
Batch pushing to min-max also significantly increases insert speed.
|
113
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "min_max"
|
3
|
+
|
4
|
+
require 'bundler/inline'
|
5
|
+
|
6
|
+
gemfile do
|
7
|
+
source 'https://rubygems.org'
|
8
|
+
gem 'rb_heap'
|
9
|
+
gem 'algorithms'
|
10
|
+
gem 'ruby-heap'
|
11
|
+
gem 'pqueue'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'benchmark'
|
15
|
+
require 'rb_heap'
|
16
|
+
require 'min_max'
|
17
|
+
|
18
|
+
|
19
|
+
data = 100_000.times.map{ Random.rand(0...1000_000_000) }
|
20
|
+
rb_hp = Heap.new(:<)
|
21
|
+
mm_hp = MinMax[]
|
22
|
+
|
23
|
+
Benchmark.bm do |x|
|
24
|
+
x.report("push_rb_heap"){ data.each{|d| rb_hp.add(d) } }
|
25
|
+
x.report("push_mm_heap"){ data.each{|d| mm_hp.push(d) } }
|
26
|
+
x.report("push_mm_heap_batches"){ mm_hp = MinMax[]; data.each_slice(1000){|d| mm_hp.push(*d) } }
|
27
|
+
end
|
28
|
+
|
29
|
+
Benchmark.bm do |x|
|
30
|
+
x.report("pop_rb_heap"){ 100_000.times{|d| rb_hp.pop } }
|
31
|
+
x.report("pop_mm_heap"){ 100_000.times{|d| mm_hp.pop_min } }
|
32
|
+
x.report("pop_mm_heap_batches"){ 1000.times{|d| mm_hp.pop_min(100) } }
|
33
|
+
end
|
34
|
+
|
35
|
+
Object.send(:remove_const, :Heap)
|
36
|
+
|
37
|
+
require 'Heap'
|
38
|
+
puts "# ruby-heap vs min_max"
|
39
|
+
ruby_hp = Heap::BinaryHeap::MinHeap.new
|
40
|
+
mm_hp = MinMax[]
|
41
|
+
|
42
|
+
Benchmark.bm do |x|
|
43
|
+
x.report("push_ruby_heap"){ data.each{|d| ruby_hp.add(d) } }
|
44
|
+
x.report("push_mm_heap"){ data.each{|d| mm_hp.push(d) } }
|
45
|
+
x.report("push_mm_heap_batches"){ mm_hp = MinMax[]; data.each_slice(1000){|d| mm_hp.push(*d) } }
|
46
|
+
end
|
47
|
+
|
48
|
+
Benchmark.bm do |x|
|
49
|
+
x.report("pop_ruby_heap"){ 100_000.times{|d| ruby_hp.extract_min! } }
|
50
|
+
x.report("pop_mm_heap"){ 100_000.times{|d| mm_hp.pop_min } }
|
51
|
+
x.report("pop_mm_heap_batches"){ 1000.times{|d| mm_hp.pop_min(100) } }
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
require 'algorithms'
|
56
|
+
minheap = Containers::MinHeap.new()
|
57
|
+
mm_hp = MinMax[]
|
58
|
+
|
59
|
+
Benchmark.bm do |x|
|
60
|
+
x.report("push_algos_heap"){ data.each{|d| minheap.push(d) } }
|
61
|
+
x.report("push_mm_heap"){ data.each{|d| mm_hp.push(d) } }
|
62
|
+
x.report("push_mm_heap_batches"){ mm_hp = MinMax[]; data.each_slice(1000){|d| mm_hp.push(*d) } }
|
63
|
+
end
|
64
|
+
|
65
|
+
Benchmark.bm do |x|
|
66
|
+
x.report("pop_algos_heap"){ 100_000.times{|d| minheap.pop } }
|
67
|
+
x.report("pop_mm_heap"){ 100_000.times{|d| mm_hp.pop_min } }
|
68
|
+
x.report("pop_mm_heap_batches"){ 1000.times{|d| mm_hp.pop_min(100) } }
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'pqueue'
|
72
|
+
pqueue = PQueue.new()
|
73
|
+
mm_hp = MinMax[]
|
74
|
+
|
75
|
+
Benchmark.bm do |x|
|
76
|
+
x.report("push_pqueue_heap"){ data.each{|d| pqueue.push(d) } }
|
77
|
+
x.report("push_mm_heap"){ data.each{|d| mm_hp.push(d) } }
|
78
|
+
x.report("push_mm_heap_batches"){ mm_hp = MinMax[]; data.each_slice(1000){|d| mm_hp.push(*d) } }
|
79
|
+
end
|
80
|
+
|
81
|
+
Benchmark.bm do |x|
|
82
|
+
x.report("pop_pqueue_heap"){ 100_000.times{|d| pqueue.pop } }
|
83
|
+
x.report("pop_mm_heap"){ 100_000.times{|d| mm_hp.pop_min } }
|
84
|
+
x.report("pop_mm_heap_batches"){ 1000.times{|d| mm_hp.pop_min(100) } }
|
85
|
+
end
|
data/ext/min_max/src/lib.rs
CHANGED
@@ -52,9 +52,9 @@ impl RubyMinMaxHeap {
|
|
52
52
|
|
53
53
|
fn push(&self, values: RArray) -> Result<(), Error> {
|
54
54
|
let mut hp = self.heap.borrow_mut();
|
55
|
-
let values_vec = values.to_vec::<(i64, i64)>()
|
56
|
-
values_vec.
|
57
|
-
hp.push(PriorityOrderableValue(priority, key));
|
55
|
+
let values_vec = values.to_vec::<(i64, i64)>()?;
|
56
|
+
values_vec.iter().for_each(|(priority, key)| {
|
57
|
+
hp.push(PriorityOrderableValue(*priority, *key));
|
58
58
|
});
|
59
59
|
Ok(())
|
60
60
|
}
|
data/lib/min_max/version.rb
CHANGED
data/lib/min_max.rb
CHANGED
@@ -6,7 +6,7 @@ require_relative "min_max/min_max"
|
|
6
6
|
class MinMax
|
7
7
|
class Error < StandardError; end
|
8
8
|
|
9
|
-
attr_reader :priority_blk, :storage
|
9
|
+
attr_reader :priority_blk, :storage
|
10
10
|
|
11
11
|
def self.[](*args, &blk)
|
12
12
|
new(*args, &blk)
|
@@ -15,28 +15,25 @@ class MinMax
|
|
15
15
|
def self.new(*args, &blk)
|
16
16
|
self._new.tap{|s|
|
17
17
|
s.instance_eval{
|
18
|
-
@priority_blk = (blk || proc{|x|
|
19
|
-
@storage = Hash.new
|
20
|
-
@counts = Hash.new(0)
|
21
|
-
@mtx ||= Mutex.new
|
18
|
+
@priority_blk = (blk || proc{|x| x.respond_to?(:priority) ? x.priority : x.to_i })
|
19
|
+
@storage = Hash.new{|h,k| h[k] = [0, nil] }
|
22
20
|
}
|
23
21
|
s.push(*args)
|
24
22
|
}
|
25
23
|
end
|
26
24
|
|
27
25
|
def push(*args)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
_push(mapped)
|
26
|
+
mapped = args.map do |a|
|
27
|
+
hash = a.hash
|
28
|
+
entry = self.storage[hash]
|
29
|
+
entry[0] += 1
|
30
|
+
entry[1] ||= a
|
31
|
+
[
|
32
|
+
(self.priority_blk.call(a) rescue 0),
|
33
|
+
hash
|
34
|
+
]
|
38
35
|
end
|
39
|
-
|
36
|
+
_push(mapped)
|
40
37
|
end
|
41
38
|
|
42
39
|
def add(*args)
|
@@ -44,32 +41,36 @@ class MinMax
|
|
44
41
|
end
|
45
42
|
|
46
43
|
def pop_max(*args)
|
47
|
-
|
48
|
-
|
49
|
-
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
50
|
-
}
|
44
|
+
popped = _pop_max(*args)
|
45
|
+
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
51
46
|
end
|
52
47
|
|
53
48
|
def pop_min(*args)
|
54
|
-
|
55
|
-
|
56
|
-
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
57
|
-
}
|
49
|
+
popped = _pop_min(*args)
|
50
|
+
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
58
51
|
end
|
59
52
|
|
60
|
-
def peek_min
|
61
|
-
|
62
|
-
peeked.kind_of?(Array) ? peeked.map{|p| retrieve(p, false) } : retrieve(popped, false)
|
53
|
+
def peek_min
|
54
|
+
retrieve(_peek_min, false)
|
63
55
|
end
|
64
56
|
|
65
|
-
def peek_max
|
66
|
-
|
67
|
-
|
57
|
+
def peek_max
|
58
|
+
retrieve(_peek_max, false)
|
59
|
+
end
|
60
|
+
|
61
|
+
def first
|
62
|
+
peek_min
|
63
|
+
end
|
64
|
+
|
65
|
+
def last
|
66
|
+
peek_max
|
68
67
|
end
|
69
68
|
|
70
69
|
def each(*args, &blk)
|
71
70
|
if block_given?
|
72
|
-
|
71
|
+
_each(*args) do |p|
|
72
|
+
blk[retrieve(p, false)]
|
73
|
+
end
|
73
74
|
else
|
74
75
|
to_enum(:each, *args)
|
75
76
|
end
|
@@ -88,28 +89,22 @@ class MinMax
|
|
88
89
|
end
|
89
90
|
|
90
91
|
def to_a_asc
|
91
|
-
|
92
|
+
_to_a_asc.map{|p| retrieve(p, false) }
|
92
93
|
end
|
93
94
|
|
94
95
|
def to_a_desc
|
95
|
-
|
96
|
+
_to_a_desc.map{|p| retrieve(p, false) }
|
96
97
|
end
|
97
98
|
|
98
99
|
def inspect
|
99
|
-
"MinMax[#{
|
100
|
+
"MinMax[#{each.first(10).map(&:to_s).join(", ")}#{size > 10 ? ", ..." : ""}]"
|
100
101
|
end
|
101
102
|
|
102
103
|
private
|
103
104
|
def retrieve(hash, remove=true)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
else
|
108
|
-
self.storage[hash]
|
109
|
-
end
|
110
|
-
else
|
111
|
-
self.storage[hash]
|
112
|
-
end
|
105
|
+
entry = self.storage[hash]
|
106
|
+
self.storage.delete(hash) if remove && (entry[0] -= 1) == 0
|
107
|
+
entry[1]
|
113
108
|
end
|
114
109
|
|
115
110
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: min_max
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
@@ -66,6 +66,7 @@ files:
|
|
66
66
|
- LICENSE.txt
|
67
67
|
- README.md
|
68
68
|
- Rakefile
|
69
|
+
- benchmarks/benchmarks.rb
|
69
70
|
- ext/min_max/Cargo.toml
|
70
71
|
- ext/min_max/extconf.rb
|
71
72
|
- ext/min_max/src/lib.rs
|
@@ -93,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
94
|
- !ruby/object:Gem::Version
|
94
95
|
version: 3.3.11
|
95
96
|
requirements: []
|
96
|
-
rubygems_version: 3.
|
97
|
+
rubygems_version: 3.5.6
|
97
98
|
signing_key:
|
98
99
|
specification_version: 4
|
99
100
|
summary: A min max heap extension for Ruby
|