min_max 0.1.3 → 0.1.5
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 +18 -3
- data/benchmarks/benchmarks.rb +85 -0
- data/ext/min_max/src/lib.rs +13 -38
- data/lib/min_max/version.rb +1 -1
- data/lib/min_max.rb +38 -49
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d739946dcb0939e2e05893222c927f4dea0eda65da5c0a00156402de3e49c29
|
4
|
+
data.tar.gz: 61b4453a94b93bb2ccefd0787a751cef5d3ef380ac0ed591c73c3b0afddc394b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70dd4402ca82a60cffbc451ea134ca9fc34391f28311d23726d22055bfc01db9ba62002f0d54c76282888993613f2bf5618dcfa7a964da39c6995747401f3a3f
|
7
|
+
data.tar.gz: 31ada4f8ed50624701e389c6a1d50b83b75a585ef703d2f18c6511498dc9373647fa1f88d959ccdcbf56336d38dc550e1ce96710644f65970f2341549f843d53
|
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
@@ -1,7 +1,8 @@
|
|
1
|
-
# MinMax
|
1
|
+
# MinMax Heap
|
2
2
|
|
3
|
-
The MinMax gem provides a high-performance minmax heap implementation for Ruby, written in Rust.
|
4
|
-
|
3
|
+
The MinMax Heap gem provides a high-performance minmax heap implementation for Ruby, written in Rust.
|
4
|
+
The gem wraps the excellent [min-max-heap-rs](https://github.com/tov/min-max-heap-rs) Rust library.
|
5
|
+
It allows for the creation of a min-max-heap and supporting operations like pushing and popping multiple items, iterating over heap items, and converting heaps to arrays.
|
5
6
|
|
6
7
|
## Features
|
7
8
|
|
@@ -25,6 +26,7 @@ Add this line to your application's Gemfile:
|
|
25
26
|
gem 'min_max'
|
26
27
|
```
|
27
28
|
|
29
|
+
|
28
30
|
And then execute:
|
29
31
|
|
30
32
|
```bash
|
@@ -34,6 +36,10 @@ bundle install
|
|
34
36
|
Or install it yourself as:
|
35
37
|
```bash
|
36
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
|
37
43
|
```
|
38
44
|
|
39
45
|
## Usage
|
@@ -84,4 +90,13 @@ heap.length # Alias for size
|
|
84
90
|
heap.clear
|
85
91
|
```
|
86
92
|
|
93
|
+
## Count number of times an item is in the heap
|
94
|
+
```ruby
|
95
|
+
heap.count(item)
|
96
|
+
```
|
97
|
+
|
98
|
+
## Check if item is contained in the heap
|
99
|
+
```ruby
|
100
|
+
heap.contains?(item)
|
101
|
+
```
|
87
102
|
|
@@ -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_algos_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_algos_heap"){ 100_000.times{|d| minheap.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
@@ -1,5 +1,5 @@
|
|
1
1
|
use magnus::scan_args::scan_args;
|
2
|
-
use magnus::value::
|
2
|
+
use magnus::value::ReprValue;
|
3
3
|
use magnus::{
|
4
4
|
block::{block_given, Yield},
|
5
5
|
define_class, function, method,
|
@@ -9,32 +9,11 @@ use magnus::{
|
|
9
9
|
use magnus::{DataTypeFunctions, Integer, TypedData};
|
10
10
|
use min_max_heap::MinMaxHeap;
|
11
11
|
use std::cell::RefCell;
|
12
|
-
use std::
|
13
|
-
use std::sync::{Arc, RwLock};
|
12
|
+
use std::rc::Rc;
|
14
13
|
|
15
14
|
#[derive(Debug, Clone)]
|
16
15
|
struct PriorityOrderableValue(i64, i64);
|
17
16
|
|
18
|
-
#[macro_use]
|
19
|
-
extern crate lazy_static;
|
20
|
-
|
21
|
-
struct MEM(
|
22
|
-
RwLock<HashMap<i64, BoxValue<Value>>>,
|
23
|
-
RwLock<HashMap<i64, RefCell<usize>>>,
|
24
|
-
);
|
25
|
-
|
26
|
-
impl MEM {
|
27
|
-
fn new() -> Self {
|
28
|
-
MEM(RwLock::new(HashMap::new()), RwLock::new(HashMap::new()))
|
29
|
-
}
|
30
|
-
}
|
31
|
-
|
32
|
-
unsafe impl Sync for MEM {}
|
33
|
-
|
34
|
-
lazy_static! {
|
35
|
-
static ref MEMORY: MEM = MEM::new();
|
36
|
-
}
|
37
|
-
|
38
17
|
impl PartialOrd for PriorityOrderableValue {
|
39
18
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
40
19
|
Some(self.cmp(other))
|
@@ -43,9 +22,8 @@ impl PartialOrd for PriorityOrderableValue {
|
|
43
22
|
|
44
23
|
impl Ord for PriorityOrderableValue {
|
45
24
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
46
|
-
|
47
|
-
|
48
|
-
}
|
25
|
+
let (PriorityOrderableValue(p1, _), PriorityOrderableValue(p2, _)) = (self, other);
|
26
|
+
p1.cmp(p2)
|
49
27
|
}
|
50
28
|
}
|
51
29
|
|
@@ -53,11 +31,8 @@ impl Eq for PriorityOrderableValue {}
|
|
53
31
|
|
54
32
|
impl PartialEq for PriorityOrderableValue {
|
55
33
|
fn eq(&self, other: &Self) -> bool {
|
56
|
-
|
57
|
-
|
58
|
-
p1.eq(p2) && (v1).eq(v2)
|
59
|
-
}
|
60
|
-
}
|
34
|
+
let (PriorityOrderableValue(p1, v1), PriorityOrderableValue(p2, v2)) = (self, other);
|
35
|
+
p1.eq(p2) && (v1).eq(v2)
|
61
36
|
}
|
62
37
|
}
|
63
38
|
|
@@ -65,21 +40,21 @@ unsafe impl Send for RubyMinMaxHeap {}
|
|
65
40
|
#[derive(DataTypeFunctions, TypedData, Clone)]
|
66
41
|
#[magnus(class = "MinMax", size, free_immediately, mark)]
|
67
42
|
struct RubyMinMaxHeap {
|
68
|
-
heap:
|
43
|
+
heap: Rc<RefCell<MinMaxHeap<PriorityOrderableValue>>>,
|
69
44
|
}
|
70
45
|
|
71
46
|
impl RubyMinMaxHeap {
|
72
47
|
fn new() -> Result<Self, Error> {
|
73
48
|
Ok(RubyMinMaxHeap {
|
74
|
-
heap:
|
49
|
+
heap: Rc::new(RefCell::new(MinMaxHeap::new())),
|
75
50
|
})
|
76
51
|
}
|
77
52
|
|
78
53
|
fn push(&self, values: RArray) -> Result<(), Error> {
|
79
54
|
let mut hp = self.heap.borrow_mut();
|
80
|
-
let values_vec = values.to_vec::<(i64, i64)>()
|
81
|
-
values_vec.
|
82
|
-
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));
|
83
58
|
});
|
84
59
|
Ok(())
|
85
60
|
}
|
@@ -213,8 +188,8 @@ impl RubyMinMaxHeap {
|
|
213
188
|
Ok(ary)
|
214
189
|
}
|
215
190
|
|
216
|
-
fn clear(&self)
|
217
|
-
|
191
|
+
fn clear(&self) {
|
192
|
+
self.heap.borrow_mut().clear()
|
218
193
|
}
|
219
194
|
}
|
220
195
|
|
data/lib/min_max/version.rb
CHANGED
data/lib/min_max.rb
CHANGED
@@ -6,46 +6,35 @@ require_relative "min_max/min_max"
|
|
6
6
|
class MinMax
|
7
7
|
class Error < StandardError; end
|
8
8
|
|
9
|
+
attr_reader :priority_blk, :storage, :mtx
|
10
|
+
|
9
11
|
def self.[](*args, &blk)
|
10
12
|
new(*args, &blk)
|
11
13
|
end
|
12
14
|
|
13
15
|
def self.new(*args, &blk)
|
14
16
|
self._new.tap{|s|
|
15
|
-
s.
|
17
|
+
s.instance_eval{
|
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] }
|
20
|
+
@mtx ||= Mutex.new
|
21
|
+
}
|
16
22
|
s.push(*args)
|
17
23
|
}
|
18
24
|
end
|
19
25
|
|
20
|
-
def priority_blk=(bll)
|
21
|
-
@priority_blk = bll
|
22
|
-
end
|
23
|
-
|
24
|
-
def storage
|
25
|
-
@storage ||= Hash.new
|
26
|
-
end
|
27
|
-
|
28
|
-
def counts
|
29
|
-
@counts ||= Hash.new(0)
|
30
|
-
end
|
31
|
-
|
32
|
-
def mtx
|
33
|
-
@_mtx ||= Mutex.new
|
34
|
-
end
|
35
|
-
|
36
26
|
def push(*args)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
_push(mapped)
|
27
|
+
mapped = args.map do |a|
|
28
|
+
hash = a.hash
|
29
|
+
entry = self.storage[hash]
|
30
|
+
entry[0] += 1
|
31
|
+
entry[1] ||= a
|
32
|
+
[
|
33
|
+
(self.priority_blk.call(a) rescue 0),
|
34
|
+
hash
|
35
|
+
]
|
47
36
|
end
|
48
|
-
|
37
|
+
_push(mapped)
|
49
38
|
end
|
50
39
|
|
51
40
|
def add(*args)
|
@@ -53,29 +42,13 @@ class MinMax
|
|
53
42
|
end
|
54
43
|
|
55
44
|
def pop_max(*args)
|
56
|
-
|
57
|
-
|
58
|
-
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
59
|
-
}
|
45
|
+
popped = _pop_max(*args)
|
46
|
+
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
60
47
|
end
|
61
48
|
|
62
49
|
def pop_min(*args)
|
63
|
-
|
64
|
-
|
65
|
-
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
66
|
-
}
|
67
|
-
end
|
68
|
-
|
69
|
-
def retrieve(hash, remove=true)
|
70
|
-
if remove
|
71
|
-
if (counts[hash] -= 1) == 0
|
72
|
-
storage.delete(hash)
|
73
|
-
else
|
74
|
-
storage[hash]
|
75
|
-
end
|
76
|
-
else
|
77
|
-
storage[hash]
|
78
|
-
end
|
50
|
+
popped = _pop_min(*args)
|
51
|
+
popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
|
79
52
|
end
|
80
53
|
|
81
54
|
def peek_min(*args)
|
@@ -100,6 +73,14 @@ class MinMax
|
|
100
73
|
each.to_a
|
101
74
|
end
|
102
75
|
|
76
|
+
def count(val)
|
77
|
+
counts.has_key?(val.hash) ? counts[val.hash] : 0
|
78
|
+
end
|
79
|
+
|
80
|
+
def contains?(val)
|
81
|
+
counts.has_key?(val.hash) && counts[val.hash] > 0
|
82
|
+
end
|
83
|
+
|
103
84
|
def to_a_asc
|
104
85
|
mtx.synchronize { _to_a_asc.map{|p| retrieve(p, false) } }
|
105
86
|
end
|
@@ -109,7 +90,15 @@ class MinMax
|
|
109
90
|
end
|
110
91
|
|
111
92
|
def inspect
|
112
|
-
"MinMax[#{
|
93
|
+
"MinMax[#{_each.first(10).map{|v| retrieve(v, false).to_s }.join(", ")}#{size > 10 ? ", ..." : ""}]"
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def retrieve(hash, remove=true)
|
98
|
+
entry = self.storage[hash]
|
99
|
+
self.storage.delete(hash) if remove && (entry[0] -= 1) == 0
|
100
|
+
entry[1]
|
113
101
|
end
|
102
|
+
|
114
103
|
end
|
115
104
|
|
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.5
|
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
|