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 +4 -4
- data/.github/workflows/ci.yml +24 -0
- data/README.md +1 -2
- data/bihash.gemspec +8 -8
- data/bin/console +2 -2
- data/lib/bihash/unimplemented_methods.rb +17 -7
- data/lib/bihash/version.rb +1 -1
- data/lib/bihash.rb +101 -47
- data/spec/bihash_spec.rb +736 -296
- metadata +15 -32
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60d3d7134bedfa4c64127bac82258cf0ff907b370a79a6cbe1e49639ae3d1972
|
|
4
|
+
data.tar.gz: b379a5ce66b2d3b1532fa7515299a04627465eb9ec2fe3178d8824eba55bdf20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
data/bihash.gemspec
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
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
|
|
14
|
-
s.homepage = '
|
|
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.
|
|
21
|
-
|
|
22
|
-
s.add_development_dependency '
|
|
23
|
-
s.add_development_dependency '
|
|
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
|
@@ -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)
|
data/lib/bihash/version.rb
CHANGED
data/lib/bihash.rb
CHANGED
|
@@ -11,32 +11,38 @@ class Bihash
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def self.try_convert(arg)
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
24
|
+
size < rhs.size && subset?(rhs)
|
|
21
25
|
end
|
|
22
26
|
|
|
23
27
|
def <=(rhs)
|
|
24
28
|
raise_error_unless_bihash(rhs)
|
|
25
|
-
|
|
29
|
+
size <= rhs.size && subset?(rhs)
|
|
26
30
|
end
|
|
27
31
|
|
|
28
32
|
def ==(rhs)
|
|
29
|
-
rhs.is_a?(self.class) &&
|
|
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
|
-
|
|
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
|
-
|
|
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(*
|
|
137
|
-
|
|
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
|
-
|
|
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(
|
|
215
|
-
dup.merge!(
|
|
243
|
+
def merge(*other_bhs)
|
|
244
|
+
dup.merge!(*other_bhs)
|
|
216
245
|
end
|
|
217
246
|
|
|
218
|
-
def merge!(
|
|
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
|
-
|
|
221
|
-
|
|
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
|
-
|
|
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
|
|
258
|
-
@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
|
-
|
|
269
|
-
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
324
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|