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
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?orObject#hashas 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 supported Ruby implementations and versions implement 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::WeakMapwith 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::WeakMapwith 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::WeakMapwith strong keys and weak values. To allow a entries in anObjectSpace::WeakMapto be garbage collected, we can't use the actual object as a key. Instead, we use the element'sobject_idas a key. As theseObjectSpace::WeakMapobjects 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::WeakMapas 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 anObjectSpace::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
STRATEGYis included into the Weak::Set class. [ Weak::Set::WeakKeysWithDelete, Weak::Set::WeakKeys, Weak::Set::StrongKeys, Weak::Set::StrongSecondaryKeys ].find(&:usable?)
Class Method Summary collapse
-
.[](*objects) ⇒ 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
selfand 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?
0ifselfand the givensetcontain the same elements,-1/+1ifselfis a proper subset / superset of the givenset, ornilif they both have unique elements orsetis not a Set. -
#==(other) ⇒ Bool
Returns true if two weak sets are equal.
-
#[](obj) ⇒ Object?
The provided
objif it is included inself,nilotherwise. -
#^(enum) ⇒ Weak::Set
Returns a new weak set containing elements exclusive between
selfand 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::WeakMapimplementation. -
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
-
#compare_by_identity? ⇒ true
Always
truesince we always compare elements by their object identity. -
#delete(obj) ⇒ self
Deletes the given object from
selfand returnsself. -
#delete?(obj) ⇒ self?
Deletes the given object from
selfand returnsselfif the given object 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
trueifselfand the givenenumhave 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
trueifselfcontains no elements. -
#freeze ⇒ self
Set objects can't be frozen since this is not enforced by the underlying
ObjectSpace::WeakMapimplementation. -
#include?(obj) ⇒ Bool
(also: #===, #member?)
trueif the given object is included inself,falseotherwise. -
#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[element1, element2, ...]". -
#intersect?(enum) ⇒ Bool
trueifselfand the given enumerable object have at least one element in common,falseotherwise. -
#keep_if {|element| ... } ⇒ Enumerator, self
Deletes every element from
selffor 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: #<)
trueifselfis a proper subset of the givenset,falseotherwise. -
#proper_superset?(other) ⇒ Bool
(also: #>)
trueifselfis a proper superset of the givenset,falseotherwise. -
#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
selffor which the given block evaluates to a truethy value. -
#replace(enum) ⇒ self
Replaces the contents of
selfwith the contents of the given enumerable object and returnsself. -
#select! {|element| ... } ⇒ Enumerator, ...
(also: #filter!)
Deletes every element from
selffor which the given block evaluates to a falsey value. -
#size ⇒ Integer
(also: #length)
The number of live elements in
self. -
#subset?(other) ⇒ Bool
(also: #<=)
trueifselfis a subset of the givenset,falseotherwise. -
#subtract(enum) ⇒ self
Deletes every element from
selfwhich appears in the given enumerable objectenumand returnsself. -
#superset?(other) ⇒ Bool
(also: #>=)
trueifselfis a superset of the givenset,falseotherwise. -
#to_a ⇒ Array
The live elements contained in
selfas anArray. -
#to_set ⇒ Set
The elements in
selfas a regularSetwith strong object references. -
#|(enum) ⇒ Weak::Set
(also: #+, #union)
A new weak set built by merging
selfand the elements of the given enumerable object.
Constructor Details
#initialize(enum = nil) {|element| ... } ⇒ Set
Returns a new instance of Set.
236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/weak/set.rb', line 236 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
.[](*objects) ⇒ Weak::Set
Returns a new weak set containing the given objects.
226 227 228 |
# File 'lib/weak/set.rb', line 226 def self.[](*objects) new(objects) end |
Instance Method Details
#&(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.
296 297 298 299 300 301 302 |
# File 'lib/weak/set.rb', line 296 def &(enum) new_set = self.class.new do_with_enum(enum) do |obj| new_set.add(obj) if include?(obj) end new_set end |
#-(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.
283 284 285 |
# File 'lib/weak/set.rb', line 283 def -(enum) dup.subtract(enum) end |
#<=>(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.
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/weak/set.rb', line 311 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 |
#==(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.
339 340 341 342 343 344 345 346 347 348 |
# File 'lib/weak/set.rb', line 339 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 |
#[](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.
376 377 378 |
# File 'lib/weak/set.rb', line 376 def [](obj) obj if include?(obj) end |
#^(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)).
361 362 363 364 365 366 367 368 369 |
# File 'lib/weak/set.rb', line 361 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 |
#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 184
|
#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.
392 393 394 |
# File 'lib/weak/set.rb', line 392 def add?(obj) add(obj) unless include?(obj) end |
#clear ⇒ self
Removes all elements and returns self
|
|
# File 'lib/weak/set.rb', line 187
|
#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.
405 406 407 408 409 |
# File 'lib/weak/set.rb', line 405 def clone(freeze: false) warn("Can't freeze #{self.class}") if freeze super(freeze: false) end |
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
415 416 417 |
# File 'lib/weak/set.rb', line 415 def compare_by_identity self end |
#compare_by_identity? ⇒ true
Returns always true since we always compare elements by their
object identity.
421 422 423 |
# File 'lib/weak/set.rb', line 421 def compare_by_identity? true end |
#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.
432 433 434 435 |
# File 'lib/weak/set.rb', line 432 def delete(obj) delete?(obj) self end |
#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 the given
object was present in the set. If the object was not in the set,
returns nil.
|
|
# File 'lib/weak/set.rb', line 190
|
#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.
446 447 448 449 450 451 452 453 |
# File 'lib/weak/set.rb', line 446 def delete_if(&block) return enum_for(__method__) { size } unless block_given? each do |obj| delete?(obj) if yield(obj) end self end |
#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?.
459 460 461 |
# File 'lib/weak/set.rb', line 459 def disjoint?(enum) !intersect?(enum) end |
#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 193
|
#empty? ⇒ Boolean
Returns true if self contains no elements.
464 465 466 |
# File 'lib/weak/set.rb', line 464 def empty? size == 0 end |
#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.
474 475 476 477 |
# File 'lib/weak/set.rb', line 474 def freeze warn("Can't freeze #{self.class}") self end |
#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 196
|
#inspect ⇒ String Also known as: to_s
The elements of the set are ordered by their object id in the
inspect output. If we detect a reference cycle (e.g. with a set
containing itself), we output "Weak::Set[...]".
Returns a string containing a human-readable representation of
the weak set, e.g., "Weak::Set[element1, element2, ...]".
484 485 486 487 488 489 490 491 492 493 494 495 |
# File 'lib/weak/set.rb', line 484 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 |
#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.
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 |
# File 'lib/weak/set.rb', line 508 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 |
#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.
535 536 537 538 539 540 541 542 |
# File 'lib/weak/set.rb', line 535 def keep_if(&block) return enum_for(__method__) { size } unless block_given? each do |obj| delete?(obj) unless yield(obj) end self end |
#merge(*enums) ⇒ self
Merges the elements of the given enumerable objects to the set and returns
self
549 550 551 552 553 554 555 556 |
# File 'lib/weak/set.rb', line 549 def merge(*enums, **nil) enums.each do |enum| do_with_enum(enum) do |obj| add(obj) end end self end |
#proper_subset?(other) ⇒ Bool Also known as: <
Returns true if self is a proper subset of the given set,
false otherwise.
576 577 578 579 580 581 582 583 584 585 586 |
# File 'lib/weak/set.rb', line 576 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 |
#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.
594 595 596 597 598 599 600 601 602 603 604 |
# File 'lib/weak/set.rb', line 594 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 |
#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 199
|
#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.
620 621 622 623 624 625 626 627 628 629 |
# File 'lib/weak/set.rb', line 620 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 |
#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 205
|
#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.
644 645 646 647 648 649 650 651 652 653 |
# File 'lib/weak/set.rb', line 644 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 |
#size ⇒ Integer Also known as: length
Returns the number of live elements in self.
|
|
# File 'lib/weak/set.rb', line 202
|
#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.
661 662 663 664 665 666 667 668 669 670 671 |
# File 'lib/weak/set.rb', line 661 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 |
#subtract(enum) ⇒ self
Deletes every element from self which appears in the given enumerable
object enum and returns self.
679 680 681 682 683 684 |
# File 'lib/weak/set.rb', line 679 def subtract(enum) do_with_enum(enum) do |obj| delete?(obj) end self end |
#superset?(other) ⇒ Bool Also known as: >=
Returns true if self is a superset of the given set, false
otherwise.
690 691 692 693 694 695 696 697 698 699 700 |
# File 'lib/weak/set.rb', line 690 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 |
#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 208
|
#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.
707 708 709 710 711 712 713 |
# File 'lib/weak/set.rb', line 707 def to_set set = ::Set.new.compare_by_identity each do |obj| set.add(obj) end set end |
#|(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.
265 266 267 268 269 270 271 |
# File 'lib/weak/set.rb', line 265 def |(enum) new_set = dup do_with_enum(enum) do |obj| new_set.add(obj) end new_set end |