min_max 0.1.2 → 0.1.4

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: aef5e878c1412daa7a16bf7aad72afff1bdcc4cfa37709e6a88601d6ef2f1a93
4
- data.tar.gz: 74f22abb3ad7a47e654cf6033d0512e00344a373e21aa18e46f86a9d38e6dc17
3
+ metadata.gz: 48b9b64396908ffd54a712e8a8e9b4eb6546e0538a6c168a69d67304dc6fcf1d
4
+ data.tar.gz: 487b79da6a3a3f99ab408af1feb17ebe33c21c08b7c64b9e20e5b259bcf764bf
5
5
  SHA512:
6
- metadata.gz: 0b992bc50cd6e4920503b8a9257916dc3cb83326126c9e56c38fb693bbf513335573f8c7730386109044de48f58dd6e8a9c1da7f4a6e91e9f28e2aeb981cd23c
7
- data.tar.gz: e5f84db084190697f2d8ab7738b70fc82ae2b4a23466936d47e74aef27e46dd39f1f2e5885b1a7d3f876f28aadc0dfc747dabffe36c05eaa3f4661f7424091bf
6
+ metadata.gz: '086026e3dfe0851390e4090e700aed60a27eaaf70ae53beb9a50164fd78a024797513994b2bd3c67995b29a655c6c8ca755b19ff42797e12190fae9bac10f4c6'
7
+ data.tar.gz: b7cebaf1da79607d638628c1ef2d54ff11126902e9c1072d4c2bfd1db883db662d323c9e760134c76941747f447494141122e2a4dad6dd0ddc9a65682bfcfd9f
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
 
@@ -23,8 +24,13 @@ Add this line to your application's Gemfile:
23
24
 
24
25
  ```ruby
25
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
26
31
  ```
27
32
 
33
+
28
34
  And then execute:
29
35
 
