Class: Weak::Set
- Inherits:
-
Object
- Object
- Weak::Set
- Includes:
- Enumerable, [ Weak[ Weak::Set[ Weak::Set::WeakKeysWithDelete, Weak::Set::WeakKeys, Weak::Set::StrongKeys, Weak::Set::StrongSecondaryKeys ].find(&:usable?)
- Defined in:
- lib/weak/set.rb,
lib/weak/set/weak_keys.rb,
lib/weak/set/strong_keys.rb,
lib/weak/set/strong_secondary_keys.rb,
lib/weak/set/weak_keys_with_delete.rb more...
Overview
This library provides the ‘Weak::Set` class. It behaves similar to the `::Set` class of the Ruby standard library, but all values are only weakly referenced. That way, all values can be garbage collected and silently removed from the set unless they are still referenced from some other live object.
Set uses ‘ObjectSpace::WeakMap` as storage, so you must note the following points:
-
Equality of elements is determined strictly by their object identity instead of ‘Object#eql?` or `Object#hash` as the Set does by default.
-
Elements can be freely changed without affecting the set.
-
All elements can be freely garbage collected by Ruby. They will be removed from the set automatically.
-
The order of elements in the set is non-deterministic. Insertion order is not preserved.
Note that Set is not inherently thread-safe. When accessing a Set from multiple threads or fibers, you MUST use a mutex or another locking mechanism.
## Implementation Details
The various Ruby implementations and versions show quite diverse behavior in their respective ‘ObjectSpace::WeakMap` implementations. To provide a unified behavior on all implementations, we use different storage strategies:
-
Ruby (aka. MRI, aka. YARV) >= 3.3 has an ‘ObjectSpace::WeakMap` with weak keys and weak values and the ability to delete elements from it. This allows a straight-forward implementation in WeakKeysWithDelete.
-
Ruby (aka. MRI, aka. YARV) < 3.3 has an ‘ObjectSpace::WeakMap` with weak keys and weak values but does not allow to directly delete entries. We emulate this with special garbage-collectible values in WeakKeys.
-
JRuby >= 9.4.6.0 and TruffleRuby >= 22 have an ‘ObjectSpace::WeakMap` with strong keys and weak values. To allow a entries in an `ObjectSpace::WeakMap` to be garbage collected, we can’t use the actual object as a key. Instead, we use the element’s ‘object_id` as a key. As these `ObjectSpace::WeakMap` objects also do not allow to delete entries, we emulate deletion with special garbage-collectible values as above. This is implemented in StrongKeys.
-
JRuby < 9.4.6.0 has a similar ‘ObjectSpace::WeakMap` as newer JRuby versions with strong keys and weak values. However generally in JRuby, Integer values (including object_ids) can have multiple different object representations in memory and are not necessarily equal to each other when used as keys in an `ObjectSpace::WeakMap`. As a workaround we use an indirect implementation with a secondary lookup table for the keys in StrongSecondaryKeys.
The required strategy is selected automatically based in the running Ruby. The external behavior is the same for all implementations.
Defined Under Namespace
Modules: StrongKeys, StrongSecondaryKeys, WeakKeys, WeakKeysWithDelete
Constant Summary collapse
- STRATEGY =
We try to find the best implementation strategy based on the current Ruby engine and version. The chosen ‘STRATEGY` is included into the Weak::Set class.
[ Weak::Set::WeakKeysWithDelete, Weak::Set::WeakKeys, Weak::Set::StrongKeys, Weak::Set::StrongSecondaryKeys ].find(&:usable?)
Class Method Summary collapse
-
.[](*ary) ⇒ Weak::Set
A new weak set containing the given objects.
Instance Method Summary collapse
-
#&(enum) ⇒ Weak::Set
(also: #intersection)
A new weak set containing elements common to ‘self` and the given enumerable object.
-
#-(enum) ⇒ Weak::Set
(also: #difference)
A new weak set built by duplicating ‘self`, removing every element that appears in the given enumerable object from that.
-
#<=>(other) ⇒ Integer?
‘0` if `self` and the given `set` contain the same elements, `-1` / `+1` if `self` is a proper subset / superset of the given `set`, or `nil` if they both have unique elements or `set` is not a Set.
-
#==(other) ⇒ Bool
Returns true if two weak sets are equal.
-
#[](obj) ⇒ Object?
The provided ‘obj` if it is included in `self`, `nil` otherwise.
-
#^(enum) ⇒ Weak::Set
Returns a new weak set containing elements exclusive between ‘self` and the given enumerable object.
-
#add(obj) ⇒ self
(also: #<<)
Adds the given object to the weak set and return ‘self`.
-
#add?(obj) ⇒ self?
Adds the given object to the weak set and returns ‘self`.
-
#clear ⇒ self
Removes all elements and returns ‘self`.
-
#clone(freeze: false) ⇒ Weak::Set
Set objects can’t be frozen since this is not enforced by the underlying ‘ObjectSpace::WeakMap` implementation.
-
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
-
#compare_by_identity? ⇒ true
Always ‘true` since we always compare elements by their object identity.
-
#delete(obj) ⇒ self
Deletes the given object from ‘self` and returns `self`.
-
#delete?(obj) ⇒ self?
Deletes the given object from ‘self` and returns `self` if it was present in the set.
-
#delete_if {|element| ... } ⇒ self, Enumerator
Deletes every element of the weak set for which the given block block evaluates to a truethy value, and returns ‘self`.
-
#disjoint?(enum) ⇒ Bool
‘true` if `self` and the given `enum` have no element in common.
-
#each {|element| ... } ⇒ self, Enumerator
Calls the given block once for each live element in ‘self`, passing that element as a parameter.
-
#empty? ⇒ Boolean
‘true` if `self` contains no elements.
-
#freeze ⇒ self
Set objects can’t be frozen since this is not enforced by the underlying ‘ObjectSpace::WeakMap` implementation.
-
#include?(obj) ⇒ Bool
(also: #===, #member?)
‘true` if the given object is included in `self`, `false` otherwise.
-
#initialize(enum = nil) {|element| ... } ⇒ Set
constructor
A new instance of Set.
-
#inspect ⇒ String
(also: #to_s)
A string containing a human-readable representation of the weak set, e.g., ‘“#<Weak::Set element2, …>”`.
-
#intersect?(enum) ⇒ Bool
‘true` if `self` and the given enumerable object have at least one element in common, `false` otherwise.
-
#keep_if {|element| ... } ⇒ Enumerator, self
Deletes every element from ‘self` for which the given block evaluates to a falsey value.
-
#merge(*enums) ⇒ self
Merges the elements of the given enumerable objects to the set and returns ‘self`.
-
#proper_subset?(other) ⇒ Bool
(also: #<)
‘true` if `self` is a proper subset of the given `set`, `false` otherwise.
-
#proper_superset?(other) ⇒ Bool
(also: #>)
‘true` if `self` is a proper superset of the given `set`, `false` otherwise.
-
#prune ⇒ self
(also: #reset)
Cleanup data structures from the set to remove data associated with deleted or garbage collected elements.
-
#reject! {|element| ... } ⇒ Enumerator, ...
Deletes every live element from ‘self` for which the given block evaluates to a truethy value.
-
#replace(enum) ⇒ self
Replaces the contents of ‘self` with the contents of the given enumerable object and returns `self`.
-
#select! {|element| ... } ⇒ Enumerator, ...
(also: #filter!)
Deletes every element from ‘self` for which the given block evaluates to a falsey value.
-
#size ⇒ Integer
(also: #length)
The number of live elements in ‘self`.
-
#subset?(other) ⇒ Bool
(also: #<=)
‘true` if `self` is a subset of the given `set`, `false` otherwise.
-
#subtract(enum) ⇒ self
Deletes every element from ‘self` which appears in the given enumerable object `enum` and returns `self`.
-
#superset?(other) ⇒ Bool
(also: #>=)
‘true` if `self` is a superset of the given `set`, `false` otherwise.
-
#to_a ⇒ Array
The live elements contained in ‘self` as an `Array`.
-
#to_set ⇒ Set
The elements in ‘self` as a regular `Set` with strong object references.
-
#|(enum) ⇒ Weak::Set
(also: #+, #union)
A new weak set built by merging ‘self` and the elements of the given enumerable object.
Constructor Details
permalink #initialize(enum = nil) {|element| ... } ⇒ Set
Returns a new instance of Set.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/weak/set.rb', line 232 def initialize(enum = nil) clear return if enum.nil? if block_given? do_with_enum(enum) do |obj| add yield(obj) end else do_with_enum(enum) do |obj| add obj end end end |
Class Method Details
permalink .[](*ary) ⇒ Weak::Set
Returns a new weak set containing the given objects.
222 223 224 |
# File 'lib/weak/set.rb', line 222 def self.[](*ary) new(ary) end |
Instance Method Details
permalink #&(enum) ⇒ Weak::Set Also known as: intersection
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns a new weak set containing elements common to ‘self` and the given enumerable object.
292 293 294 295 296 297 298 |
# File 'lib/weak/set.rb', line 292 def &(enum) new_set = self.class.new do_with_enum(enum) do |obj| new_set.add(obj) if include?(obj) end new_set end |
permalink #-(enum) ⇒ Weak::Set Also known as: difference
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns a new weak set built by duplicating ‘self`, removing every element that appears in the given enumerable object from that.
279 280 281 |
# File 'lib/weak/set.rb', line 279 def -(enum) dup.subtract(enum) end |
permalink #<=>(other) ⇒ Integer?
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns ‘0` if `self` and the given `set` contain the same elements, `-1` / `+1` if `self` is a proper subset / superset of the given `set`, or `nil` if they both have unique elements or `set` is not a Weak::Set.
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/weak/set.rb', line 307 def <=>(other) return unless Weak::Set === other return 0 if equal?(other) other_ary = other.to_a own_ary = to_a case own_ary.size <=> other_ary.size when -1 -1 if own_ary.all?(other) when 1 1 if other_ary.all?(self) else 0 if own_ary.all?(other) end end |
permalink #==(other) ⇒ Bool
Returns true if two weak sets are equal. The equality of each couple of elements is defined according to strict object equality so that, e.g., different strings are not equal, even if they may contain the same data.
335 336 337 338 339 340 341 342 343 344 |
# File 'lib/weak/set.rb', line 335 def ==(other) return true if equal?(other) return false unless Weak::Set === other other_ary = other.to_a own_ary = to_a return false unless own_ary.size == other_ary.size own_ary.all?(other) end |
permalink #[](obj) ⇒ Object?
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns the provided ‘obj` if it is included in `self`, `nil` otherwise.
372 373 374 |
# File 'lib/weak/set.rb', line 372 def [](obj) obj if include?(obj) end |
permalink #^(enum) ⇒ Weak::Set
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns a new weak set containing elements exclusive between ‘self` and the given enumerable object. `(set ^ enum)` is equivalent to `((set | enum) - (set & enum))`.
357 358 359 360 361 362 363 364 365 |
# File 'lib/weak/set.rb', line 357 def ^(enum) return dup if enum.nil? new_set = self.class.new.merge(enum) each do |obj| new_set.add(obj) unless new_set.delete?(obj) end new_set end |
permalink #add(obj) ⇒ self Also known as: <<
Adds the given object to the weak set and return ‘self`. Use #merge to add many elements at once.
In contrast to other “regular” objects, we will not retain a strong reference to the added object. Unless some other live objects still references the object, it will eventually be garbage-collected.
|
# File 'lib/weak/set.rb', line 181
|
permalink #add?(obj) ⇒ self?
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Adds the given object to the weak set and returns ‘self`. If the object is already in the set, returns `nil`.
388 389 390 |
# File 'lib/weak/set.rb', line 388 def add?(obj) add(obj) unless include?(obj) end |
permalink #clear ⇒ self
Removes all elements and returns ‘self`
|
# File 'lib/weak/set.rb', line 184
|
permalink #clone(freeze: false) ⇒ Weak::Set
Weak::Set objects can’t be frozen since this is not enforced by the underlying ‘ObjectSpace::WeakMap` implementation. Thus, we try to signal this by not actually setting the `frozen?` flag and ignoring attempts to freeze us with just a warning.
401 402 403 404 405 |
# File 'lib/weak/set.rb', line 401 def clone(freeze: false) warn("Can't freeze #{self.class}") if freeze super(freeze: false) end |
permalink #compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
411 412 413 |
# File 'lib/weak/set.rb', line 411 def compare_by_identity self end |
permalink #compare_by_identity? ⇒ true
Returns always ‘true` since we always compare elements by their object identity.
417 418 419 |
# File 'lib/weak/set.rb', line 417 def compare_by_identity? true end |
permalink #delete(obj) ⇒ self
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Deletes the given object from ‘self` and returns `self`. Use #subtract to delete many items at once.
427 428 429 430 |
# File 'lib/weak/set.rb', line 427 def delete(obj) delete?(obj) self end |
permalink #delete?(obj) ⇒ self?
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Deletes the given object from ‘self` and returns `self` if it was present in the set. If the object was not in the set, returns `nil`.
|
# File 'lib/weak/set.rb', line 187
|
permalink #delete_if {|element| ... } ⇒ self, Enumerator
Deletes every element of the weak set for which the given block block evaluates to a truethy value, and returns ‘self`. Returns an `Enumerator` if no block is given.
441 442 443 444 445 446 447 448 |
# File 'lib/weak/set.rb', line 441 def delete_if(&block) return enum_for(__method__) { size } unless block_given? each do |obj| delete?(obj) if yield(obj) end self end |
permalink #disjoint?(enum) ⇒ Bool
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns ‘true` if `self` and the given `enum` have no element in common. This method is the opposite of #intersect?.
454 455 456 |
# File 'lib/weak/set.rb', line 454 def disjoint?(enum) !intersect?(enum) end |
permalink #each {|element| ... } ⇒ self, Enumerator
Calls the given block once for each live element in ‘self`, passing that element as a parameter. Returns the weak set itself.
If no block is given, an ‘Enumerator` is returned instead.
|
# File 'lib/weak/set.rb', line 190
|
permalink #empty? ⇒ Boolean
Returns ‘true` if `self` contains no elements.
459 460 461 |
# File 'lib/weak/set.rb', line 459 def empty? size == 0 end |
permalink #freeze ⇒ self
Weak::Set objects can’t be frozen since this is not enforced by the underlying ‘ObjectSpace::WeakMap` implementation. Thus, we try to signal this by not actually setting the `frozen?` flag and ignoring attempts to freeze us with just a warning.
469 470 471 472 |
# File 'lib/weak/set.rb', line 469 def freeze warn("Can't freeze #{self.class}") self end |
permalink #include?(obj) ⇒ Bool Also known as: ===, member?
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns ‘true` if the given object is included in `self`, `false` otherwise.
|
# File 'lib/weak/set.rb', line 193
|
permalink #inspect ⇒ String Also known as: to_s
Returns a string containing a human-readable representation of the weak set, e.g., ‘“#<Weak::Set element2, …>”`.
476 477 478 479 480 481 482 483 484 485 486 487 |
# File 'lib/weak/set.rb', line 476 def inspect object_ids = (Thread.current[INSPECT_KEY] ||= []) return "#<#{self.class} {...}>" if object_ids.include?(object_id) object_ids << object_id begin elements = to_a.sort_by!(&:__id__).inspect[1..-2] "#<#{self.class} {#{elements}}>" ensure object_ids.pop end end |
permalink #intersect?(enum) ⇒ Bool
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns ‘true` if `self` and the given enumerable object have at least one element in common, `false` otherwise.
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'lib/weak/set.rb', line 500 def intersect?(enum) case enum when Weak::Set enum_ary = enum.to_a own_ary = to_a if own_ary.size < enum_ary.size own_ary.any?(enum) else enum_ary.any?(self) end else enumerable(enum).any?(self) end end |
permalink #keep_if {|element| ... } ⇒ Enumerator, self
Deletes every element from ‘self` for which the given block evaluates to a falsey value.
If no block is given, an ‘Enumerator` is returned instead.
527 528 529 530 531 532 533 534 |
# File 'lib/weak/set.rb', line 527 def keep_if(&block) return enum_for(__method__) { size } unless block_given? each do |obj| delete?(obj) unless yield(obj) end self end |
permalink #merge(*enums) ⇒ self
Merges the elements of the given enumerable objects to the set and returns ‘self`
541 542 543 544 545 546 547 548 |
# File 'lib/weak/set.rb', line 541 def merge(*enums, **nil) enums.each do |enum| do_with_enum(enum) do |obj| add(obj) end end self end |
permalink #proper_subset?(other) ⇒ Bool Also known as: <
Returns ‘true` if `self` is a proper subset of the given `set`, `false` otherwise.
571 572 573 574 575 576 577 578 579 580 581 |
# File 'lib/weak/set.rb', line 571 def proper_subset?(other) if Weak::Set === other other_ary = other.to_a own_ary = to_a return false unless own_ary.size < other_ary.size own_ary.all?(other) else raise ArgumentError, "value must be a weak set" end end |
permalink #proper_superset?(other) ⇒ Bool Also known as: >
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns ‘true` if `self` is a proper superset of the given `set`, `false` otherwise.
589 590 591 592 593 594 595 596 597 598 599 |
# File 'lib/weak/set.rb', line 589 def proper_superset?(other) if Weak::Set === other other_ary = other.to_a own_ary = to_a return false unless own_ary.size > other_ary.size other_ary.all?(self) else raise ArgumentError, "value must be a weak set" end end |
permalink #prune ⇒ self Also known as: reset
Cleanup data structures from the set to remove data associated with deleted or garbage collected elements. This method may be called automatically for some Weak::Set operations.
|
# File 'lib/weak/set.rb', line 196
|
permalink #reject! {|element| ... } ⇒ Enumerator, ...
Deletes every live element from ‘self` for which the given block evaluates to a truethy value.
Equivalent to #delete_if, but returns ‘nil` if no changes were made.
If no block is given, an ‘Enumerator` is returned instead.
deleted, or an `Enumerator` if no block was given.
#see #delete_if
615 616 617 618 619 620 621 622 623 624 |
# File 'lib/weak/set.rb', line 615 def reject!(&block) return enum_for(__method__) { size } unless block_given? deleted_anything = false each do |obj| deleted_anything = true if yield(obj) && delete?(obj) end self if deleted_anything end |
permalink #replace(enum) ⇒ self
Replaces the contents of ‘self` with the contents of the given enumerable object and returns `self`.
|
# File 'lib/weak/set.rb', line 202
|
permalink #select! {|element| ... } ⇒ Enumerator, ... Also known as: filter!
Deletes every element from ‘self` for which the given block evaluates to a falsey value.
Equivalent to #keep_if, but returns ‘nil` if no changes were made.
If no block is given, an ‘Enumerator` is returned instead.
639 640 641 642 643 644 645 646 647 648 |
# File 'lib/weak/set.rb', line 639 def select!(&block) return enum_for(__method__) { size } unless block_given? deleted_anything = false each do |obj| deleted_anything = true if !yield(obj) && delete?(obj) end self if deleted_anything end |
permalink #size ⇒ Integer Also known as: length
Returns the number of live elements in ‘self`.
|
# File 'lib/weak/set.rb', line 199
|
permalink #subset?(other) ⇒ Bool Also known as: <=
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns ‘true` if `self` is a subset of the given `set`, `false` otherwise.
656 657 658 659 660 661 662 663 664 665 666 |
# File 'lib/weak/set.rb', line 656 def subset?(other) if Weak::Set === other other_ary = other.to_a own_ary = to_a return false unless own_ary.size <= other_ary.size own_ary.all?(other) else raise ArgumentError, "value must be a weak set" end end |
permalink #subtract(enum) ⇒ self
Deletes every element from ‘self` which appears in the given enumerable object `enum` and returns `self`.
674 675 676 677 678 679 |
# File 'lib/weak/set.rb', line 674 def subtract(enum) do_with_enum(enum) do |obj| delete?(obj) end self end |
permalink #superset?(other) ⇒ Bool Also known as: >=
Returns ‘true` if `self` is a superset of the given `set`, `false` otherwise.
685 686 687 688 689 690 691 692 693 694 695 |
# File 'lib/weak/set.rb', line 685 def superset?(other) if Weak::Set === other other_ary = other.to_a own_ary = to_a return false unless own_ary.size >= other_ary.size other_ary.all?(self) else raise ArgumentError, "value must be a weak set" end end |
permalink #to_a ⇒ Array
The order of elements on the returned ‘Array` is non-deterministic. We do not preserve preserve insertion order.
Returns the live elements contained in ‘self` as an `Array`.
|
# File 'lib/weak/set.rb', line 205
|
permalink #to_set ⇒ Set
The returned set is configured to compare elements by their object identity, similar to a ‘Weak::Set`.
Returns the elements in ‘self` as a regular `Set` with strong object references.
702 703 704 705 706 707 708 |
# File 'lib/weak/set.rb', line 702 def to_set set = ::Set.new.compare_by_identity each do |obj| set.add(obj) end set end |
permalink #|(enum) ⇒ Weak::Set Also known as: +, union
Weak::Set does not test member equality with ‘==` or `eql?`. Instead, it always checks strict object equality, so that, e.g., different strings are not considered equal, even if they may contain the same string content.
Returns a new weak set built by merging ‘self` and the elements of the given enumerable object.
261 262 263 264 265 266 267 |
# File 'lib/weak/set.rb', line 261 def |(enum) new_set = dup do_with_enum(enum) do |obj| new_set.add(obj) end new_set end |