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 +5 -5
- 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 +119 -61
- data/spec/bihash_spec.rb +744 -294
- metadata +14 -32
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
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,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(
|
|
196
|
-
dup.merge!(
|
|
243
|
+
def merge(*other_bhs)
|
|
244
|
+
dup.merge!(*other_bhs)
|
|
197
245
|
end
|
|
198
246
|
|
|
199
|
-
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
|
|
200
250
|
raise_error_if_frozen
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
|
239
|
-
@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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
-
|
|
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 |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|