bihash 1.1.0 → 2.0.0

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
- SHA1:
3
- metadata.gz: 267f9af4575dbcca9216e1c780c6d5bd91f291be
4
- data.tar.gz: 249056135bb1a35e4f90c7b4f6453f317dfc410a
2
+ SHA256:
3
+ metadata.gz: 60d3d7134bedfa4c64127bac82258cf0ff907b370a79a6cbe1e49639ae3d1972
4
+ data.tar.gz: b379a5ce66b2d3b1532fa7515299a04627465eb9ec2fe3178d8824eba55bdf20
5
5
  SHA512:
6
- metadata.gz: 4fb2d4c5807667407e8feff5f189e52ee899900cb1c85d025ebb25b439d570f8f0b60530bfd1b13fa9bb45f8c2c1f0d49e9ae4ac4648af16421aedb24bb2b2e9
7
- data.tar.gz: dd4c378857235311be4573879a1aea3932e73ff55edf40052bbdb6e3c8c5ee2f63170677a88fb5eb5b0c84347e2cbb19bb9b58282a8c2099ab769456b292c568
6
+ metadata.gz: 419635498252cfa57afbaadb1034a284732ae9b2fa69d12b38848917587281b68ede9dfdc4f9341e48f9fb3209d0090316561a3b715d8e82cac2a1e1c02cfd81
7
+ data.tar.gz: 30f903147f6d270f8f2c9c275e6f12c33a87184b1fcf5ee4aa19dc7a313bb6e63e5d97a23d2cffad92ebd5425a80964b6017aa02b8215583226ca40d6eeb535d
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby-version: ['3.2', '3.3', '3.4', '4.0']
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Set up Ruby ${{ matrix.ruby-version }}
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{ matrix.ruby-version }}
22
+ bundler-cache: true
23
+ - name: Run tests
24
+ run: bundle exec rake spec
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
- [![Build Status](https://travis-ci.org/Cohen-Carlisle/bihash.svg?branch=master)](https://travis-ci.org/Cohen-Carlisle/bihash)
2
1
  # Bihash
3
2
 
4
- A simple gem that implements a bidrectional hash
3
+ A simple gem that implements a bidirectional hash
5
4
 
6
5
  ## Usage
7
6
 
data/bihash.gemspec CHANGED
@@ -1,5 +1,4 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'bihash/version'
5
4
 
@@ -10,15 +9,16 @@ Gem::Specification.new do |s|
10
9
  s.email = ['cohen.carlisle@gmail.com']
11
10
 
12
11
  s.summary = 'Bidirectional Hash'
13
- s.description = 'A simple gem that implements a bidrectional hash'
14
- s.homepage = 'http://rubygems.org/gems/bihash'
12
+ s.description = 'A simple gem that implements a bidirectional hash'
13
+ s.homepage = 'https://github.com/Cohen-Carlisle/bihash'
15
14
  s.license = 'MIT'
16
15
 
17
16
  s.files = `git ls-files`.split("\n")
18
17
  s.require_paths = ['lib']
19
18
 
20
- s.add_development_dependency 'bundler', '~> 1.11'
21
- s.add_development_dependency 'rake', '~> 10.0'
22
- s.add_development_dependency 'minitest', '~> 5.5'
23
- s.add_development_dependency 'pry', '~> 0.10'
19
+ s.required_ruby_version = '>= 3.2'
20
+
21
+ s.add_development_dependency 'rake', '~> 13.2'
22
+ s.add_development_dependency 'minitest', '~> 5.25'
23
+ s.add_development_dependency 'irb', '~> 1.14'
24
24
  end
data/bin/console CHANGED
@@ -2,5 +2,5 @@
2
2
  require 'bundler/setup'
3
3
  require 'bihash'
4
4
 
5
- require 'pry'
6
- Pry.start
5
+ require 'irb'
6
+ IRB.start
@@ -1,6 +1,20 @@
1
- require 'set'
2
-
3
1
  class Bihash
2
+ UNIMPLEMENTED_CLASS_METHODS = Set[
3
+ # a bihash is not converted to keyword args like Hash instances can be
4
+ 'ruby2_keywords_hash',
5
+ 'ruby2_keywords_hash?'
6
+ ]
7
+
8
+ def self.respond_to?(method, private = false)
9
+ UNIMPLEMENTED_CLASS_METHODS.include?(method.to_s) ? false : super
10
+ end
11
+
12
+ UNIMPLEMENTED_CLASS_METHODS.each do |method|
13
+ define_singleton_method(method) do |*|
14
+ raise NoMethodError, "Bihash::#{method} not implemented"
15
+ end
16
+ end
17
+
4
18
  UNIMPLEMENTED_METHODS = Set[
5
19
  # expected to only deal with half the hash: keys or values
6
20
  'keys',
@@ -13,15 +27,11 @@ class Bihash
13
27
  'transform_values!',
14
28
  # O(n) reverse lookups
15
29
  'key',
16
- 'index',
17
30
  'rassoc',
18
31
  'value?',
19
32
  'has_value?',
20
33
  # meaningless on bihash as both sides already hashed
21
- 'invert',
22
- # mass removal of nil, but a bihash can have only one pair containing nil
23
- 'compact',
24
- 'compact!'
34
+ 'invert'
25
35
  ]
26
36
 
27
37
  def respond_to?(method, private = false)
@@ -1,3 +1,3 @@
1
1
  class Bihash
2
- VERSION = '1.1.0'
2
+ VERSION = '2.0.0'
3
3
  end
data/lib/bihash.rb CHANGED
@@ -11,32 +11,38 @@ class Bihash
11
11
  end
12
12
 
13
13
  def self.try_convert(arg)
14
- h = Hash.try_convert(arg)
15
- h && new_from_hash(h)
14
+ if arg.is_a?(self)
15
+ arg
16
+ else
17
+ h = Hash.try_convert(arg)
18
+ h && self[h]
19
+ end
16
20
  end
17
21
 
18
22
  def <(rhs)
19
23
  raise_error_unless_bihash(rhs)
20
- merged_hash_attrs < rhs.send(:merged_hash_attrs)
24
+ size < rhs.size && subset?(rhs)
21
25
  end
22
26
 
23
27
  def <=(rhs)
24
28
  raise_error_unless_bihash(rhs)
25
- merged_hash_attrs <= rhs.send(:merged_hash_attrs)
29
+ size <= rhs.size && subset?(rhs)
26
30
  end
27
31
 
28
32
  def ==(rhs)
29
- rhs.is_a?(self.class) && merged_hash_attrs == rhs.send(:merged_hash_attrs)
33
+ rhs.is_a?(self.class) &&
34
+ size == rhs.size &&
35
+ merged_hash_attrs.eql?(rhs.send(:merged_hash_attrs))
30
36
  end
31
37
 
32
38
  def >(rhs)
33
39
  raise_error_unless_bihash(rhs)
34
- merged_hash_attrs > rhs.send(:merged_hash_attrs)
40
+ size > rhs.size && rhs.send(:subset?, self)
35
41
  end
36
42
 
37
43
  def >=(rhs)
38
44
  raise_error_unless_bihash(rhs)
39
- merged_hash_attrs >= rhs.send(:merged_hash_attrs)
45
+ size >= rhs.size && rhs.send(:subset?, self)
40
46
  end
41
47
 
42
48
  def [](key)
@@ -68,8 +74,19 @@ class Bihash
68
74
  self
69
75
  end
70
76
 
77
+ def compact
78
+ dup.tap { |d| d.compact! }
79
+ end
80
+
81
+ def compact!
82
+ reject! { |k1, k2| k1.nil? || k2.nil? }
83
+ end
84
+
71
85
  def compare_by_identity
72
86
  raise_error_if_frozen
87
+ if illegal_state?(compare_by_id: true)
88
+ raise 'Cannot set compare_by_identity while a key is duplicated outside its own pair'
89
+ end
73
90
  @forward.compare_by_identity
74
91
  @reverse.compare_by_identity
75
92
  self
@@ -110,6 +127,10 @@ class Bihash
110
127
  @default_proc = arg
111
128
  end
112
129
 
130
+ def deconstruct_keys(_keys)
131
+ merged_hash_attrs
132
+ end
133
+
113
134
  def delete(key)
114
135
  raise_error_if_frozen
115
136
  if @forward.key?(key)
@@ -129,12 +150,12 @@ class Bihash
129
150
  @forward.each { |k,v| delete(k) if yield(k,v) }
130
151
  self
131
152
  else
132
- to_enum(:delete_if)
153
+ to_enum(:delete_if) { size }
133
154
  end
134
155
  end
135
156
 
136
- def dig(*keys)
137
- (@forward.key?(keys[0]) ? @forward : @reverse).dig(*keys)
157
+ def dig(key, *rest)
158
+ rest.empty? ? self[key] : self[key]&.dig(*rest)
138
159
  end
139
160
 
140
161
  def each(&block)
@@ -142,7 +163,7 @@ class Bihash
142
163
  @forward.each(&block)
143
164
  self
144
165
  else
145
- to_enum(:each)
166
+ to_enum(:each) { size }
146
167
  end
147
168
  end
148
169
 
@@ -152,12 +173,39 @@ class Bihash
152
173
 
153
174
  alias :eql? :==
154
175
 
176
+ def except(*args)
177
+ dup_without_defaults.tap do |bh|
178
+ args.each do |arg|
179
+ bh.delete(arg)
180
+ end
181
+ end
182
+ end
183
+
155
184
  def fetch(key, *default, &block)
156
185
  (@forward.key?(key) ? @forward : @reverse).fetch(key, *default, &block)
157
186
  end
158
187
 
159
- def fetch_values(*keys)
160
- keys.map { |key| fetch(key) }
188
+ def fetch_values(*keys, &block)
189
+ keys.map { |key| fetch(key, &block) }
190
+ end
191
+
192
+ def filter(&block)
193
+ if block_given?
194
+ dup_without_defaults.tap { |d| d.select!(&block) }
195
+ else
196
+ to_enum(:select) { size }
197
+ end
198
+ end
199
+
200
+ def filter!(&block)
201
+ if block_given?
202
+ raise_error_if_frozen
203
+ old_size = size
204
+ keep_if(&block)
205
+ old_size == size ? nil : self
206
+ else
207
+ to_enum(:select!) { size }
208
+ end
161
209
  end
162
210
 
163
211
  def_delegator :@forward, :flatten
@@ -182,7 +230,7 @@ class Bihash
182
230
  @forward.each { |k,v| delete(k) unless yield(k,v) }
183
231
  self
184
232
  else
185
- to_enum(:keep_if)
233
+ to_enum(:keep_if) { size }
186
234
  end
187
235
  end
188
236
 
@@ -192,14 +240,18 @@ class Bihash
192
240
 
193
241
  alias :member? :has_key?
194
242
 
195
- def merge(other_bh)
196
- dup.merge!(other_bh)
243
+ def merge(*other_bhs)
244
+ dup.merge!(*other_bhs)
197
245
  end
198
246
 
199
- def merge!(other_bh)
247
+ def merge!(*other_bhs)
248
+ # NOTE: merge/merge!/update intentionally do not implement block support yet
249
+ # see https://github.com/Cohen-Carlisle/bihash/issues/17
200
250
  raise_error_if_frozen
201
- raise_error_unless_bihash(other_bh)
202
- other_bh.each { |k,v| store(k,v) }
251
+ other_bhs.each do |other_bh|
252
+ raise_error_unless_bihash(other_bh)
253
+ other_bh.each { |k,v| store(k,v) }
254
+ end
203
255
  self
204
256
  end
205
257
 
@@ -215,9 +267,9 @@ class Bihash
215
267
 
216
268
  def reject(&block)
217
269
  if block_given?
218
- dup.tap { |d| d.reject!(&block) }
270
+ dup_without_defaults.tap { |d| d.reject!(&block) }
219
271
  else
220
- to_enum(:reject)
272
+ to_enum(:reject) { size }
221
273
  end
222
274
  end
223
275
 
@@ -228,53 +280,37 @@ class Bihash
228
280
  delete_if(&block)
229
281
  old_size == size ? nil : self
230
282
  else
231
- to_enum(:reject!)
283
+ to_enum(:reject!) { size }
232
284
  end
233
285
  end
234
286
 
235
287
  def replace(other_bh)
236
288
  raise_error_if_frozen
237
289
  raise_error_unless_bihash(other_bh)
238
- @forward.replace(other_bh.instance_variable_get(:@forward))
239
- @reverse.replace(other_bh.instance_variable_get(:@reverse))
290
+ @forward = other_bh.instance_variable_get(:@forward).dup
291
+ @reverse = other_bh.instance_variable_get(:@reverse).dup
292
+ @default = other_bh.default
293
+ @default_proc = other_bh.default_proc
240
294
  self
241
295
  end
242
296
 
243
- def select(&block)
244
- if block_given?
245
- dup.tap { |d| d.select!(&block) }
246
- else
247
- to_enum(:select)
248
- end
249
- end
297
+ alias :select :filter
250
298
 
251
- def select!(&block)
252
- if block_given?
253
- raise_error_if_frozen
254
- old_size = size
255
- keep_if(&block)
256
- old_size == size ? nil : self
257
- else
258
- to_enum(:select!)
259
- end
260
- end
299
+ alias :select! :filter!
261
300
 
262
301
  def shift
263
302
  raise_error_if_frozen
264
- if empty?
265
- default_value(nil)
266
- else
267
- @reverse.shift
268
- @forward.shift
269
- end
303
+ @reverse.shift
304
+ @forward.shift
270
305
  end
271
306
 
272
307
  def_delegator :@forward, :size
273
308
 
274
309
  def slice(*args)
275
- self.class.new.tap do |bh|
310
+ self.class.new.tap do |new_bh|
311
+ new_bh.compare_by_identity if compare_by_identity?
276
312
  args.each do |arg|
277
- bh[arg] = self[arg] if key?(arg)
313
+ new_bh[arg] = self[arg] if key?(arg)
278
314
  end
279
315
  end
280
316
  end
@@ -282,10 +318,16 @@ class Bihash
282
318
  alias :store :[]=
283
319
 
284
320
  def to_h
285
- @forward.dup
321
+ if block_given?
322
+ @forward.to_h { |k,v| yield(k,v) }
323
+ else
324
+ @forward.dup
325
+ end
286
326
  end
287
327
 
288
- alias :to_hash :to_h
328
+ def to_hash
329
+ to_h
330
+ end
289
331
 
290
332
  def to_proc
291
333
  method(:[]).to_proc
@@ -302,6 +344,7 @@ class Bihash
302
344
  private
303
345
 
304
346
  def self.new_from_hash(h)
347
+ h = Hash[h.to_a] if h.compare_by_identity? && RUBY_VERSION.to_f < 3.3
305
348
  bihash = new
306
349
  bihash.instance_variable_set(:@reverse, h.invert)
307
350
  bihash.instance_variable_set(:@forward, h)
@@ -316,12 +359,22 @@ class Bihash
316
359
  @default_proc ? @default_proc.call(self, key) : @default
317
360
  end
318
361
 
319
- def illegal_state?
320
- fw = @forward
321
- (fw.keys | fw.values).size + fw.select { |k,v| k == v }.size < fw.size * 2
362
+ def dup_without_defaults
363
+ dup.tap { |bh| bh.default = nil }
364
+ end
365
+
366
+ def illegal_state?(compare_by_id: compare_by_identity?)
367
+ if compare_by_id
368
+ unique_members = (@forward.keys + @forward.values).uniq(&:object_id).count
369
+ duplicate_pairs = @forward.count { |k,v| k.equal?(v) }
370
+ else
371
+ unique_members = (@forward.keys | @forward.values).count
372
+ duplicate_pairs = @forward.count { |k,v| k.eql?(v) }
373
+ end
374
+ unique_members + duplicate_pairs < @forward.length * 2
322
375
  end
323
376
 
324
- def initialize(*args, &block)
377
+ def initialize(*args, capacity: 0, &block)
325
378
  raise_error_if_frozen
326
379
  if block_given? && !args.empty?
327
380
  raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
@@ -329,21 +382,22 @@ class Bihash
329
382
  raise ArgumentError, "wrong number of arguments (#{args.size} for 0..1)"
330
383
  end
331
384
  super()
332
- @forward, @reverse = {}, {}
385
+ if RUBY_VERSION.to_f < 3.4
386
+ @forward, @reverse = {}, {}
387
+ else
388
+ @forward, @reverse = Hash.new(capacity:), Hash.new(capacity:)
389
+ end
333
390
  @default, @default_proc = args[0], block
334
391
  end
335
392
 
336
- def initialize_copy(source)
337
- super
338
- @forward, @reverse = @forward.dup, @reverse.dup
339
- end
393
+ alias :initialize_copy :replace
340
394
 
341
395
  def merged_hash_attrs
342
396
  @reverse.merge(@forward)
343
397
  end
344
398
 
345
399
  def raise_error_if_frozen
346
- raise "can't modify frozen Bihash" if frozen?
400
+ raise FrozenError, "can't modify frozen Bihash" if frozen?
347
401
  end
348
402
 
349
403
  def raise_error_unless_bihash(obj)
@@ -351,4 +405,8 @@ class Bihash
351
405
  raise TypeError, "wrong argument type #{obj.class} (expected Bihash)"
352
406
  end
353
407
  end
408
+
409
+ def subset?(other_bh)
410
+ @forward.all? { |k,v| other_bh.key?(k) && other_bh[k].eql?(v) }
411
+ end
354
412
  end