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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/LICENSE.txt +21 -0
- data/README.md +232 -0
- data/lib/weak/map/abstract_strong_keys.rb +87 -0
- data/lib/weak/map/deletable.rb +65 -0
- data/lib/weak/map/strong_keys.rb +186 -0
- data/lib/weak/map/strong_secondary_keys.rb +229 -0
- data/lib/weak/map/weak_keys.rb +134 -0
- data/lib/weak/map/weak_keys_with_delete.rb +126 -0
- data/lib/weak/map.rb +714 -0
- data/lib/weak/set/strong_keys.rb +123 -0
- data/lib/weak/set/strong_secondary_keys.rb +154 -0
- data/lib/weak/set/weak_keys.rb +107 -0
- data/lib/weak/set/weak_keys_with_delete.rb +94 -0
- data/lib/weak/set.rb +749 -0
- data/lib/weak/version.rb +14 -0
- data/lib/weak.rb +45 -0
- metadata +65 -0
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
|