Class: WeakSet
- Inherits:
-
Object
- Object
- WeakSet
- Includes:
- Enumerable, [ WeakSet[ WeakSet::WeakKeysWithDelete, WeakSet::WeakKeys, WeakSet::StrongKeys, WeakSet::StrongSecondaryKeys ].find(&:usable?)
- Defined in:
- lib/weak_set.rb,
lib/weak_set/version.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 WeakSet 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.
WeakSet 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#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 WeakSet is not inherently thread-safe. When accessing a WeakSet 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 a 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 a 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 a WeakMap with strong keys and
weak values. To allow a entries in a 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 WeakMaps 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 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 and are not necessarily equal to each other when used as keys in a WeakMap. As a workaround we use a more 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 WeakSet class. [ WeakSet::WeakKeysWithDelete, WeakSet::WeakKeys, WeakSet::StrongKeys, WeakSet::StrongSecondaryKeys ].find(&:usable?)
- VERSION =
The WeakSet version as a
Gem::Version
string. We follow semantic versioning. "0.0.1.pre"
Class Method Summary collapse
-
.[](*ary) ⇒ WeakSet
A new weak set containing the given objects.
Instance Method Summary collapse
-
#&(enum) ⇒ WeakSet
(also: #intersection)
A new weak set containing elements common to
self
and the given enumerable object. -
#-(enum) ⇒ WeakSet
(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
ifself
and the givenset
contain the same elements,-1
/+1
ifself
is a proper subset / superset of the givenset
, ornil
if they both have unique elements orset
is not a WeakSet. -
#==(other) ⇒ Bool
Returns true if two weak sets are equal.
-
#[](obj) ⇒ Object?
The provided
obj
if it is included inself
,nil
otherwise. -
#^(enum) ⇒ WeakSet
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) ⇒ WeakSet
WeakSets 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 returnsself
. -
#delete?(obj) ⇒ self?
Deletes the given object from
self
and returnsself
if it was present in the set. -
#delete_if {|element| ... } ⇒ self, Enumerator
Deletes every element of the weak set for which block evaluates to a truethy value, and returns
self
. -
#disjoint?(enum) ⇒ Bool
true
ifself
and the givenenum
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
ifself
contains no elements. -
#freeze ⇒ self
WeakSets 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 inself
,false
otherwise. -
#initialize(enum = nil) {|element| ... } ⇒ WeakSet
constructor
A new instance of WeakSet.
-
#inspect ⇒ String
(also: #to_s)
A string containing a human-readable representation of the weak set, e.g.,
"#<WeakSet: {element1, element2, ...}>"
. -
#intersect?(enum) ⇒ Bool
true
ifself
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
ifself
is a proper subset of the givenset
,false
otherwise. -
#proper_superset?(other) ⇒ Bool
(also: #>)
true
ifself
is a proper superset of the givenset
,false
otherwise. -
#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 returnsself
. -
#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
ifself
is a subset of the givenset
,false
otherwise. -
#subtract(enum) ⇒ self
Deletes every element from
self
which appears in the given enumerable objectenum
and returnsself
. -
#superset?(other) ⇒ Bool
(also: #>=)
true
ifself
is a superset of the givenset
,false
otherwise. -
#to_a ⇒ Array
The live elements contained in
self
as anArray
. -
#to_set ⇒ Set
The elements in
self
as a regularSet
with strong object references. -
#|(enum) ⇒ WeakSet
(also: #+, #union)
A new weak set built by merging
self
and the elements of the given enumerable object.
Constructor Details
#initialize(enum = nil) {|element| ... } ⇒ WeakSet
Returns a new instance of WeakSet.
201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/weak_set.rb', line 201 def initialize(enum = nil) super() 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
.[](*ary) ⇒ WeakSet
Returns a new weak set containing the given objects.
191 192 193 |
# File 'lib/weak_set.rb', line 191 def self.[](*ary) new(ary) end |
Instance Method Details
#&(enum) ⇒ WeakSet Also known as: intersection
WeakSet 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.
260 261 262 263 264 265 266 |
# File 'lib/weak_set.rb', line 260 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) ⇒ WeakSet Also known as: difference
WeakSet 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.
247 248 249 |
# File 'lib/weak_set.rb', line 247 def -(enum) dup.subtract(enum) end |
#<=>(other) ⇒ Integer?
WeakSet 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
WeakSet.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/weak_set.rb', line 275 def <=>(other) return unless WeakSet === 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.
303 304 305 306 307 308 309 310 311 312 |
# File 'lib/weak_set.rb', line 303 def ==(other) return true if equal?(other) return false unless WeakSet === 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?
WeakSet 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.
340 341 342 |
# File 'lib/weak_set.rb', line 340 def [](obj) obj if include?(obj) end |
#^(enum) ⇒ WeakSet
WeakSet 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))
.
325 326 327 328 329 330 331 332 333 |
# File 'lib/weak_set.rb', line 325 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 156
|
#add?(obj) ⇒ self?
WeakSet 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
.
356 357 358 |
# File 'lib/weak_set.rb', line 356 def add?(obj) add(obj) unless include?(obj) end |
#clear ⇒ self
Removes all elements and returns self
|
# File 'lib/weak_set.rb', line 159
|
#clone(freeze: false) ⇒ WeakSet
WeakSets 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.
368 369 370 371 372 |
# File 'lib/weak_set.rb', line 368 def clone(freeze: false) warn("Can't freeze WeakSet") if freeze super(freeze: false) end |
#compare_by_identity ⇒ self
This method does nothing as we always compare elements by their object identity.
378 379 380 |
# File 'lib/weak_set.rb', line 378 def compare_by_identity self end |
#compare_by_identity? ⇒ true
Returns always true
since we always compare elements by their
object identity.
384 385 386 |
# File 'lib/weak_set.rb', line 384 def compare_by_identity? true end |
#delete(obj) ⇒ self
WeakSet 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.
394 395 396 397 |
# File 'lib/weak_set.rb', line 394 def delete(obj) delete?(obj) self end |
#delete?(obj) ⇒ self?
WeakSet 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 165
|
#delete_if {|element| ... } ⇒ self, Enumerator
Deletes every element of the weak set for which block evaluates to a truethy
value, and returns self
. Returns an Enumerator
if no block is given.
407 408 409 410 411 412 413 414 |
# File 'lib/weak_set.rb', line 407 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
WeakSet 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?.
420 421 422 |
# File 'lib/weak_set.rb', line 420 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 162
|
#empty? ⇒ Boolean
Returns true
if self
contains no elements.
425 426 427 |
# File 'lib/weak_set.rb', line 425 def empty? size == 0 end |
#freeze ⇒ self
WeakSets 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.
434 435 436 437 |
# File 'lib/weak_set.rb', line 434 def freeze warn("Can't freeze WeakSet") self end |
#include?(obj) ⇒ Bool Also known as: ===, member?
WeakSet 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 168
|
#inspect ⇒ String Also known as: to_s
Returns a string containing a human-readable representation of the
weak set, e.g., "#<WeakSet: {element1, element2, ...}>"
.
441 442 443 444 445 446 447 448 449 450 451 452 |
# File 'lib/weak_set.rb', line 441 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
WeakSet 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.
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/weak_set.rb', line 465 def intersect?(enum) case enum when WeakSet 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.
492 493 494 495 496 497 498 499 |
# File 'lib/weak_set.rb', line 492 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
506 507 508 509 510 511 512 513 |
# File 'lib/weak_set.rb', line 506 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.
536 537 538 539 540 541 542 543 544 545 546 |
# File 'lib/weak_set.rb', line 536 def proper_subset?(other) if WeakSet === 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: >
WeakSet 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.
554 555 556 557 558 559 560 561 562 563 564 |
# File 'lib/weak_set.rb', line 554 def proper_superset?(other) if WeakSet === 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 |
#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
599 600 601 602 603 604 605 606 607 608 |
# File 'lib/weak_set.rb', line 599 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
.
577 578 579 580 581 582 583 584 |
# File 'lib/weak_set.rb', line 577 def replace(enum) cleared do do_with_enum(enum) do |obj| add(obj) end end self end |
#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.
623 624 625 626 627 628 629 630 631 632 |
# File 'lib/weak_set.rb', line 623 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 171
|
#subset?(other) ⇒ Bool Also known as: <=
WeakSet 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.
640 641 642 643 644 645 646 647 648 649 650 |
# File 'lib/weak_set.rb', line 640 def subset?(other) if WeakSet === 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
.
658 659 660 661 662 663 |
# File 'lib/weak_set.rb', line 658 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.
669 670 671 672 673 674 675 676 677 678 679 |
# File 'lib/weak_set.rb', line 669 def superset?(other) if WeakSet === 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 174
|
#to_set ⇒ Set
The returned set is configured to compare elements by their object
identity, similar to a WeakSet
.
Returns the elements in self
as a regular Set
with strong object
references.
686 687 688 689 690 691 692 |
# File 'lib/weak_set.rb', line 686 def to_set set = Set.new.compare_by_identity each do |obj| set.add(obj) end set end |
#|(enum) ⇒ WeakSet Also known as: +, union
WeakSet 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.
229 230 231 232 233 234 235 |
# File 'lib/weak_set.rb', line 229 def |(enum) new_set = dup do_with_enum(enum) do |obj| new_set.add(obj) end new_set end |