bihash 1.2.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
2
  SHA256:
3
- metadata.gz: a83c5fe6e801a94f9e8166a1d7fbad9cf217ceb6350d7028c8378934c641a523
4
- data.tar.gz: daefa34c37020a266fc8f82e5acdd962b4f5a8db4dbf53ad61713be5cb6bc38f
3
+ metadata.gz: 60d3d7134bedfa4c64127bac82258cf0ff907b370a79a6cbe1e49639ae3d1972
4
+ data.tar.gz: b379a5ce66b2d3b1532fa7515299a04627465eb9ec2fe3178d8824eba55bdf20
5
5
  SHA512:
6
- metadata.gz: 39bb9c095ceccc8229c2646f2b1f44cba29c957f202c93ff45bd0fdeffd8b1d3e1f875cfd895aa1086c0e546a8b1304945de473710a45161724929f142e95161
7
- data.tar.gz: c93e9e0b8d0564921d52242874083c6b2d41b8c5b6d3dc7045e1d7013b2fbbeb4a1a913bda8f8231f6c281701dedab8b40adebb7d6129024408b449b27e99a47
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'
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.2.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,19 +173,27 @@ 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) }
161
190
  end
162
191
 
163
192
  def filter(&block)
164
193
  if block_given?
165
- dup.tap { |d| d.select!(&block) }
194
+ dup_without_defaults.tap { |d| d.select!(&block) }
166
195
  else
167
- to_enum(:select)
196
+ to_enum(:select) { size }
168
197
  end
169
198
  end
170
199
 
@@ -175,7 +204,7 @@ class Bihash
175
204
  keep_if(&block)
176
205
  old_size == size ? nil : self
177
206
  else
178
- to_enum(:select!)
207
+ to_enum(:select!) { size }
179
208
  end
180
209
  end
181
210
 
@@ -201,7 +230,7 @@ class Bihash
201
230
  @forward.each { |k,v| delete(k) unless yield(k,v) }
202
231
  self
203
232
  else
204
- to_enum(:keep_if)
233
+ to_enum(:keep_if) { size }
205
234
  end
206
235
  end
207
236
 
@@ -211,14 +240,18 @@ class Bihash
211
240
 
212
241
  alias :member? :has_key?
213
242
 
214
- def merge(other_bh)
215
- dup.merge!(other_bh)
243
+ def merge(*other_bhs)
244
+ dup.merge!(*other_bhs)
216
245
  end
217
246
 
218
- 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
219
250
  raise_error_if_frozen
220
- raise_error_unless_bihash(other_bh)
221
- 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
222
255
  self
223
256
  end
224
257
 
@@ -234,9 +267,9 @@ class Bihash
234
267
 
235
268
  def reject(&block)
236
269
  if block_given?
237
- dup.tap { |d| d.reject!(&block) }
270
+ dup_without_defaults.tap { |d| d.reject!(&block) }
238
271
  else
239
- to_enum(:reject)
272
+ to_enum(:reject) { size }
240
273
  end
241
274
  end
242
275
 
@@ -247,15 +280,17 @@ class Bihash
247
280
  delete_if(&block)
248
281
  old_size == size ? nil : self
249
282
  else
250
- to_enum(:reject!)
283
+ to_enum(:reject!) { size }
251
284
  end
252
285
  end
253
286
 
254
287
  def replace(other_bh)
255
288
  raise_error_if_frozen
256
289
  raise_error_unless_bihash(other_bh)
257
- @forward.replace(other_bh.instance_variable_get(:@forward))
258
- @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
259
294
  self
260
295
  end
261
296
 
@@ -265,20 +300,17 @@ class Bihash
265
300
 
266
301
  def shift
267
302
  raise_error_if_frozen
268
- if empty?
269
- default_value(nil)
270
- else
271
- @reverse.shift
272
- @forward.shift
273
- end
303
+ @reverse.shift
304
+ @forward.shift
274
305
  end
275
306
 
276
307
  def_delegator :@forward, :size
277
308
 
278
309
  def slice(*args)
279
- self.class.new.tap do |bh|
310
+ self.class.new.tap do |new_bh|
311
+ new_bh.compare_by_identity if compare_by_identity?
280
312
  args.each do |arg|
281
- bh[arg] = self[arg] if key?(arg)
313
+ new_bh[arg] = self[arg] if key?(arg)
282
314
  end
283
315
  end
284
316
  end
@@ -286,10 +318,16 @@ class Bihash
286
318
  alias :store :[]=
287
319
 
288
320
  def to_h
289
- @forward.dup
321
+ if block_given?
322
+ @forward.to_h { |k,v| yield(k,v) }
323
+ else
324
+ @forward.dup
325
+ end
290
326
  end
291
327
 
292
- alias :to_hash :to_h
328
+ def to_hash
329
+ to_h
330
+ end
293
331
 
294
332
  def to_proc
295
333
  method(:[]).to_proc
@@ -306,6 +344,7 @@ class Bihash
306
344
  private
307
345
 
308
346
  def self.new_from_hash(h)
347
+ h = Hash[h.to_a] if h.compare_by_identity? && RUBY_VERSION.to_f < 3.3
309
348
  bihash = new
310
349
  bihash.instance_variable_set(:@reverse, h.invert)
311
350
  bihash.instance_variable_set(:@forward, h)
@@ -320,12 +359,22 @@ class Bihash
320
359
  @default_proc ? @default_proc.call(self, key) : @default
321
360
  end
322
361
 
323
- def illegal_state?
324
- fw = @forward
325
- (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 }
326
364
  end
327
365
 
328
- def initialize(*args, &block)
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
375
+ end
376
+
377
+ def initialize(*args, capacity: 0, &block)
329
378
  raise_error_if_frozen
330
379
  if block_given? && !args.empty?
331
380
  raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
@@ -333,21 +382,22 @@ class Bihash
333
382
  raise ArgumentError, "wrong number of arguments (#{args.size} for 0..1)"
334
383
  end
335
384
  super()
336
- @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
337
390
  @default, @default_proc = args[0], block
338
391
  end
339
392
 
340
- def initialize_copy(source)
341
- super
342
- @forward, @reverse = @forward.dup, @reverse.dup
343
- end
393
+ alias :initialize_copy :replace
344
394
 
345
395
  def merged_hash_attrs
346
396
  @reverse.merge(@forward)
347
397
  end
348
398
 
349
399
  def raise_error_if_frozen
350
- raise "can't modify frozen Bihash" if frozen?
400
+ raise FrozenError, "can't modify frozen Bihash" if frozen?
351
401
  end
352
402
 
353
403
  def raise_error_unless_bihash(obj)
@@ -355,4 +405,8 @@ class Bihash
355
405
  raise TypeError, "wrong argument type #{obj.class} (expected Bihash)"
356
406
  end
357
407
  end
408
+
409
+ def subset?(other_bh)
410
+ @forward.all? { |k,v| other_bh.key?(k) && other_bh[k].eql?(v) }
411
+ end
358
412
  end