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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48b9b64396908ffd54a712e8a8e9b4eb6546e0538a6c168a69d67304dc6fcf1d
4
- data.tar.gz: 487b79da6a3a3f99ab408af1feb17ebe33c21c08b7c64b9e20e5b259bcf764bf
3
+ metadata.gz: 012d1e5c570db138f41f8ac8556dde89598878d15fc9176a7006bdd33e5deaf5
4
+ data.tar.gz: 70a8c3a7bb6afdaf23260ccd56c87acd1bd85e49d40d9231fbb316bf6beb4917
5
5
  SHA512:
6
- metadata.gz: '086026e3dfe0851390e4090e700aed60a27eaaf70ae53beb9a50164fd78a024797513994b2bd3c67995b29a655c6c8ca755b19ff42797e12190fae9bac10f4c6'
7
- data.tar.gz: b7cebaf1da79607d638628c1ef2d54ff11126902e9c1072d4c2bfd1db883db662d323c9e760134c76941747f447494141122e2a4dad6dd0ddc9a65682bfcfd9f
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.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
@@ -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
@@ -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)>().unwrap();
56
- values_vec.into_iter().for_each(|(priority, key)| {
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
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MinMax
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
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, :counts, :mtx
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| (x.respond_to?(:priority) ? x.priority : x.to_i ) rescue 0})
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
- mtx.synchronize do
29
- mapped = args.map do |a|
30
- self.counts[a.hash] += 1
31
- self.storage[a.hash] = a
32
- [
33
- self.priority_blk.call(a),
34
- a.hash
35
- ]
36
- end
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
- self
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
- mtx.synchronize {
48
- popped = _pop_max(*args)
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
- mtx.synchronize {
55
- popped = _pop_min(*args)
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(*args)
61
- peeked = _peek_min(*args)
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(*args)
66
- peeked = _peek_max(*args)
67
- peeked.kind_of?(Array) ? peeked.map{|p| retrieve(p, false) } : retrieve(popped, false)
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
- mtx.synchronize { _each(*args).map{|p| blk[retrieve(p, false)] } }
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
- mtx.synchronize { _to_a_asc.map{|p| retrieve(p, false) } }
92
+ _to_a_asc.map{|p| retrieve(p, false) }
92
93
  end
93
94
 
94
95
  def to_a_desc
95
- mtx.synchronize { _to_a_desc.map{|p| retrieve(p, false) } }
96
+ _to_a_desc.map{|p| retrieve(p, false) }
96
97
  end
97
98
 
98
99
  def inspect
99
- "MinMax[#{_each.first(10).map{|v| retrieve(v, false).to_s }.join(", ")}#{size > 10 ? ", ..." : ""}]"
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
- if remove
105
- if (self.counts[hash] -= 1) == 0
106
- self.storage.delete(hash)
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
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.4.19
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