min_max 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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