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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b1020f3ac8e632e3b2b4d99318a78edf48a06d3099e5812637d8dfd5fdf1623
4
- data.tar.gz: b774055ca657edf4a01712a71020cbb55df034e38f3db676b0c85484ae1f155e
3
+ metadata.gz: 6d739946dcb0939e2e05893222c927f4dea0eda65da5c0a00156402de3e49c29
4
+ data.tar.gz: 61b4453a94b93bb2ccefd0787a751cef5d3ef380ac0ed591c73c3b0afddc394b
5
5
  SHA512:
6
- metadata.gz: 2c7a1e0c3893382526f5c94c488618a3d01b7e19aa3a6d1b3cfc0aa012ede494bfb033c1b9544224bcfa3f9147d3d96779d11c83f2723a384c36109c5f837dba
7
- data.tar.gz: 4c60344475780f72a7369cac4fc0a22e156750fee690b326a2cbc9b6d1c492446f05dbdda5e39910e9f9549b848730ade850fda8e282d418ba1fa0e8e8c19793
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.1.0"
273
+ version = "1.3.0"
274
274
  source = "registry+https://github.com/rust-lang/crates.io-index"
275
- checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
275
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
276
276
 
277
277
  [[package]]
278
278
  name = "syn"
data/Cargo.toml CHANGED
@@ -8,5 +8,6 @@ resolver = "2"
8
8
 
9
9
  [profile.release]
10
10
  opt-level = 3
11
- lto = "fat"
11
+ lto = "thin"
12
12
  codegen-units = 1
13
+ strip = "debuginfo"
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
- This gem 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.
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
@@ -1,5 +1,5 @@
1
1
  use magnus::scan_args::scan_args;
2
- use magnus::value::{BoxValue, ReprValue};
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::collections::HashMap;
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
- match (self, other) {
47
- (PriorityOrderableValue(p1, _), PriorityOrderableValue(p2, _)) => p1.cmp(p2),
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
- match (self, other) {
57
- (PriorityOrderableValue(p1, v1), PriorityOrderableValue(p2, v2)) => {
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: Arc<RefCell<MinMaxHeap<PriorityOrderableValue>>>,
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: Arc::new(RefCell::new(MinMaxHeap::new())),
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)>().unwrap();
81
- values_vec.into_iter().for_each(|(priority, key)| {
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) -> Option<()> {
217
- Some(self.heap.borrow_mut().clear())
191
+ fn clear(&self) {
192
+ self.heap.borrow_mut().clear()
218
193
  }
219
194
  }
220
195
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MinMax
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
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.priority_blk =(blk || proc{|x| (x.respond_to?(:priority) ? x.priority : x.to_i ) rescue 0})
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
- mtx.synchronize do
38
- mapped = args.map do |a|
39
- counts[a.hash] += 1
40
- storage[a.hash] = a
41
- [
42
- @priority_blk.call(a),
43
- a.hash
44
- ]
45
- end
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
- self
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
- mtx.synchronize {
57
- popped = _pop_max(*args)
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
- mtx.synchronize {
64
- popped = _pop_min(*args)
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[#{to_a_asc.first(10).map{|v| v.to_s}.join(", ")}#{size > 10 ? ", ..." : ""}]"
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.3
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