weak 0.1.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.
data/lib/weak/set.rb ADDED
@@ -0,0 +1,749 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) Holger Just
4
+ #
5
+ # This software may be modified and distributed under the terms
6
+ # of the MIT license. See the LICENSE.txt file for details.
7
+
8
+ require "set"
9
+
10
+ require_relative "set/weak_keys_with_delete"
11
+ require_relative "set/weak_keys"
12
+ require_relative "set/strong_keys"
13
+ require_relative "set/strong_secondary_keys"
14
+
15
+ ##
16
+ module Weak
17
+ # This library provides the `Weak::Set` class. It behaves similar to the
18
+ # `::Set` class of the Ruby standard library, but all values are only weakly
19
+ # referenced. That way, all values can be garbage collected and silently
20
+ # removed from the set unless they are still referenced from some other live
21
+ # object.
22
+ #
23
+ # {Weak::Set} uses `ObjectSpace::WeakMap` as storage, so you must note the
24
+ # following points:
25
+ #
26
+ # - Equality of elements is determined strictly by their object identity
27
+ # instead of `Object#eql?` or `Object#hash` as the Set does by default.
28
+ # - Elements can be freely changed without affecting the set.
29
+ # - All elements can be freely garbage collected by Ruby. They will be removed
30
+ # from the set automatically.
31
+ # - The order of elements in the set is non-deterministic. Insertion order is
32
+ # not preserved.
33
+ #
34
+ # Note that {Weak::Set} is not inherently thread-safe. When accessing a
35
+ # {Weak::Set} from multiple threads or fibers, you MUST use a mutex or another
36
+ # locking mechanism.
37
+ #
38
+ # ## Implementation Details
39
+ #
40
+ # The various Ruby implementations and versions show quite diverse behavior in
41
+ # their respective `ObjectSpace::WeakMap` implementations. To provide a
42
+ # unified behavior on all implementations, we use different storage
43
+ # strategies:
44
+ #
45
+ # - Ruby (aka. MRI, aka. YARV) >= 3.3 has an `ObjectSpace::WeakMap` with weak
46
+ # keys and weak values and the ability to delete elements from it. This
47
+ # allows a straight-forward implementation in
48
+ # {Weak::Set::WeakKeysWithDelete}.
49
+ # - Ruby (aka. MRI, aka. YARV) < 3.3 has an `ObjectSpace::WeakMap` with weak
50
+ # keys and weak values but does not allow to directly delete entries. We
51
+ # emulate this with special garbage-collectible values in
52
+ # {Weak::Set::WeakKeys}.
53
+ # - JRuby >= 9.4.6.0 and TruffleRuby >= 22 have an `ObjectSpace::WeakMap` with
54
+ # strong keys and weak values. To allow a entries in an
55
+ # `ObjectSpace::WeakMap` to be garbage collected, we can't use the actual
56
+ # object as a key. Instead, we use the element's `object_id` as a key. As
57
+ # these `ObjectSpace::WeakMap` objects also do not allow to delete entries,
58
+ # we emulate deletion with special garbage-collectible values as above. This
59
+ # is implemented in {Weak::Set::StrongKeys}.
60
+ # - JRuby < 9.4.6.0 has a similar `ObjectSpace::WeakMap` as newer JRuby
61
+ # versions with strong keys and weak values. However generally in JRuby,
62
+ # Integer values (including object_ids) can have multiple different object
63
+ # representations in memory and are not necessarily equal to each other when
64
+ # used as keys in an `ObjectSpace::WeakMap`. As a workaround we use an
65
+ # indirect implementation with a secondary lookup table for the keys in
66
+ # {Weak::Set::StrongSecondaryKeys}.
67
+ #
68
+ # The required strategy is selected automatically based in the running
69
+ # Ruby. The external behavior is the same for all implementations.
70
+ #
71
+ # @example
72
+ # require "weak/set"
73
+ #
74
+ # s1 = Weak::Set[1, 2] #=> #<Weak::Set {1, 2}>
75
+ # s2 = Weak::Set.new [1, 2] #=> #<Weak::Set {1, 2}>
76
+ # s1 == s2 #=> true
77
+ # s1.add(:foo) #=> #<Weak::Set {1, 2, :foo}>
78
+ # s1.merge([2, 6]) #=> #<Weak::Set {1, 2, :foo, 6}>
79
+ # s1.subset?(s2) #=> false
80
+ # s2.subset?(s1) #=> true
81
+ class Set
82
+ include Enumerable
83
+
84
+ # We try to find the best implementation strategy based on the current Ruby
85
+ # engine and version. The chosen `STRATEGY` is included into the {Weak::Set}
86
+ # class.
87
+ STRATEGY = [
88
+ Weak::Set::WeakKeysWithDelete,
89
+ Weak::Set::WeakKeys,
90
+ Weak::Set::StrongKeys,
91
+ Weak::Set::StrongSecondaryKeys
92
+ ].find(&:usable?)
93
+
94
+ include STRATEGY
95
+
96
+ ############################################################################
97
+ # Here follows the documentation of strategy-specific methods which are
98
+ # implemented in one of the include modules depending on the current Ruby.
99
+
100
+ # @!macro _note_object_equality
101
+ # @note {Weak::Set} does not test member equality with `==` or `eql?`.
102
+ # Instead, it always checks strict object equality, so that, e.g.,
103
+ # different strings are not considered equal, even if they may contain
104
+ # the same string content.
105
+
106
+ # @!macro weak_set_method_add
107
+ # Adds the given object to the weak set and return `self`. Use {#merge} to
108
+ # add many elements at once.
109
+ #
110
+ # In contrast to other "regular" objects, we will not retain a strong
111
+ # reference to the added object. Unless some other live objects still
112
+ # references the object, it will eventually be garbage-collected.
113
+ #
114
+ # @param obj [Object] an object
115
+ # @return [self]
116
+ #
117
+ # @example
118
+ # Weak::Set[1, 2].add(3) #=> #<Weak::Set {1, 2, 3}>
119
+ # Weak::Set[1, 2].add([3, 4]) #=> #<Weak::Set {1, 2, [3, 4]}>
120
+ # Weak::Set[1, 2].add(2) #=> #<Weak::Set {1, 2}>
121
+
122
+ # @!macro weak_set_method_clear
123
+ # Removes all elements and returns `self`
124
+ #
125
+ # @return [self]
126
+
127
+ # @!macro weak_set_method_delete_question
128
+ # Deletes the given object from `self` and returns `self` if it was
129
+ # present in the set. If the object was not in the set, returns `nil`.
130
+ #
131
+ # @param obj [Object]
132
+ # @return [self, nil] `self` if the given object was deleted from the set
133
+ # or `nil` if the object was not part of the set
134
+ # @!macro _note_object_equality
135
+
136
+ # @!macro weak_set_method_each
137
+ # Calls the given block once for each live element in `self`, passing that
138
+ # element as a parameter. Returns the weak set itself.
139
+ #
140
+ # If no block is given, an `Enumerator` is returned instead.
141
+ #
142
+ # @yield [element] calls the given block once for each element in `self`
143
+ # @yieldparam element [Object] the yielded value
144
+ # @return [self, Enumerator] `self` if a block was given or an
145
+ # `Enumerator` if no block was given.
146
+
147
+ # @!macro weak_set_method_include_question
148
+ # @param obj [Object] an object
149
+ # @return [Bool] `true` if the given object is included in `self`, `false`
150
+ # otherwise
151
+ # @!macro _note_object_equality
152
+
153
+ # @!macro weak_set_method_prune
154
+ # Cleanup data structures from the set to remove data associated with
155
+ # deleted or garbage collected elements. This method may be called
156
+ # automatically for some {Weak::Set} operations.
157
+ #
158
+ # @return [self]
159
+
160
+ # @!macro weak_set_method_replace
161
+ # Replaces the contents of `self` with the contents of the given
162
+ # enumerable object and returns `self`.
163
+ #
164
+ # @param enum (see #do_with_enum)
165
+ # @return [self]
166
+ # @example
167
+ # set = Weak::Set[1, :c, :s] #=> #<Weak::Set {1, :c, :s}>
168
+ # set.replace([1, 2]) #=> #<Weak::Set {1, 2}>
169
+ # set #=> #<Weak::Set {1, 2}>
170
+
171
+ # @!macro weak_set_method_size
172
+ # @return [Integer] the number of live elements in `self`
173
+
174
+ # @!macro weak_set_method_to_a
175
+ # @return [Array] the live elements contained in `self` as an `Array`
176
+ # @note The order of elements on the returned `Array` is
177
+ # non-deterministic. We do not preserve preserve insertion order.
178
+
179
+ ############################################################################
180
+
181
+ # @!method add(obj)
182
+ # @!macro weak_set_method_add
183
+
184
+ # @!method clear
185
+ # @!macro weak_set_method_clear
186
+
187
+ # @!method delete?(obj)
188
+ # @!macro weak_set_method_delete_question
189
+
190
+ # @!method each
191
+ # @!macro weak_set_method_each
192
+
193
+ # @!method include?(obj)
194
+ # @!macro weak_set_method_include_question
195
+
196
+ # @!method prune
197
+ # @!macro weak_set_method_prune
198
+
199
+ # @!method size
200
+ # @!macro weak_set_method_size
201
+
202
+ # @!method replace(enum)
203
+ # @!macro weak_set_method_replace
204
+
205
+ # @!method to_a
206
+ # @!macro weak_set_method_to_a
207
+
208
+ ############################################################################
209
+
210
+ # The same value as `Set::InspectKey`. This is used as a key in
211
+ # `Thread.current` in {#inspect} to resolve object loops.
212
+ INSPECT_KEY = :__inspect_key__
213
+ private_constant :INSPECT_KEY
214
+
215
+ # @param ary [Array<Object>] a list of objects
216
+ # @return [Weak::Set] a new weak set containing the given objects
217
+ #
218
+ # @example
219
+ # Weak::Set[1, 2] # => #<Weak::Set {1, 2}>
220
+ # Weak::Set[1, 2, 1] # => #<Weak::Set {1, 2}>
221
+ # Weak::Set[1, :c, :s] # => #<Weak::Set {1, :c, :s}>
222
+ def self.[](*ary)
223
+ new(ary)
224
+ end
225
+
226
+ # @param enum (see #do_with_enum)
227
+ # @yield [element] calls the given block once for each element in `enum` and
228
+ # add the block's return value instead if the enum's value. Make sure to
229
+ # only return objects which are references somewhere else to avoid them
230
+ # being quickly garbage collected again.
231
+ # @yieldparam element [Object] the yielded value from the `enum`
232
+ def initialize(enum = nil)
233
+ clear
234
+
235
+ return if enum.nil?
236
+ if block_given?
237
+ do_with_enum(enum) do |obj|
238
+ add yield(obj)
239
+ end
240
+ else
241
+ do_with_enum(enum) do |obj|
242
+ add obj
243
+ end
244
+ end
245
+ end
246
+
247
+ alias_method :<<, :add
248
+ alias_method :===, :include?
249
+ alias_method :member?, :include?
250
+ alias_method :length, :size
251
+ alias_method :reset, :prune
252
+
253
+ # @param enum (see #do_with_enum)
254
+ # @return [Weak::Set] a new weak set built by merging `self` and the elements
255
+ # of the given enumerable object.
256
+ # @!macro _note_object_equality
257
+ #
258
+ # @example
259
+ # Weak::Set[1, 2, 3] | Weak::Set[2, 4, 5] # => #<Weak::Set {1, 2, 3, 4, 5}>
260
+ # Weak::Set[1, 3, :z] | (1..4) # => #<Weak::Set {1, 3, :z, 2, 4}>
261
+ def |(enum)
262
+ new_set = dup
263
+ do_with_enum(enum) do |obj|
264
+ new_set.add(obj)
265
+ end
266
+ new_set
267
+ end
268
+ alias_method :+, :|
269
+ alias_method :union, :|
270
+
271
+ # @param enum (see #do_with_enum)
272
+ # @return [Weak::Set] a new weak set built by duplicating `self`, removing
273
+ # every element that appears in the given enumerable object from that.
274
+ # @!macro _note_object_equality
275
+ #
276
+ # @example
277
+ # Weak::Set[1, 3, 5] - Weak::Set[1, 5] # => #<Weak::Set {3}>
278
+ # Weak::Set['a', 'b', 'z'] - ['a', 'c'] # => #<Weak::Set {"b", "z"}>
279
+ def -(enum)
280
+ dup.subtract(enum)
281
+ end
282
+ alias_method :difference, :-
283
+
284
+ # @param enum (see #do_with_enum g)
285
+ # @return [Weak::Set] a new weak set containing elements common to `self`
286
+ # and the given enumerable object.
287
+ # @!macro _note_object_equality
288
+ #
289
+ # @example
290
+ # Weak::Set[1, 3, 5] & Weak::Set[3, 2, 1] # => #<Weak::Set {3, 1}>
291
+ # Weak::Set[1, 2, 9] & [2, 1, 3] # => #<Weak::Set {1, 2}>
292
+ def &(enum)
293
+ new_set = self.class.new
294
+ do_with_enum(enum) do |obj|
295
+ new_set.add(obj) if include?(obj)
296
+ end
297
+ new_set
298
+ end
299
+ alias_method :intersection, :&
300
+
301
+ # @param other [Weak::Set] a weak set
302
+ # @return [Integer, nil] `0` if `self` and the given `set` contain the same
303
+ # elements, `-1` / `+1` if `self` is a proper subset / superset of the
304
+ # given `set`, or `nil` if they both have unique elements or `set` is not
305
+ # a {Weak::Set}
306
+ # @!macro _note_object_equality
307
+ def <=>(other)
308
+ return unless Weak::Set === other
309
+ return 0 if equal?(other)
310
+
311
+ other_ary = other.to_a
312
+ own_ary = to_a
313
+ case own_ary.size <=> other_ary.size
314
+ when -1
315
+ -1 if own_ary.all?(other)
316
+ when 1
317
+ 1 if other_ary.all?(self)
318
+ else
319
+ 0 if own_ary.all?(other)
320
+ end
321
+ end
322
+
323
+ # Returns true if two weak sets are equal. The equality of each couple
324
+ # of elements is defined according to strict object equality so that, e.g.,
325
+ # different strings are not equal, even if they may contain the same data.
326
+ #
327
+ # @param other [Weak::Set] a weak set to compare to `self`
328
+ # @return [Bool] `true` if the `other` object is a weak set containing
329
+ # exactly the same elements as `self`, `false` otherwise
330
+ #
331
+ # @example
332
+ # Weak::Set[1, 2] == Weak::Set[2, 1] #=> true
333
+ # Weak::Set[1, 3, 5] == Weak::Set[1, 5] #=> false
334
+ # Weak::Set[1, 2, 3] == [1, 3, 2] #=> false
335
+ def ==(other)
336
+ return true if equal?(other)
337
+ return false unless Weak::Set === other
338
+
339
+ other_ary = other.to_a
340
+ own_ary = to_a
341
+
342
+ return false unless own_ary.size == other_ary.size
343
+ own_ary.all?(other)
344
+ end
345
+
346
+ # Returns a new weak set containing elements exclusive between `self` and
347
+ # the given enumerable object. `(set ^ enum)` is equivalent to
348
+ # `((set | enum) - (set & enum))`.
349
+ #
350
+ # @param enum (see #do_with_enum)
351
+ # @return [Weak::Set] a new weak set
352
+ # @!macro _note_object_equality
353
+ #
354
+ # @example
355
+ # Weak::Set[1, 2] ^ Set[2, 3] #=> #<Weak::Set {3, 1}>
356
+ # Weak::Set[1, :b, :c] ^ [:b, :d] #=> #<Weak::Set {:d, 1, :c}>
357
+ def ^(enum)
358
+ return dup if enum.nil?
359
+
360
+ new_set = self.class.new.merge(enum)
361
+ each do |obj|
362
+ new_set.add(obj) unless new_set.delete?(obj)
363
+ end
364
+ new_set
365
+ end
366
+
367
+ # @param obj [Object] an object
368
+ # @return [Object, nil] the provided `obj` if it is included in `self`,
369
+ # `nil` otherwise
370
+ # @see #include?
371
+ # @!macro _note_object_equality
372
+ def [](obj)
373
+ obj if include?(obj)
374
+ end
375
+
376
+ # Adds the given object to the weak set and returns `self`. If the object is
377
+ # already in the set, returns `nil`.
378
+ #
379
+ # @param obj [Object] an object to add to the weak set
380
+ # @return [self, nil] `self` if the object was added, `nil` if it was part
381
+ # of the set already
382
+ # @!macro _note_object_equality
383
+ #
384
+ # @example
385
+ # Weak::Set[1, 2].add?(3) #=> #<Weak::Set {1, 2, 3}>
386
+ # Weak::Set[1, 2].add?([3, 4]) #=> #<Weak::Set {1, 2, [3, 4]}>
387
+ # Weak::Set[1, 2].add?(2) #=> nil
388
+ def add?(obj)
389
+ add(obj) unless include?(obj)
390
+ end
391
+
392
+ # {Weak::Set} objects can't be frozen since this is not enforced by the
393
+ # underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
394
+ # this by not actually setting the `frozen?` flag and ignoring attempts to
395
+ # freeze us with just a warning.
396
+ #
397
+ # @param freeze [Bool, nil] ignored; we always behave as if this is false.
398
+ # If this is set to a truethy value, we emit a warning.
399
+ # @return [Weak::Set] a new `Weak::Set` object containing the same elements
400
+ # as `self`
401
+ def clone(freeze: false)
402
+ warn("Can't freeze #{self.class}") if freeze
403
+
404
+ super(freeze: false)
405
+ end
406
+
407
+ # This method does nothing as we always compare elements by their object
408
+ # identity.
409
+ #
410
+ # @return [self]
411
+ def compare_by_identity
412
+ self
413
+ end
414
+
415
+ # @return [true] always `true` since we always compare elements by their
416
+ # object identity
417
+ def compare_by_identity?
418
+ true
419
+ end
420
+
421
+ # Deletes the given object from `self` and returns `self`. Use {#subtract}
422
+ # to delete many items at once.
423
+ #
424
+ # @param obj [Object] an object to delete from the weak set
425
+ # @return [self] always returns self
426
+ # @!macro _note_object_equality
427
+ def delete(obj)
428
+ delete?(obj)
429
+ self
430
+ end
431
+
432
+ # Deletes every element of the weak set for which the given block block
433
+ # evaluates to a truethy value, and returns `self`. Returns an `Enumerator`
434
+ # if no block is given.
435
+ #
436
+ # @yield [element] calls the given block once with each element. If the
437
+ # block returns a truethy value, the element is deleted from the set
438
+ # @yieldparam element [Object] a live element of the set
439
+ # @return [self, Enumerator] `self` or an `Enumerator` if no block was given
440
+ # @see #reject!
441
+ def delete_if(&block)
442
+ return enum_for(__method__) { size } unless block_given?
443
+
444
+ each do |obj|
445
+ delete?(obj) if yield(obj)
446
+ end
447
+ self
448
+ end
449
+
450
+ # @param enum (see #intersect)
451
+ # @return [Bool] `true` if `self` and the given `enum` have no element in
452
+ # common. This method is the opposite of {#intersect?}.
453
+ # @!macro _note_object_equality
454
+ def disjoint?(enum)
455
+ !intersect?(enum)
456
+ end
457
+
458
+ # @return [Boolean] `true` if `self` contains no elements
459
+ def empty?
460
+ size == 0
461
+ end
462
+
463
+ # {Weak::Set} objects can't be frozen since this is not enforced by the
464
+ # underlying `ObjectSpace::WeakMap` implementation. Thus, we try to signal
465
+ # this by not actually setting the `frozen?` flag and ignoring attempts to
466
+ # freeze us with just a warning.
467
+ #
468
+ # @return [self]
469
+ def freeze
470
+ warn("Can't freeze #{self.class}")
471
+ self
472
+ end
473
+
474
+ # @return [String] a string containing a human-readable representation of
475
+ # the weak set, e.g., `"#<Weak::Set {element1, element2, ...}>"`
476
+ def inspect
477
+ object_ids = (Thread.current[INSPECT_KEY] ||= [])
478
+ return "#<#{self.class} {...}>" if object_ids.include?(object_id)
479
+
480
+ object_ids << object_id
481
+ begin
482
+ elements = to_a.sort_by!(&:__id__).inspect[1..-2]
483
+ "#<#{self.class} {#{elements}}>"
484
+ ensure
485
+ object_ids.pop
486
+ end
487
+ end
488
+ alias_method :to_s, :inspect
489
+
490
+ # @param enum (see #enumerable)
491
+ # @return [Bool] `true` if `self` and the given enumerable object have at
492
+ # least one element in common, `false` otherwise
493
+ # @!macro _note_object_equality
494
+ #
495
+ # @example
496
+ # Weak::Set[1, 2, 3].intersect? Weak::Set[4, 5] #=> false
497
+ # Weak::Set[1, 2, 3].intersect? Weak::Set[3, 4] #=> true
498
+ # Weak::Set[1, 2, 3].intersect? 4..5 #=> false
499
+ # Weak::Set[1, 2, 3].intersect? [3, 4] #=> true
500
+ def intersect?(enum)
501
+ case enum
502
+ when Weak::Set
503
+ enum_ary = enum.to_a
504
+ own_ary = to_a
505
+
506
+ if own_ary.size < enum_ary.size
507
+ own_ary.any?(enum)
508
+ else
509
+ enum_ary.any?(self)
510
+ end
511
+ else
512
+ enumerable(enum).any?(self)
513
+ end
514
+ end
515
+
516
+ # Deletes every element from `self` for which the given block evaluates to
517
+ # a falsey value.
518
+ #
519
+ # If no block is given, an `Enumerator` is returned instead.
520
+ #
521
+ # @yield [element] calls the given block once for each element in the
522
+ # array
523
+ # @yieldparam element [Object] the element to check
524
+ # @return [Enumerator, self] `self` if a block was given, or an
525
+ # `Enumerator` if no block was given.
526
+ # @see select!
527
+ def keep_if(&block)
528
+ return enum_for(__method__) { size } unless block_given?
529
+
530
+ each do |obj|
531
+ delete?(obj) unless yield(obj)
532
+ end
533
+ self
534
+ end
535
+
536
+ # Merges the elements of the given enumerable objects to the set and returns
537
+ # `self`
538
+ #
539
+ # @param enums [Array<#each_entry, #each>] a list of enumerable objects
540
+ # @return [self]
541
+ def merge(*enums, **nil)
542
+ enums.each do |enum|
543
+ do_with_enum(enum) do |obj|
544
+ add(obj)
545
+ end
546
+ end
547
+ self
548
+ end
549
+
550
+ # @!visibility private
551
+ def pretty_print(pp)
552
+ pp.group(1, "#<#{self.class}", ">") do
553
+ pp.breakable
554
+ pp.group(1, "{", "}") do
555
+ pp.seplist(to_a.sort_by!(&:__id__)) do |obj|
556
+ pp.pp obj
557
+ end
558
+ end
559
+ end
560
+ end
561
+
562
+ # @!visibility private
563
+ def pretty_print_cycle(pp)
564
+ pp.text "#<#{self.class} {#{empty? ? "" : "..."}}>"
565
+ end
566
+
567
+ # @param other [Weak::Set] a weak set
568
+ # @return [Bool] `true` if `self` is a proper subset of the given `set`,
569
+ # `false` otherwise
570
+ # @see subset?
571
+ def proper_subset?(other)
572
+ if Weak::Set === other
573
+ other_ary = other.to_a
574
+ own_ary = to_a
575
+
576
+ return false unless own_ary.size < other_ary.size
577
+ own_ary.all?(other)
578
+ else
579
+ raise ArgumentError, "value must be a weak set"
580
+ end
581
+ end
582
+ alias_method :<, :proper_subset?
583
+
584
+ # @param other [Weak::Set] a weak set
585
+ # @return [Bool] `true` if `self` is a proper superset of the given `set`,
586
+ # `false` otherwise
587
+ # @!macro _note_object_equality
588
+ # @see superset?
589
+ def proper_superset?(other)
590
+ if Weak::Set === other
591
+ other_ary = other.to_a
592
+ own_ary = to_a
593
+
594
+ return false unless own_ary.size > other_ary.size
595
+ other_ary.all?(self)
596
+ else
597
+ raise ArgumentError, "value must be a weak set"
598
+ end
599
+ end
600
+ alias_method :>, :proper_superset?
601
+
602
+ # Deletes every live element from `self` for which the given block
603
+ # evaluates to a truethy value.
604
+ #
605
+ # Equivalent to {#delete_if}, but returns `nil` if no changes were made.
606
+ #
607
+ # If no block is given, an `Enumerator` is returned instead.
608
+ #
609
+ # @yield [element] calls the given block once for each live object in `self`
610
+ # @yieldparam element [Object] the element to check
611
+ # @return [Enumerator, self, nil] `self` if a block was given and some
612
+ # element(s) were deleted, `nil` if a block was given but no keys were
613
+ # deleted, or an `Enumerator` if no block was given.
614
+ # #see #delete_if
615
+ def reject!(&block)
616
+ return enum_for(__method__) { size } unless block_given?
617
+
618
+ deleted_anything = false
619
+ each do |obj|
620
+ deleted_anything = true if yield(obj) && delete?(obj)
621
+ end
622
+
623
+ self if deleted_anything
624
+ end
625
+
626
+ # Deletes every element from `self` for which the given block evaluates to
627
+ # a falsey value.
628
+ #
629
+ # Equivalent to {#keep_if}, but returns `nil` if no changes were made.
630
+ #
631
+ # If no block is given, an `Enumerator` is returned instead.
632
+ #
633
+ # @yield [element] calls the given block once for each element in the set
634
+ # @yieldparam element [Object] the element to check
635
+ # @return [Enumerator, self, nil] `self` if a block was given and some
636
+ # element(s) were deleted, `nil` if a block was given but nothing was
637
+ # deleted, or an `Enumerator` if no block was given.
638
+ # @see keep_if
639
+ def select!(&block)
640
+ return enum_for(__method__) { size } unless block_given?
641
+
642
+ deleted_anything = false
643
+ each do |obj|
644
+ deleted_anything = true if !yield(obj) && delete?(obj)
645
+ end
646
+
647
+ self if deleted_anything
648
+ end
649
+ alias_method :filter!, :select!
650
+
651
+ # @param other [Weak::Set] a weak set
652
+ # @return [Bool] `true` if `self` is a subset of the given `set`, `false`
653
+ # otherwise
654
+ # @!macro _note_object_equality
655
+ # @see proper_subset?
656
+ def subset?(other)
657
+ if Weak::Set === other
658
+ other_ary = other.to_a
659
+ own_ary = to_a
660
+
661
+ return false unless own_ary.size <= other_ary.size
662
+ own_ary.all?(other)
663
+ else
664
+ raise ArgumentError, "value must be a weak set"
665
+ end
666
+ end
667
+ alias_method :<=, :subset?
668
+
669
+ # Deletes every element from `self` which appears in the given enumerable
670
+ # object `enum` and returns `self`.
671
+ #
672
+ # @param enum (see #do_with_enum)
673
+ # @return [self]
674
+ def subtract(enum)
675
+ do_with_enum(enum) do |obj|
676
+ delete?(obj)
677
+ end
678
+ self
679
+ end
680
+
681
+ # @param other [Weak::Set] a weak set
682
+ # @return [Bool] `true` if `self` is a superset of the given `set`, `false`
683
+ # otherwise
684
+ # @see proper_superset?
685
+ def superset?(other)
686
+ if Weak::Set === other
687
+ other_ary = other.to_a
688
+ own_ary = to_a
689
+
690
+ return false unless own_ary.size >= other_ary.size
691
+ other_ary.all?(self)
692
+ else
693
+ raise ArgumentError, "value must be a weak set"
694
+ end
695
+ end
696
+ alias_method :>=, :superset?
697
+
698
+ # @return [Set] the elements in `self` as a regular `Set` with strong object
699
+ # references
700
+ # @note The returned set is configured to compare elements by their object
701
+ # identity, similar to a `Weak::Set`.
702
+ def to_set
703
+ set = ::Set.new.compare_by_identity
704
+ each do |obj|
705
+ set.add(obj)
706
+ end
707
+ set
708
+ end
709
+
710
+ private
711
+
712
+ # @param enum [Weak::Set, #each_entry #each] a {Weak::Set} or an enumerable
713
+ # object
714
+ # return [void]
715
+ # @raise [ArgumentError] if the given `enum` is not enumerable
716
+ def do_with_enum(enum, &block)
717
+ if Weak::Set === enum
718
+ enum.each(&block)
719
+ elsif enum.respond_to?(:each_entry)
720
+ enum.each_entry(&block)
721
+ elsif enum.respond_to?(:each)
722
+ enum.each(&block)
723
+ else
724
+ raise ArgumentError, "value must be enumerable"
725
+ end
726
+ end
727
+
728
+ # @param enum [Weak::Set, Enumerable, #each_entry, #each] a {Weak::Set}, or
729
+ # an Enumerable object, e.g. an `Array` or `Set`, or an object which
730
+ # responds to `each_entry` or `each`
731
+ # @return [Enumerable] an object which is an `Enumerable`
732
+ def enumerable(enum)
733
+ if Enumerable === enum
734
+ enum
735
+ elsif enum.respond_to?(:each_entry)
736
+ enum.enum_for(:each_entry)
737
+ elsif enum.respond_to?(:each)
738
+ enum.enum_for(:each)
739
+ else
740
+ raise ArgumentError, "value must be enumerable"
741
+ end
742
+ end
743
+
744
+ # Callback method which is called on the new object during `dup` or `clone`
745
+ def initialize_copy(orig)
746
+ initialize(orig)
747
+ end
748
+ end
749
+ end