30
36
  ```bash
@@ -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
 
@@ -1,4 +1,3 @@
1
- use magnus::block::Proc;
2
1
  use magnus::scan_args::scan_args;
3
2
  use magnus::value::ReprValue;
4
3
  use magnus::{
@@ -7,73 +6,14 @@ use magnus::{
7
6
  prelude::*,
8
7
  Error, IntoValue, RArray, Ruby, Value,
9
8
  };
10
- use magnus::{eval, gc, DataTypeFunctions, RHash, RString, TypedData};
9
+ use magnus::{DataTypeFunctions, Integer, TypedData};
11
10
  use min_max_heap::MinMaxHeap;
12
11
  use std::cell::RefCell;
13
- use std::collections::HashMap;
14
- use std::sync::{Arc, RwLock};
12
+ use std::rc::Rc;
15
13
 
16
14
  #[derive(Debug, Clone)]
17
15
  struct PriorityOrderableValue(i64, i64);
18
16
 
19
- #[macro_use]
20
- extern crate lazy_static;
21
-
22
- struct MEM(
23
- RwLock<HashMap<i64, Box<Value>>>,
24
- RwLock<HashMap<i64, RefCell<usize>>>,
25
- );
26
-
27
- impl MEM {
28
- fn new() -> Self {
29
- MEM(RwLock::new(HashMap::new()), RwLock::new(HashMap::new()))
30
- }
31
- }
32
-
33
- unsafe impl Sync for MEM {}
34
-
35
- lazy_static! {
36
- static ref MEMORY: MEM = MEM::new();
37
- }
38
-
39
- fn get_rb_obj(key: i64) -> Option<Value> {
40
- let binding = (*MEMORY).0.read().unwrap();
41
- let v = binding.get(&key).unwrap();
42
- Some(**v)
43
- }
44
-
45
- fn pop_rb_obj(key: i64) -> Option<Value> {
46
- let binding = (*MEMORY).1.read().unwrap();
47
- let mut count = binding.get(&key).unwrap().borrow_mut();
48
- if *count == 0 {
49
- return None;
50
- }
51
- *count -= 1;
52
- let res = if *count == 0 {
53
- let val = (*MEMORY).0.write().unwrap().remove(&key).unwrap();
54
- gc::unregister_address(&*val);
55
- Some(*val)
56
- } else {
57
- let binding = (*MEMORY).0.read().unwrap();
58
- let v = binding.get(&key).unwrap();
59
- Some(**v)
60
- };
61
- res
62
- }
63
-
64
- fn push_rb_obj(key: i64, value: Value) -> i64 {
65
- let mut binding = (*MEMORY).1.write().unwrap();
66
- let mut count = binding.entry(key).or_insert(RefCell::new(0)).borrow_mut();
67
-
68
- *count += 1;
69
- if *count == 1 {
70
- let boxed = Box::new(value);
71
- gc::register_address(&*boxed);
72
- (*MEMORY).0.write().unwrap().insert(key, boxed);
73
- };
74
- return key;
75
- }
76
-
77
17
  impl PartialOrd for PriorityOrderableValue {
78
18
  fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
79
19
  Some(self.cmp(other))
@@ -82,9 +22,8 @@ impl PartialOrd for PriorityOrderableValue {
82
22
 
83
23
  impl Ord for PriorityOrderableValue {
84
24
  fn cmp(&self, other: &Self) -> std::cmp::Ordering {
85
- match (self, other) {
86
- (PriorityOrderableValue(p1, _), PriorityOrderableValue(p2, _)) => p1.cmp(p2),
87
- }
25
+ let (PriorityOrderableValue(p1, _), PriorityOrderableValue(p2, _)) = (self, other);
26
+ p1.cmp(p2)
88
27
  }
89
28
  }
90
29
 
@@ -92,13 +31,8 @@ impl Eq for PriorityOrderableValue {}
92
31
 
93
32
  impl PartialEq for PriorityOrderableValue {
94
33
  fn eq(&self, other: &Self) -> bool {
95
- match (self, other) {
96
- (PriorityOrderableValue(p1, v1), PriorityOrderableValue(p2, v2)) => {
97
- let v1_obj = get_rb_obj(*v1).unwrap();
98
- let v2_obj = get_rb_obj(*v2).unwrap();
99
- p1.eq(p2) && (v1_obj).eql(v2_obj).unwrap_or_default()
100
- }
101
- }
34
+ let (PriorityOrderableValue(p1, v1), PriorityOrderableValue(p2, v2)) = (self, other);
35
+ p1.eq(p2) && (v1).eq(v2)
102
36
  }
103
37
  }
104
38
 
@@ -106,50 +40,37 @@ unsafe impl Send for RubyMinMaxHeap {}
106
40
  #[derive(DataTypeFunctions, TypedData, Clone)]
107
41
  #[magnus(class = "MinMax", size, free_immediately, mark)]
108
42
  struct RubyMinMaxHeap {
109
- heap: Arc<RefCell<MinMaxHeap<PriorityOrderableValue>>>,
110
- priority_proc: Arc<Proc>,
43
+ heap: Rc<RefCell<MinMaxHeap<PriorityOrderableValue>>>,
111
44
  }
112
45
 
113
46
  impl RubyMinMaxHeap {
114
- fn new(args: &[Value]) -> Result<Self, Error> {
115
- let args = scan_args::<(), (), (), (), RHash, Option<Proc>>(args)?;
116
- let prc: Option<Proc> = args.block;
117
-
47
+ fn new() -> Result<Self, Error> {
118
48
  Ok(RubyMinMaxHeap {
119
- heap: Arc::new(RefCell::new(MinMaxHeap::new())),
120
- priority_proc: prc
121
- .unwrap_or_else(|| {
122
- let value: Value =
123
- eval("Proc.new {|x| x.respond_to?(:priority) ? x.priority : x.to_i }")
124
- .unwrap();
125
- Proc::from_value(value).unwrap()
126
- })
127
- .into(),
49
+ heap: Rc::new(RefCell::new(MinMaxHeap::new())),
128
50
  })
129
51
  }
130
52
 
131
- fn push(&self, value: &[Value]) -> Result<(), Error> {
53
+ fn push(&self, values: RArray) -> Result<(), Error> {
132
54
  let mut hp = self.heap.borrow_mut();
133
- value.iter().for_each(|v| {
134
- let priority: i64 = (*self.priority_proc).call([*v]).unwrap_or_default();
135
- let key = v.hash().unwrap().to_i64().unwrap();
136
- hp.push(PriorityOrderableValue(priority, push_rb_obj(key, *v)));
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));
137
58
  });
138
59
  Ok(())
139
60
  }
140
61
 
141
- fn peek_max(&self) -> Result<Option<Value>, Error> {
62
+ fn peek_max(&self) -> Result<Option<i64>, Error> {
142
63
  let hp = self.heap.borrow();
143
64
  match hp.peek_max() {
144
- Some(PriorityOrderableValue(_, value)) => Ok(get_rb_obj(*value)),
65
+ Some(PriorityOrderableValue(_, value)) => Ok(Some(*value)),
145
66
  _ => Ok(None),
146
67
  }
147
68
  }
148
69
 
149
- fn peek_min(&self) -> Result<Option<Value>, Error> {
70
+ fn peek_min(&self) -> Result<Option<i64>, Error> {
150
71
  let hp = self.heap.borrow();
151
72
  match hp.peek_min() {
152
- Some(PriorityOrderableValue(_, value)) => Ok(get_rb_obj(*value)),
73
+ Some(PriorityOrderableValue(_, value)) => Ok(Some(*value)),
153
74
  _ => Ok(None),
154
75
  }
155
76
  }
@@ -162,20 +83,20 @@ impl RubyMinMaxHeap {
162
83
  let mut result = vec![];
163
84
  for _ in 0..c {
164
85
  match { self.heap.borrow_mut() }.pop_max() {
165
- Some(PriorityOrderableValue(_, value)) => {
166
- result.push(pop_rb_obj(value).unwrap())
167
- }
86
+ Some(PriorityOrderableValue(_, value)) => result.push(Integer::from_i64(value)),
168
87
  _ => break,
169
88
  }
170
89
  }
171
90
  let ary = RArray::new();
172
91
  ary.cat(&result)?;
173
- Ok(Some(ary.into_value()))
92
+ Ok(Some(ary.as_value()))
174
93
  } else {
175
94
  let mut hp = self.heap.borrow_mut();
176
95
  let val = hp.pop_max();
177
96
  match val {
178
- Some(PriorityOrderableValue(_, value)) => Ok(pop_rb_obj(value)),
97
+ Some(PriorityOrderableValue(_, value)) => {
98
+ Ok(Some(Integer::from_i64(value).as_value()))
99
+ }
179
100
  _ => Ok(None),
180
101
  }
181
102
  }
@@ -188,19 +109,21 @@ impl RubyMinMaxHeap {
188
109
  if let Some(c) = count {
189
110
  let mut result = vec![];
190
111
  for _ in 0..c {
191
- match { self.heap.borrow_mut().pop_min() } {
192
- Some(PriorityOrderableValue(_, value)) => {
193
- result.push(pop_rb_obj(value).unwrap())
194
- }
112
+ match { self.heap.borrow_mut() }.pop_min() {
113
+ Some(PriorityOrderableValue(_, value)) => result.push(Integer::from_i64(value)),
195
114
  _ => break,
196
115
  }
197
116
  }
198
117
  let ary = RArray::new();
199
118
  ary.cat(&result)?;
200
- Ok(Some(ary.into_value()))
119
+ Ok(Some(ary.as_value()))
201
120
  } else {
202
- match { self.heap.borrow_mut().pop_min() } {
203
- Some(PriorityOrderableValue(_, value)) => Ok(pop_rb_obj(value)),
121
+ let mut hp = self.heap.borrow_mut();
122
+ let val = hp.pop_min();
123
+ match val {
124
+ Some(PriorityOrderableValue(_, value)) => {
125
+ Ok(Some(Integer::from_i64(value).as_value()))
126
+ }
204
127
  _ => Ok(None),
205
128
  }
206
129
  }
@@ -223,45 +146,14 @@ impl RubyMinMaxHeap {
223
146
  .into_vec()
224
147
  .into_iter()
225
148
  .map(|orderable_value| match orderable_value {
226
- PriorityOrderableValue(_, value) => get_rb_obj(value).unwrap(),
149
+ PriorityOrderableValue(_, value) => Integer::from_i64(value).as_value(),
227
150
  });
228
151
  Yield::Iter(Box::new(iter))
229
152
  } else {
230
- Yield::Enumerator(self.clone().into_value().enumeratorize("each", ()))
153
+ Yield::Enumerator(self.clone().into_value().enumeratorize("_each", ()))
231
154
  }
232
155
  }
233
156
 
234
- fn to_a(&self) -> Result<RArray, Error> {
235
- let each: Value = self.clone().into_value().funcall("each", ())?;
236
- Ok(each.funcall("to_a", ())?)
237
- }
238
-
239
- fn inspect(&self) -> Result<RString, Error> {
240
- let mut visited = std::collections::HashSet::new();
241
- Ok(self.build_inspect(&mut visited)?)
242
- }
243
-
244
- fn build_inspect(
245
- &self,
246
- visited: &mut std::collections::HashSet<*const Self>,
247
- ) -> Result<RString, Error> {
248
- // Check for circular reference
249
- if !visited.insert(self as *const Self) {
250
- return Ok(RString::new("[Circular Reference Detected]"));
251
- }
252
-
253
- let to_a: Value = self.to_a()?.into_value();
254
- let r: String = match to_a.funcall("inspect", ()) {
255
- Ok(inspect_value) => inspect_value,
256
- Err(_) => return Ok(RString::new("[Error inspecting array]")),
257
- };
258
-
259
- // Ensure the current object is removed from the visited set to allow re-visiting in future calls
260
- visited.remove(&(self as *const Self));
261
-
262
- Ok(RString::new(&format!("MinMax{}", r)))
263
- }
264
-
265
157
  fn to_a_asc(&self) -> Result<RArray, Error> {
266
158
  let ary = RArray::new();
267
159
 
@@ -272,7 +164,7 @@ impl RubyMinMaxHeap {
272
164
  .into_vec_asc()
273
165
  .iter()
274
166
  .map(|orderable_value| match orderable_value {
275
- PriorityOrderableValue(_, value) => get_rb_obj(*value).unwrap(),
167
+ PriorityOrderableValue(_, value) => Integer::from_i64(*value).as_value(),
276
168
  })
277
169
  .collect();
278
170
  ary.cat(&sorted)?;
@@ -289,35 +181,33 @@ impl RubyMinMaxHeap {
289
181
  .into_vec_desc()
290
182
  .iter()
291
183
  .map(|orderable_value| match orderable_value {
292
- PriorityOrderableValue(_, value) => get_rb_obj(*value).unwrap(),
184
+ PriorityOrderableValue(_, value) => Integer::from_i64(*value).as_value(),
293
185
  })
294
186
  .collect();
295
187
  ary.cat(&sorted)?;
296
188
  Ok(ary)
297
189
  }
298
190
 
299
- fn clear(&self) -> Option<()> {
300
- Some(self.heap.borrow_mut().clear())
191
+ fn clear(&self) {
192
+ self.heap.borrow_mut().clear()
301
193
  }
302
194
  }
303
195
 
304
196
  #[magnus::init]
305
197
  fn init(ruby: &Ruby) -> Result<(), Error> {
306
198
  let rb_c_min_max = define_class("MinMax", ruby.class_object())?;
307
- rb_c_min_max.define_singleton_method("new", function!(RubyMinMaxHeap::new, -1))?;
308
- rb_c_min_max.define_method("_push", method!(RubyMinMaxHeap::push, -1))?;
199
+ rb_c_min_max.define_singleton_method("_new", function!(RubyMinMaxHeap::new, 0))?;
200
+ rb_c_min_max.define_method("_push", method!(RubyMinMaxHeap::push, 1))?;
309
201
  rb_c_min_max.define_method("_pop_max", method!(RubyMinMaxHeap::pop_max, -1))?;
310
202
  rb_c_min_max.define_method("_pop_min", method!(RubyMinMaxHeap::pop_min, -1))?;
311
203
  rb_c_min_max.define_method("empty?", method!(RubyMinMaxHeap::is_empty, 0))?;
312
- rb_c_min_max.define_method("each", method!(RubyMinMaxHeap::each, 0))?;
313
- rb_c_min_max.define_method("peek_min", method!(RubyMinMaxHeap::peek_min, 0))?;
314
- rb_c_min_max.define_method("peek_max", method!(RubyMinMaxHeap::peek_max, 0))?;
315
- rb_c_min_max.define_method("to_a", method!(RubyMinMaxHeap::to_a, 0))?;
316
- rb_c_min_max.define_method("to_a_asc", method!(RubyMinMaxHeap::to_a_asc, 0))?;
317
- rb_c_min_max.define_method("to_a_desc", method!(RubyMinMaxHeap::to_a_desc, 0))?;
204
+ rb_c_min_max.define_method("_each", method!(RubyMinMaxHeap::each, 0))?;
205
+ rb_c_min_max.define_method("_peek_min", method!(RubyMinMaxHeap::peek_min, 0))?;
206
+ rb_c_min_max.define_method("_peek_max", method!(RubyMinMaxHeap::peek_max, 0))?;
207
+ rb_c_min_max.define_method("_to_a_asc", method!(RubyMinMaxHeap::to_a_asc, 0))?;
208
+ rb_c_min_max.define_method("_to_a_desc", method!(RubyMinMaxHeap::to_a_desc, 0))?;
318
209
  rb_c_min_max.define_method("clear", method!(RubyMinMaxHeap::clear, 0))?;
319
210
  rb_c_min_max.define_method("size", method!(RubyMinMaxHeap::size, 0))?;
320
211
  rb_c_min_max.define_method("length", method!(RubyMinMaxHeap::size, 0))?;
321
- rb_c_min_max.define_method("inspect", method!(RubyMinMaxHeap::inspect, 0))?;
322
212
  Ok(())
323
213
  }
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class MinMax
4
- VERSION = "0.1.2"
5
-
4
+ VERSION = "0.1.4"
6
5
  end
data/lib/min_max.rb CHANGED
@@ -6,28 +6,111 @@ 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
10
+
9
11
  def self.[](*args, &blk)
10
- new(&blk).tap{|s| s.push(*args) }
12
+ new(*args, &blk)
11
13
  end
12
14
 
13
- def mtx
14
- @_mtx ||= Mutex.new
15
+ def self.new(*args, &blk)
16
+ self._new.tap{|s|
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
22
+ }
23
+ s.push(*args)
24
+ }
15
25
  end
16
26
 
17
27
  def push(*args)
18
- mtx.synchronize { _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)
38
+ end
39
+ self
19
40
  end
20
41
 
21
42
  def add(*args)
22
- mtx.synchronize { _push(*args) }
43
+ push(*args)
23
44
  end
24
45
 
25
46
  def pop_max(*args)
26
- mtx.synchronize { _pop_max(*args) }
47
+ mtx.synchronize {
48
+ popped = _pop_max(*args)
49
+ popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
50
+ }
27
51
  end
28
52
 
29
53
  def pop_min(*args)
30
- mtx.synchronize { _pop_min(*args) }
54
+ mtx.synchronize {
55
+ popped = _pop_min(*args)
56
+ popped.kind_of?(Array) ? popped.map{|p| retrieve(p) } : retrieve(popped)
57
+ }
58
+ end
59
+
60
+ def peek_min(*args)
61
+ peeked = _peek_min(*args)
62
+ peeked.kind_of?(Array) ? peeked.map{|p| retrieve(p, false) } : retrieve(popped, false)
63
+ end
64
+
65
+ def peek_max(*args)
66
+ peeked = _peek_max(*args)
67
+ peeked.kind_of?(Array) ? peeked.map{|p| retrieve(p, false) } : retrieve(popped, false)
68
+ end
69
+
70
+ def each(*args, &blk)
71
+ if block_given?
72
+ mtx.synchronize { _each(*args).map{|p| blk[retrieve(p, false)] } }
73
+ else
74
+ to_enum(:each, *args)
75
+ end
76
+ end
77
+
78
+ def to_a
79
+ each.to_a
80
+ end
81
+
82
+ def count(val)
83
+ counts.has_key?(val.hash) ? counts[val.hash] : 0
31
84
  end
85
+
86
+ def contains?(val)
87
+ counts.has_key?(val.hash) && counts[val.hash] > 0
88
+ end
89
+
90
+ def to_a_asc
91
+ mtx.synchronize { _to_a_asc.map{|p| retrieve(p, false) } }
92
+ end
93
+
94
+ def to_a_desc
95
+ mtx.synchronize { _to_a_desc.map{|p| retrieve(p, false) } }
96
+ end
97
+
98
+ def inspect
99
+ "MinMax[#{_each.first(10).map{|v| retrieve(v, false).to_s }.join(", ")}#{size > 10 ? ", ..." : ""}]"
100
+ end
101
+
102
+ private
103
+ 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
113
+ end
114
+
32
115
  end
33
116
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: min_max
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter Coppieters
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-24 00:00:00.000000000 Z
11
+ date: 2024-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rb_sys