1 # frozen_string_literal: true
4 # set.rb - defines the Set class
6 # Copyright (c) 2002-2023 Akinori MUSHA <knu@iDaemons.org>
8 # Documentation by Akinori MUSHA and Gavin Sinclair.
10 # All rights reserved. You can redistribute and/or modify it under the same
15 # This library provides the Set class, which implements a collection
16 # of unordered values with no duplicates. It is a hybrid of Array's
17 # intuitive inter-operation facilities and Hash's fast lookup.
19 # The method `to_set` is added to Enumerable for convenience.
21 # Set is easy to use with Enumerable objects (implementing `each`).
22 # Most of the initializer methods and binary operators accept generic
23 # Enumerable objects besides sets and arrays. An Enumerable object
24 # can be converted to Set using the `to_set` method.
26 # Set uses Hash as storage, so you must note the following points:
28 # * Equality of elements is determined according to Object#eql? and
29 # Object#hash. Use Set#compare_by_identity to make a set compare
30 # its elements by their identity.
31 # * Set assumes that the identity of each element does not change
32 # while it is stored. Modifying an element of a set will render the
33 # set to an unreliable state.
34 # * When a string is to be stored, a frozen copy of the string is
35 # stored instead unless the original string is already frozen.
39 # The comparison operators `<`, `>`, `<=`, and `>=` are implemented as
40 # shorthand for the {proper_,}{subset?,superset?} methods. The `<=>`
41 # operator reflects this order, or return `nil` for sets that both
42 # have distinct elements (`{x, y}` vs. `{x, z}` for example).
48 # s1 = Set[1, 2] #=> #<Set: {1, 2}>
49 # s2 = [1, 2].to_set #=> #<Set: {1, 2}>
51 # s1.add("foo") #=> #<Set: {1, 2, "foo"}>
52 # s1.merge([2, 6]) #=> #<Set: {1, 2, "foo", 6}>
53 # s1.subset?(s2) #=> false
54 # s2.subset?(s1) #=> true
59 # - Akinori MUSHA <<knu@iDaemons.org>> (current maintainer)
63 # First, what's elsewhere. \Class \Set:
65 # - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
66 # - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
67 # which provides dozens of additional methods.
69 # In particular, class \Set does not have many methods of its own
70 # for fetching or for iterating.
71 # Instead, it relies on those in \Enumerable.
73 # Here, class \Set provides methods that are useful for:
75 # - [Creating a Set](#class-Set-label-Methods+for+Creating+a+Set)
76 # - [Set Operations](#class-Set-label-Methods+for+Set+Operations)
77 # - [Comparing](#class-Set-label-Methods+for+Comparing)
78 # - [Querying](#class-Set-label-Methods+for+Querying)
79 # - [Assigning](#class-Set-label-Methods+for+Assigning)
80 # - [Deleting](#class-Set-label-Methods+for+Deleting)
81 # - [Converting](#class-Set-label-Methods+for+Converting)
82 # - [Iterating](#class-Set-label-Methods+for+Iterating)
83 # - [And more....](#class-Set-label-Other+Methods)
85 # ### Methods for Creating a \Set
88 # Returns a new set containing the given objects.
90 # Returns a new set containing either the given objects
91 # (if no block given) or the return values from the called block
94 # ### Methods for \Set Operations
96 # - [|](#method-i-7C) (aliased as #union and #+):
97 # Returns a new set containing all elements from +self+
98 # and all elements from a given enumerable (no duplicates).
99 # - [&](#method-i-26) (aliased as #intersection):
100 # Returns a new set containing all elements common to +self+
101 # and a given enumerable.
102 # - [-](#method-i-2D) (aliased as #difference):
103 # Returns a copy of +self+ with all elements
104 # in a given enumerable removed.
105 # - [\^](#method-i-5E):
106 # Returns a new set containing all elements from +self+
107 # and a given enumerable except those common to both.
109 # ### Methods for Comparing
111 # - [<=>](#method-i-3C-3D-3E):
112 # Returns -1, 0, or 1 as +self+ is less than, equal to,
113 # or greater than a given object.
114 # - [==](#method-i-3D-3D):
115 # Returns whether +self+ and a given enumerable are equal,
116 # as determined by Object#eql?.
117 # - \#compare_by_identity?:
118 # Returns whether the set considers only identity
119 # when comparing elements.
121 # ### Methods for Querying
123 # - \#length (aliased as #size):
124 # Returns the count of elements.
126 # Returns whether the set has no elements.
127 # - \#include? (aliased as #member? and #===):
128 # Returns whether a given object is an element in the set.
129 # - \#subset? (aliased as [<=](#method-i-3C-3D)):
130 # Returns whether a given object is a subset of the set.
131 # - \#proper_subset? (aliased as [<](#method-i-3C)):
132 # Returns whether a given enumerable is a proper subset of the set.
133 # - \#superset? (aliased as [>=](#method-i-3E-3D])):
134 # Returns whether a given enumerable is a superset of the set.
135 # - \#proper_superset? (aliased as [>](#method-i-3E)):
136 # Returns whether a given enumerable is a proper superset of the set.
138 # Returns +true+ if the set and a given enumerable
139 # have no common elements, +false+ otherwise.
141 # Returns +true+ if the set and a given enumerable:
142 # have any common elements, +false+ otherwise.
143 # - \#compare_by_identity?:
144 # Returns whether the set considers only identity
145 # when comparing elements.
147 # ### Methods for Assigning
149 # - \#add (aliased as #<<):
150 # Adds a given object to the set; returns +self+.
152 # If the given object is not an element in the set,
153 # adds it and returns +self+; otherwise, returns +nil+.
155 # Merges the elements of each given enumerable object to the set; returns +self+.
157 # Replaces the contents of the set with the contents
158 # of a given enumerable.
160 # ### Methods for Deleting
163 # Removes all elements in the set; returns +self+.
165 # Removes a given object from the set; returns +self+.
167 # If the given object is an element in the set,
168 # removes it and returns +self+; otherwise, returns +nil+.
170 # Removes each given object from the set; returns +self+.
171 # - \#delete_if - Removes elements specified by a given block.
172 # - \#select! (aliased as #filter!):
173 # Removes elements not specified by a given block.
175 # Removes elements not specified by a given block.
177 # Removes elements specified by a given block.
179 # ### Methods for Converting
182 # Returns a hash that classifies the elements,
183 # as determined by the given block.
184 # - \#collect! (aliased as #map!):
185 # Replaces each element with a block return-value.
187 # Returns a hash that classifies the elements,
188 # as determined by the given block;
189 # differs from #classify in that the block may accept
190 # either one or two arguments.
192 # Returns a new set that is a recursive flattening of +self+.
194 # Replaces each nested set in +self+ with the elements from that set.
195 # - \#inspect (aliased as #to_s):
196 # Returns a string displaying the elements.
198 # Returns a string containing all elements, converted to strings
199 # as needed, and joined by the given record separator.
201 # Returns an array containing all set elements.
203 # Returns +self+ if given no arguments and no block;
204 # with a block given, returns a new set consisting of block
207 # ### Methods for Iterating
210 # Calls the block with each successive element; returns +self+.
215 # Resets the internal state; useful if an object
216 # has been modified while an element in the set.
223 # Creates a new set containing the given objects.
225 # Set[1, 2] # => #<Set: {1, 2}>
226 # Set[1, 2, 1] # => #<Set: {1, 2}>
227 # Set[1, 'c', :s] # => #<Set: {1, "c", :s}>
232 # Creates a new set containing the elements of the given enumerable
235 # If a block is given, the elements of enum are preprocessed by the
238 # Set.new([1, 2]) #=> #<Set: {1, 2}>
239 # Set.new([1, 2, 1]) #=> #<Set: {1, 2}>
240 # Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}>
241 # Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}>
242 # Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}>
243 def initialize(enum = nil, &block) # :yields: o
244 @hash ||= Hash.new(false)
249 do_with_enum(enum) { |o| add(block[o]) }
255 # Makes the set compare its elements by their identity and returns
256 # self. This method may not be supported by all subclasses of Set.
257 def compare_by_identity
258 if @hash.respond_to?(:compare_by_identity)
259 @hash.compare_by_identity
262 raise NotImplementedError, "#{self.class.name}\##{__method__} is not implemented"
266 # Returns true if the set will compare its elements by their
267 # identity. Also see Set#compare_by_identity.
268 def compare_by_identity?
269 @hash.respond_to?(:compare_by_identity?) && @hash.compare_by_identity?
272 def do_with_enum(enum, &block) # :nodoc:
273 if enum.respond_to?(:each_entry)
274 enum.each_entry(&block) if block
275 elsif enum.respond_to?(:each)
276 enum.each(&block) if block
278 raise ArgumentError, "value must be enumerable"
281 private :do_with_enum
284 def initialize_dup(orig)
286 @hash = orig.instance_variable_get(:@hash).dup
289 # Clone internal hash.
290 def initialize_clone(orig, **options)
292 @hash = orig.instance_variable_get(:@hash).clone(**options)
300 # Returns the number of elements.
306 # Returns true if the set contains no elements.
311 # Removes all elements and returns self.
313 # set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
314 # set.clear #=> #<Set: {}>
321 # Replaces the contents of the set with the contents of the given
322 # enumerable object and returns self.
324 # set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
325 # set.replace([1, 2]) #=> #<Set: {1, 2}>
326 # set #=> #<Set: {1, 2}>
328 if enum.instance_of?(self.class)
329 @hash.replace(enum.instance_variable_get(:@hash))
332 do_with_enum(enum) # make sure enum is enumerable before calling clear
338 # Converts the set to an array. The order of elements is uncertain.
340 # Set[1, 2].to_a #=> [1, 2]
341 # Set[1, 'c', :s].to_a #=> [1, "c", :s]
346 # Returns self if no arguments are given. Otherwise, converts the
347 # set to another with `klass.new(self, *args, &block)`.
349 # In subclasses, returns `klass.new(self, *args, &block)` unless
351 def to_set(klass = Set, *args, &block)
352 return self if instance_of?(Set) && klass == Set && block.nil? && args.empty?
353 klass.new(self, *args, &block)
356 def flatten_merge(set, seen = Set.new) # :nodoc:
359 if seen.include?(e_id = e.object_id)
360 raise ArgumentError, "tried to flatten recursive Set"
364 flatten_merge(e, seen)
373 protected :flatten_merge
375 # Returns a new set that is a copy of the set, flattening each
376 # containing set recursively.
378 self.class.new.flatten_merge(self)
381 # Equivalent to Set#flatten, but replaces the receiver with the
382 # result in place. Returns nil if no modifications were made.
384 replace(flatten()) if any?(Set)
387 # Returns true if the set contains the given object.
389 # Note that <code>include?</code> and <code>member?</code> do not test member
390 # equality using <code>==</code> as do other Enumerables.
392 # See also Enumerable#include?
396 alias member? include?
398 # Returns true if the set is a superset of the given set.
401 when set.instance_of?(self.class) && @hash.respond_to?(:>=)
402 @hash >= set.instance_variable_get(:@hash)
404 size >= set.size && set.all?(self)
406 raise ArgumentError, "value must be a set"
411 # Returns true if the set is a proper superset of the given set.
412 def proper_superset?(set)
414 when set.instance_of?(self.class) && @hash.respond_to?(:>)
415 @hash > set.instance_variable_get(:@hash)
417 size > set.size && set.all?(self)
419 raise ArgumentError, "value must be a set"
422 alias > proper_superset?
424 # Returns true if the set is a subset of the given set.
427 when set.instance_of?(self.class) && @hash.respond_to?(:<=)
428 @hash <= set.instance_variable_get(:@hash)
430 size <= set.size && all?(set)
432 raise ArgumentError, "value must be a set"
437 # Returns true if the set is a proper subset of the given set.
438 def proper_subset?(set)
440 when set.instance_of?(self.class) && @hash.respond_to?(:<)
441 @hash < set.instance_variable_get(:@hash)
443 size < set.size && all?(set)
445 raise ArgumentError, "value must be a set"
448 alias < proper_subset?
450 # Returns 0 if the set are equal,
451 # -1 / +1 if the set is a proper subset / superset of the given set,
452 # or nil if they both have unique elements.
454 return unless set.is_a?(Set)
456 case size <=> set.size
457 when -1 then -1 if proper_subset?(set)
458 when +1 then +1 if proper_superset?(set)
459 else 0 if self.==(set)
463 # Returns true if the set and the given enumerable have at least one
466 # Set[1, 2, 3].intersect? Set[4, 5] #=> false
467 # Set[1, 2, 3].intersect? Set[3, 4] #=> true
468 # Set[1, 2, 3].intersect? 4..5 #=> false
469 # Set[1, 2, 3].intersect? [3, 4] #=> true
481 raise ArgumentError, "value must be enumerable"
485 # Returns true if the set and the given enumerable have
486 # no element in common. This method is the opposite of `intersect?`.
488 # Set[1, 2, 3].disjoint? Set[3, 4] #=> false
489 # Set[1, 2, 3].disjoint? Set[4, 5] #=> true
490 # Set[1, 2, 3].disjoint? [3, 4] #=> false
491 # Set[1, 2, 3].disjoint? 4..5 #=> true
496 # Calls the given block once for each element in the set, passing
497 # the element as parameter. Returns an enumerator if no block is
500 block_given? or return enum_for(__method__) { size }
501 @hash.each_key(&block)
505 # Adds the given object to the set and returns self. Use `merge` to
506 # add many elements at once.
508 # Set[1, 2].add(3) #=> #<Set: {1, 2, 3}>
509 # Set[1, 2].add([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
510 # Set[1, 2].add(2) #=> #<Set: {1, 2}>
517 # Adds the given object to the set and returns self. If the
518 # object is already in the set, returns nil.
520 # Set[1, 2].add?(3) #=> #<Set: {1, 2, 3}>
521 # Set[1, 2].add?([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
522 # Set[1, 2].add?(2) #=> nil
524 add(o) unless include?(o)
527 # Deletes the given object from the set and returns self. Use
528 # `subtract` to delete many items at once.
534 # Deletes the given object from the set and returns self. If the
535 # object is not in the set, returns nil.
537 delete(o) if include?(o)
540 # Deletes every element of the set for which block evaluates to
541 # true, and returns self. Returns an enumerator if no block is
544 block_given? or return enum_for(__method__) { size }
545 # @hash.delete_if should be faster, but using it breaks the order
546 # of enumeration in subclasses.
547 select { |o| yield o }.each { |o| @hash.delete(o) }
551 # Deletes every element of the set for which block evaluates to
552 # false, and returns self. Returns an enumerator if no block is
555 block_given? or return enum_for(__method__) { size }
556 # @hash.keep_if should be faster, but using it breaks the order of
557 # enumeration in subclasses.
558 reject { |o| yield o }.each { |o| @hash.delete(o) }
562 # Replaces the elements with ones returned by `collect()`.
563 # Returns an enumerator if no block is given.
565 block_given? or return enum_for(__method__) { size }
567 each { |o| set << yield(o) }
572 # Equivalent to Set#delete_if, but returns nil if no changes were
573 # made. Returns an enumerator if no block is given.
575 block_given? or return enum_for(__method__) { size }
581 # Equivalent to Set#keep_if, but returns nil if no changes were
582 # made. Returns an enumerator if no block is given.
584 block_given? or return enum_for(__method__) { size }
590 # Equivalent to Set#select!
591 alias filter! select!
593 # Merges the elements of the given enumerable objects to the set and
595 def merge(*enums, **nil)
597 if enum.instance_of?(self.class)
598 @hash.update(enum.instance_variable_get(:@hash))
600 do_with_enum(enum) { |o| add(o) }
607 # Deletes every element that appears in the given enumerable object
610 do_with_enum(enum) { |o| delete(o) }
614 # Returns a new set built by merging the set and the elements of the
615 # given enumerable object.
617 # Set[1, 2, 3] | Set[2, 4, 5] #=> #<Set: {1, 2, 3, 4, 5}>
618 # Set[1, 5, 'z'] | (1..6) #=> #<Set: {1, 5, "z", 2, 3, 4, 6}>
625 # Returns a new set built by duplicating the set, removing every
626 # element that appears in the given enumerable object.
628 # Set[1, 3, 5] - Set[1, 5] #=> #<Set: {3}>
629 # Set['a', 'b', 'z'] - ['a', 'c'] #=> #<Set: {"b", "z"}>
635 # Returns a new set containing elements common to the set and the
636 # given enumerable object.
638 # Set[1, 3, 5] & Set[3, 2, 1] #=> #<Set: {3, 1}>
639 # Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> #<Set: {"a", "b"}>
644 each { |o| n.add(o) if enum.include?(o) }
646 enum.each { |o| n.add(o) if include?(o) }
649 do_with_enum(enum) { |o| n.add(o) if include?(o) }
655 # Returns a new set containing elements exclusive between the set
656 # and the given enumerable object. `(set ^ enum)` is equivalent to
657 # `((set | enum) - (set & enum))`.
659 # Set[1, 2] ^ Set[2, 3] #=> #<Set: {3, 1}>
660 # Set[1, 'b', 'c'] ^ ['b', 'd'] #=> #<Set: {"d", 1, "c"}>
663 each { |o| n.add(o) unless n.delete?(o) }
667 # Returns true if two sets are equal. The equality of each couple
668 # of elements is defined according to Object#eql?.
670 # Set[1, 2] == Set[2, 1] #=> true
671 # Set[1, 3, 5] == Set[1, 5] #=> false
672 # Set['a', 'b', 'c'] == Set['a', 'c', 'b'] #=> true
673 # Set['a', 'b', 'c'] == ['a', 'c', 'b'] #=> false
675 if self.equal?(other)
677 elsif other.instance_of?(self.class)
678 @hash == other.instance_variable_get(:@hash)
679 elsif other.is_a?(Set) && self.size == other.size
680 other.all? { |o| @hash.include?(o) }
690 def eql?(o) # :nodoc:
691 return false unless o.is_a?(Set)
692 @hash.eql?(o.instance_variable_get(:@hash))
695 # Resets the internal state after modification to existing elements
698 # Elements will be reindexed and deduplicated.
700 if @hash.respond_to?(:rehash)
701 @hash.rehash # This should perform frozenness check.
703 raise FrozenError, "can't modify frozen #{self.class.name}" if frozen?
708 # Returns true if the given object is a member of the set,
709 # and false otherwise.
711 # Used in case statements:
716 # when Set[:potato, :carrot]
718 # when Set[:apple, :banana]
725 # Set[1, 2, 3] === 2 #=> true
726 # Set[1, 2, 3] === 4 #=> false
730 # Classifies the set by the return value of the given block and
731 # returns a hash of {value => set of elements} pairs. The block is
732 # called once for each element of the set, passing the element as
736 # files = Set.new(Dir.glob("*.rb"))
737 # hash = files.classify { |f| File.mtime(f).year }
738 # hash #=> {2000=>#<Set: {"a.rb", "b.rb"}>,
739 # # 2001=>#<Set: {"c.rb", "d.rb", "e.rb"}>,
740 # # 2002=>#<Set: {"f.rb"}>}
742 # Returns an enumerator if no block is given.
743 def classify # :yields: o
744 block_given? or return enum_for(__method__) { size }
749 (h[yield(i)] ||= self.class.new).add(i)
755 # Divides the set into a set of subsets according to the commonality
756 # defined by the given block.
758 # If the arity of the block is 2, elements o1 and o2 are in common
759 # if block.call(o1, o2) is true. Otherwise, elements o1 and o2 are
760 # in common if block.call(o1) == block.call(o2).
763 # numbers = Set[1, 3, 4, 6, 9, 10, 11]
764 # set = numbers.divide { |i,j| (i - j).abs == 1 }
765 # set #=> #<Set: {#<Set: {1}>,
766 # # #<Set: {11, 9, 10}>,
770 # Returns an enumerator if no block is given.
772 func or return enum_for(__method__) { size }
777 class << dig = {} # :nodoc:
780 alias tsort_each_node each_key
781 def tsort_each_child(node, &block)
782 fetch(node).each(&block)
788 each{ |v| func.call(u, v) and a << v }
792 dig.each_strongly_connected_component { |css|
793 set.add(self.class.new(css))
797 Set.new(classify(&func).values)
801 # Returns a string created by converting each element of the set to a string
802 # See also: Array#join
803 def join(separator=nil)
807 InspectKey = :__inspect_key__ # :nodoc:
809 # Returns a string containing a human-readable representation of the
810 # set ("#<Set: {element1, element2, ...}>").
812 ids = (Thread.current[InspectKey] ||= [])
814 if ids.include?(object_id)
815 return sprintf('#<%s: {...}>', self.class.name)
820 return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2])
828 def pretty_print(pp) # :nodoc:
829 pp.group(1, sprintf('#<%s:', self.class.name), '>') {
831 pp.group(1, '{', '}') {
832 pp.seplist(self) { |o|
839 def pretty_print_cycle(pp) # :nodoc:
840 pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...')
845 # Makes a set from the enumerable object with given arguments.
846 # Needs to `require "set"` to use this method.
847 def to_set(klass = Set, *args, &block)
848 klass.new(self, *args, &block)
849 end unless method_defined?(:to_set)
852 autoload :SortedSet, "#{__dir__}/set/sorted_set"