# `Set` implements a collection of values with no duplicates. # # An `Enumerable` object can be converted to `Set` using the `#to_set` method. # # `Set` uses `Hash` as storage, so you must note the following points: # # * Equality of elements is determined according to `Object#==` and `Object#hash`. # * Enumeration follows the order that the elements were inserted. # * `Set` assumes that the identity of each element does not change while it is stored. Modifying an element of a set will render the set to an unreliable state. # # ### Example # # ``` # s1 = Set{1, 2} # s2 = [1, 2].to_set # s3 = Set.new [1, 2] # s1 == s2 # => true # s1 == s3 # => true # s1.add(2) # s1.concat([6, 8]) # s1.subset_of? s2 # => false # s2.subset_of? s1 # => true # ``` struct Set(T) include Enumerable(T) include Iterable(T) # Creates a new, empty `Set`. # # ``` # s = Set(Int32).new # s.empty? # => true # ``` # # An initial capacity can be specified, and it will be set as the initial capacity # of the internal `Hash`. def initialize(initial_capacity = nil) @hash = Hash(T, Nil).new(initial_capacity: initial_capacity) end protected def initialize(*, using_hash @hash : Hash(T, Nil)) end # Optimized version of `new` used when *other* is also an `Indexable` def self.new(other : Indexable(T)) Set(T).new(other.size).concat(other) end # Creates a new set from the elements in *enumerable*. # # ``` # a = [1, 3, 5] # s = Set.new a # s.empty? # => false # ``` def self.new(enumerable : Enumerable(T)) Set(T).new.concat(enumerable) end # Makes this set compare objects using their object identity (`object_id)` # for types that define such method (`Reference` types, but also structs that # might wrap other `Reference` types and delegate the `object_id` method to them). # # ``` # s = Set{"foo", "bar"} # s.includes?("fo" + "o") # => true # # s.compare_by_identity # s.compare_by_identity? # => true # s.includes?("fo" + "o") # => false # not the same String instance # ``` def compare_by_identity : self @hash.compare_by_identity self end # Returns `true` if this Set is comparing objects by `object_id`. # # See `compare_by_identity`. def compare_by_identity? : Bool @hash.compare_by_identity? end # Alias for `add` def <<(object : T) : self add object end # Adds *object* to the set and returns `self`. # # ``` # s = Set{1, 5} # s.includes? 8 # => false # s.add(8) # s.includes? 8 # => true # ``` def add(object : T) : self @hash[object] = nil self end # Adds *object* to the set and returns `true` on success # and `false` if the value was already in the set. # # ``` # s = Set{1, 5} # s.add? 8 # => true # s.add? 8 # => false # ``` def add?(object : T) : Bool @hash.put(object, nil) { return true } false end # Adds `#each` element of *elems* to the set and returns `self`. # # ``` # s = Set{1, 5} # s.concat [5, 5, 8, 9] # s.size # => 4 # ``` # # See also: `#|` to merge two sets and return a new one. def concat(elems) : self elems.each { |elem| self << elem } self end # Returns `true` if *object* exists in the set. # # ``` # s = Set{1, 5} # s.includes? 5 # => true # s.includes? 9 # => false # ``` def includes?(object) : Bool @hash.has_key?(object) end # Removes the *object* from the set and returns `true` if it was present, otherwise returns `false`. # # ``` # s = Set{1, 5} # s.includes? 5 # => true # s.delete 5 # => true # s.includes? 5 # => false # s.delete 5 # => false # ``` def delete(object) : Bool @hash.delete(object) { return false } true end # Returns the number of elements in the set. # # ``` # s = Set{1, 5} # s.size # => 2 # ``` def size : Int32 @hash.size end # Removes all elements in the set, and returns `self`. # # ``` # s = Set{1, 5} # s.size # => 2 # s.clear # s.size # => 0 # ``` def clear : self @hash.clear self end # Returns `true` if the set is empty. # # ``` # s = Set(Int32).new # s.empty? # => true # s << 3 # s.empty? # => false # ``` def empty? : Bool @hash.empty? end # Yields each element of the set, and returns `nil`. def each(& : T ->) : Nil @hash.each_key do |key| yield key end end # Returns an iterator for each element of the set. def each @hash.each_key end # Intersection: returns a new set containing elements common to both sets. # # ``` # Set{1, 1, 3, 5} & Set{1, 2, 3} # => Set{1, 3} # Set{'a', 'b', 'b', 'z'} & Set{'a', 'b', 'c'} # => Set{'a', 'b'} # ``` def &(other : Set) : Set(T) smallest, largest = self, other if largest.size < smallest.size smallest, largest = largest, smallest end set = Set(T).new smallest.each do |value| set.add value if largest.includes?(value) end set end # Union: returns a new set containing all unique elements from both sets. # # ``` # Set{1, 1, 3, 5} | Set{1, 2, 3} # => Set{1, 3, 5, 2} # Set{'a', 'b', 'b', 'z'} | Set{'a', 'b', 'c'} # => Set{'a', 'b', 'z', 'c'} # ``` # # See also: `#concat` to add elements from a set to `self`. def |(other : Set(U)) : Set(T | U) forall U set = Set(T | U).new(Math.max(size, other.size)) each { |value| set.add value } other.each { |value| set.add value } set end # Addition: returns a new set containing the unique elements from both sets. # # ``` # Set{1, 1, 2, 3} + Set{3, 4, 5} # => Set{1, 2, 3, 4, 5} # ``` def +(other : Set(U)) : Set(T | U) forall U self | other end # Returns the additive identity of this type. # # This is an empty set. def self.additive_identity : self new end # Difference: returns a new set containing elements in this set that are not # present in the other. # # ``` # Set{1, 2, 3, 4, 5} - Set{2, 4} # => Set{1, 3, 5} # Set{'a', 'b', 'b', 'z'} - Set{'a', 'b', 'c'} # => Set{'z'} # ``` def -(other : Set) : Set(T) set = Set(T).new each do |value| set.add value unless other.includes?(value) end set end # Difference: returns a new set containing elements in this set that are not # present in the other enumerable. # # ``` # Set{1, 2, 3, 4, 5} - [2, 4] # => Set{1, 3, 5} # Set{'a', 'b', 'b', 'z'} - ['a', 'b', 'c'] # => Set{'z'} # ``` def -(other : Enumerable) : Set(T) dup.subtract other end # Symmetric Difference: returns a new set `(self - other) | (other - self)`. # Equivalently, returns `(self | other) - (self & other)`. # # ``` # Set{1, 2, 3, 4, 5} ^ Set{2, 4, 6} # => Set{1, 3, 5, 6} # Set{'a', 'b', 'b', 'z'} ^ Set{'a', 'b', 'c'} # => Set{'z', 'c'} # ``` def ^(other : Set(U)) : Set(T | U) forall U set = Set(T | U).new each do |value| set.add value unless other.includes?(value) end other.each do |value| set.add value unless includes?(value) end set end # Symmetric Difference: returns a new set `(self - other) | (other - self)`. # Equivalently, returns `(self | other) - (self & other)`. # # ``` # Set{1, 2, 3, 4, 5} ^ [2, 4, 6] # => Set{1, 3, 5, 6} # Set{'a', 'b', 'b', 'z'} ^ ['a', 'b', 'c'] # => Set{'z', 'c'} # ``` def ^(other : Enumerable(U)) : Set(T | U) forall U set = Set(T | U).new(self) other.each do |value| if includes?(value) set.delete value else set.add value end end set end # Returns `self` after removing from it those elements that are present in # the given enumerable. # # ``` # Set{'a', 'b', 'b', 'z'}.subtract Set{'a', 'b', 'c'} # => Set{'z'} # Set{1, 2, 3, 4, 5}.subtract [2, 4, 6] # => Set{1, 3, 5} # ``` def subtract(other : Enumerable) : self other.each do |value| delete value end self end # Returns `true` if both sets have the same elements. # # ``` # Set{1, 5} == Set{1, 5} # => true # ``` def ==(other : Set) : Bool same?(other) || @hash == other.@hash end # Same as `#includes?`. # # It is for convenience with using on `case` statement. # # ``` # red_like = Set{"red", "pink", "violet"} # blue_like = Set{"blue", "azure", "violet"} # # case "violet" # when red_like & blue_like # puts "red & blue like color!" # when red_like # puts "red like color!" # when blue_like # puts "blue like color!" # end # ``` # # See also: `Object#===`. def ===(object : T) : Bool includes? object end # Returns a new `Set` with all of the same elements. def dup : Set(T) set = Set(T).new(using_hash: @hash.dup) set.compare_by_identity if compare_by_identity? set end # Returns a new `Set` with all of the elements cloned. def clone : Set(T) clone = Set(T).new(self.size) clone.compare_by_identity if compare_by_identity? each do |element| clone << element.clone end clone end # Returns the elements as an `Array`. # # ``` # Set{1, 5}.to_a # => [1,5] # ``` def to_a : Array(T) @hash.keys end # Returns an `Array` with the results of running *block* against each element of the collection. # # ``` # Set{1, 2, 3, 4, 5}.to_a { |i| i // 2 } # => [0, 1, 1, 2, 2] # ``` def to_a(& : T -> U) : Array(U) forall U array = Array(U).new(size) @hash.each_key do |key| array << yield key end array end # Alias of `#to_s`. def inspect(io : IO) : Nil to_s(io) end def pretty_print(pp) : Nil pp.list("Set{", self, "}") end # See `Object#hash(hasher)` def_hash @hash # Returns `true` if the set and the given set have at least one element in # common. # # ``` # Set{1, 2, 3}.intersects? Set{4, 5} # => false # Set{1, 2, 3}.intersects? Set{3, 4} # => true # ``` def intersects?(other : Set) : Bool if size < other.size any? { |o| other.includes?(o) } else other.any? { |o| includes?(o) } end end # Writes a string representation of the set to *io*. def to_s(io : IO) : Nil io << "Set{" join io, ", ", &.inspect(io) io << '}' end # Returns `true` if the set is a subset of the *other* set. # # This set must have the same or fewer elements than the *other* set, and all # of elements in this set must be present in the *other* set. # # ``` # Set{1, 5}.subset_of? Set{1, 3, 5} # => true # Set{1, 3, 5}.subset_of? Set{1, 3, 5} # => true # ``` def subset_of?(other : Set) : Bool return false if other.size < size all? { |value| other.includes?(value) } end # Returns `true` if the set is a proper subset of the *other* set. # # This set must have fewer elements than the *other* set, and all # of elements in this set must be present in the *other* set. # # ``` # Set{1, 5}.proper_subset_of? Set{1, 3, 5} # => true # Set{1, 3, 5}.proper_subset_of? Set{1, 3, 5} # => false # ``` def proper_subset_of?(other : Set) : Bool return false if other.size <= size all? { |value| other.includes?(value) } end # Returns `true` if the set is a superset of the *other* set. # # The *other* must have the same or fewer elements than this set, and all of # elements in the *other* set must be present in this set. # # ``` # Set{1, 3, 5}.superset_of? Set{1, 5} # => true # Set{1, 3, 5}.superset_of? Set{1, 3, 5} # => true # ``` def superset_of?(other : Set) : Bool other.subset_of?(self) end # Returns `true` if the set is a superset of the *other* set. # # The *other* must have fewer elements than this set, and all of # elements in the *other* set must be present in this set. # # ``` # Set{1, 3, 5}.proper_superset_of? Set{1, 5} # => true # Set{1, 3, 5}.proper_superset_of? Set{1, 3, 5} # => false # ``` def proper_superset_of?(other : Set) : Bool other.proper_subset_of?(self) end # :nodoc: def object_id : UInt64 @hash.object_id end # :nodoc: def same?(other : Set) : Bool @hash.same?(other.@hash) end # Replaces every element of the set with a value returned from the block, and # returns `self`. def map!(& : T -> T) : self @hash.transform_keys! { |k, _| yield(k) } self end # Deletes every element of the set for which the block is falsey, and returns # `self`. def select!(& : T ->) : self @hash.select! { |k, _| yield(k) } self end # Deletes every element of the set for which the block is truthy, and returns # `self`. def reject!(& : T ->) : self @hash.reject! { |k, _| yield(k) } self end # Rebuilds the set based on the current elements. # # When using mutable data types as elements, modifying an elements after it # was inserted into the `Set` may lead to undefined behaviour. This method # re-indexes the set using the current elements. def rehash : Nil @hash.rehash end end module Enumerable # Returns a new `Set` with each unique element in the enumerable. def to_set : Set(T) Set.new(self) end # Returns a new `Set` with the unique results of running *block* against each # element of the enumerable. def to_set(&block : T -> U) : Set(U) forall U set = Set(U).new each do |elem| set << yield elem end set end end