# <!-- rdoc-file=lib/set.rb -->
# This library provides the Set class, which implements a collection
# of unordered values with no duplicates. It is a hybrid of Array's
# intuitive inter-operation facilities and Hash's fast lookup.
# The method `to_set` is added to Enumerable for convenience.
# Set is easy to use with Enumerable objects (implementing `each`).
# Most of the initializer methods and binary operators accept generic
# Enumerable objects besides sets and arrays. 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#eql? and
#      Object#hash. Use Set#compare_by_identity to make a set compare
#      its elements by their identity.
# *   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.
# *   When a string is to be stored, a frozen copy of the string is
#      stored instead unless the original string is already frozen.
# ## Comparison
# The comparison operators `<`, `>`, `<=`, and `>=` are implemented as
# shorthand for the {proper_,}{subset?,superset?} methods. The `<=>`
# operator reflects this order, or return `nil` for sets that both
# have distinct elements (`{x, y}` vs. `{x, z}` for example).
# ## Example
#     require 'set'
# s1 = Set[1, 2]                        #=> #<Set: {1, 2}>
# s2 = [1, 2].to_set                    #=> #<Set: {1, 2}>
# s1 == s2                              #=> true
# s1.add("foo")                         #=> #<Set: {1, 2, "foo"}>
# s1.merge([2, 6])                      #=> #<Set: {1, 2, "foo", 6}>
# s1.subset?(s2)                        #=> false
# s2.subset?(s1)                        #=> true
#
# ## Contact
# *   Akinori MUSHA <mailto:knu@iDaemons.org> (current maintainer)
# ## What's Here
# First, what's elsewhere. Class Set:
# *   Inherits from [class Object](rdoc-ref:Object@What-27s+Here).
# *   Includes [module Enumerable](rdoc-ref:Enumerable@What-27s+Here),
#      which provides dozens of additional methods.
# In particular, class Set does not have many methods of its own
# for fetching or for iterating.
# Instead, it relies on those in Enumerable.
# Here, class Set provides methods that are useful for:
# *   [Creating a Set](#class-Set-label-Methods+for+Creating+a+Set)
# *   [Set Operations](#class-Set-label-Methods+for+Set+Operations)
# *   [Comparing](#class-Set-label-Methods+for+Comparing)
# *   [Querying](#class-Set-label-Methods+for+Querying)
# *   [Assigning](#class-Set-label-Methods+for+Assigning)
# *   [Deleting](#class-Set-label-Methods+for+Deleting)
# *   [Converting](#class-Set-label-Methods+for+Converting)
# *   [Iterating](#class-Set-label-Methods+for+Iterating)
# *   [And more....](#class-Set-label-Other+Methods)
# ### Methods for Creating a Set
# *   ::[]:
#      Returns a new set containing the given objects.
# *   ::new:
#      Returns a new set containing either the given objects
#      (if no block given) or the return values from the called block
#      (if a block given).
# ### Methods for Set Operations
# *   [|](#method-i-7C) (aliased as #union and #+):
#      Returns a new set containing all elements from `self`
#      and all elements from a given enumerable (no duplicates).
# *   [&](#method-i-26) (aliased as #intersection):
#      Returns a new set containing all elements common to `self`
#      and a given enumerable.
# *   [-](#method-i-2D) (aliased as #difference):
#      Returns a copy of `self` with all elements
#      in a given enumerable removed.
# *   [\^](#method-i-5E):
#      Returns a new set containing all elements from `self`
#      and a given enumerable except those common to both.
# ### Methods for Comparing
# *   [<=>](#method-i-3C-3D-3E):
#      Returns -1, 0, or 1 as `self` is less than, equal to,
#      or greater than a given object.
# *   [==](#method-i-3D-3D):
#      Returns whether `self` and a given enumerable are equal,
#      as determined by Object#eql?.
# *   #compare_by_identity?:
#      Returns whether the set considers only identity
#      when comparing elements.
# ### Methods for Querying
# *   #length (aliased as #size):
#      Returns the count of elements.
# *   #empty?:
#      Returns whether the set has no elements.
# *   #include? (aliased as #member? and #===):
#      Returns whether a given object is an element in the set.
# *   #subset? (aliased as [<=](#method-i-3C-3D)):
#      Returns whether a given object is a subset of the set.
# *   #proper_subset? (aliased as [<](#method-i-3C)):
#      Returns whether a given enumerable is a proper subset of the set.
# *   #superset? (aliased as [>=](#method-i-3E-3D)]):
#      Returns whether a given enumerable is a superset of the set.
# *   #proper_superset? (aliased as [>](#method-i-3E)):
#      Returns whether a given enumerable is a proper superset of the set.
# *   #disjoint?:
#      Returns `true` if the set and a given enumerable
#      have no common elements, `false` otherwise.
# *   #intersect?:
#      Returns `true` if the set and a given enumerable:
#      have any common elements, `false` otherwise.
# *   #compare_by_identity?:
#      Returns whether the set considers only identity
#      when comparing elements.
# ### Methods for Assigning
# *   #add (aliased as #<<):
#      Adds a given object to the set; returns `self`.
# *   #add?:
#      If the given object is not an element in the set,
#      adds it and returns `self`; otherwise, returns `nil`.
# *   #merge:
#      Merges the elements of each given enumerable object to the set; returns
#     `self`.
# *   #replace:
#      Replaces the contents of the set with the contents
#      of a given enumerable.
# ### Methods for Deleting
# *   #clear:
#      Removes all elements in the set; returns `self`.
# *   #delete:
#      Removes a given object from the set; returns `self`.
# *   #delete?:
#      If the given object is an element in the set,
#      removes it and returns `self`; otherwise, returns `nil`.
# *   #subtract:
#      Removes each given object from the set; returns `self`.
# *   #delete_if - Removes elements specified by a given block.
# *   #select! (aliased as #filter!):
#      Removes elements not specified by a given block.
# *   #keep_if:
#      Removes elements not specified by a given block.
# *   #reject!
#      Removes elements specified by a given block.
# ### Methods for Converting
# *   #classify:
#      Returns a hash that classifies the elements,
#      as determined by the given block.
# *   #collect! (aliased as #map!):
#      Replaces each element with a block return-value.
# *   #divide:
#      Returns a hash that classifies the elements,
#      as determined by the given block;
#      differs from #classify in that the block may accept
#      either one or two arguments.
# *   #flatten:
#      Returns a new set that is a recursive flattening of `self`.
#      #flatten!:
#      Replaces each nested set in `self` with the elements from that set.
# *   #inspect (aliased as #to_s):
#      Returns a string displaying the elements.
# *   #join:
#      Returns a string containing all elements, converted to strings
#      as needed, and joined by the given record separator.
# *   #to_a:
#      Returns an array containing all set elements.
# *   #to_set:
#      Returns `self` if given no arguments and no block;
#      with a block given, returns a new set consisting of block
#      return values.
# ### Methods for Iterating
# *   #each:
#      Calls the block with each successive element; returns `self`.
# ### Other Methods
# *   #reset:
#      Resets the internal state; useful if an object
#      has been modified while an element in the set.
#
class Set[unchecked out A]
  include Enumerable[A]

  # <!--
  #   rdoc-file=lib/set.rb
  #   - new(enum = nil) { |o| ... }
  # -->
  # Creates a new set containing the elements of the given enumerable
  # object.
  # If a block is given, the elements of enum are preprocessed by the
  # given block.
  #     Set.new([1, 2])                       #=> #<Set: {1, 2}>
  #     Set.new([1, 2, 1])                    #=> #<Set: {1, 2}>
  #     Set.new([1, 'c', :s])                 #=> #<Set: {1, "c", :s}>
  #     Set.new(1..5)                         #=> #<Set: {1, 2, 3, 4, 5}>
  #     Set.new([1, 2, 3]) { |x| x * x }      #=> #<Set: {1, 4, 9}>
  #
  def initialize: (_Each[A]) -> untyped
                | [X] (_Each[X]) { (X) -> A } -> untyped
                | (?nil) -> untyped

  # <!--
  #   rdoc-file=lib/set.rb
  #   - [](*ary)
  # -->
  # Creates a new set containing the given objects.
  #     Set[1, 2]                   # => #<Set: {1, 2}>
  #     Set[1, 2, 1]                # => #<Set: {1, 2}>
  #     Set[1, 'c', :s]             # => #<Set: {1, "c", :s}>
  #
  def self.[]: [X] (*X) -> Set[X]

  # <!--
  #   rdoc-file=lib/set.rb
  #   - &(enum)
  # -->
  # Returns a new set containing elements common to the set and the
  # given enumerable object.
  #     Set[1, 3, 5] & Set[3, 2, 1]             #=> #<Set: {3, 1}>
  #     Set['a', 'b', 'z'] & ['a', 'b', 'c']    #=> #<Set: {"a", "b"}>
  #
  def &: (_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - intersection(enum)
  # -->
  #
  alias intersection &

  # <!--
  #   rdoc-file=lib/set.rb
  #   - |(enum)
  # -->
  # Returns a new set built by merging the set and the elements of the
  # given enumerable object.
  #     Set[1, 2, 3] | Set[2, 4, 5]         #=> #<Set: {1, 2, 3, 4, 5}>
  #     Set[1, 5, 'z'] | (1..6)             #=> #<Set: {1, 5, "z", 2, 3, 4, 6}>
  #
  def |: (_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - union(enum)
  # -->
  #
  alias union |

  # <!--
  #   rdoc-file=lib/set.rb
  #   - +(enum)
  # -->
  #
  alias + |

  # <!--
  #   rdoc-file=lib/set.rb
  #   - -(enum)
  # -->
  # Returns a new set built by duplicating the set, removing every
  # element that appears in the given enumerable object.
  #     Set[1, 3, 5] - Set[1, 5]                #=> #<Set: {3}>
  #     Set['a', 'b', 'z'] - ['a', 'c']         #=> #<Set: {"b", "z"}>
  #
  def -: (_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - difference(enum)
  # -->
  #
  alias difference -

  # <!--
  #   rdoc-file=lib/set.rb
  #   - add(o)
  # -->
  # Adds the given object to the set and returns self. Use `merge` to
  # add many elements at once.
  #     Set[1, 2].add(3)                    #=> #<Set: {1, 2, 3}>
  #     Set[1, 2].add([3, 4])               #=> #<Set: {1, 2, [3, 4]}>
  #     Set[1, 2].add(2)                    #=> #<Set: {1, 2}>
  #
  def add: (A) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - <<(o)
  # -->
  #
  alias << add

  # <!--
  #   rdoc-file=lib/set.rb
  #   - add?(o)
  # -->
  # Adds the given object to the set and returns self. If the
  # object is already in the set, returns nil.
  #     Set[1, 2].add?(3)                    #=> #<Set: {1, 2, 3}>
  #     Set[1, 2].add?([3, 4])               #=> #<Set: {1, 2, [3, 4]}>
  #     Set[1, 2].add?(2)                    #=> nil
  #
  def add?: (A) -> self?

  # <!--
  #   rdoc-file=lib/set.rb
  #   - include?(o)
  # -->
  # Returns true if the set contains the given object.
  # Note that `include?` and `member?` do not test member
  # equality using `==` as do other Enumerables.
  # See also Enumerable#include?
  #
  def include?: (A) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - member?(o)
  # -->
  #
  alias member? include?

  # <!--
  #   rdoc-file=lib/set.rb
  #   - ^(enum)
  # -->
  # Returns a new set containing elements exclusive between the set
  # and the given enumerable object. `(set ^ enum)` is equivalent to
  # `((set | enum) - (set & enum))`.
  #     Set[1, 2] ^ Set[2, 3]                   #=> #<Set: {3, 1}>
  #     Set[1, 'b', 'c'] ^ ['b', 'd']           #=> #<Set: {"d", 1, "c"}>
  #
  def ^: (_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - classify() { |o| ... }
  # -->
  # Classifies the set by the return value of the given block and
  # returns a hash of {value => set of elements} pairs. The block is
  # called once for each element of the set, passing the element as
  # parameter.
  #     require 'set'
  #     files = Set.new(Dir.glob("*.rb"))
  #     hash = files.classify { |f| File.mtime(f).year }
  #     hash       #=> {2000=>#<Set: {"a.rb", "b.rb"}>,
  #                #    2001=>#<Set: {"c.rb", "d.rb", "e.rb"}>,
  #                #    2002=>#<Set: {"f.rb"}>}
  #
  # Returns an enumerator if no block is given.
  #
  def classify: [X] () { (A) -> X } -> Hash[X, self]

  # <!--
  #   rdoc-file=lib/set.rb
  #   - clear()
  # -->
  # Removes all elements and returns self.
  #     set = Set[1, 'c', :s]             #=> #<Set: {1, "c", :s}>
  #     set.clear                         #=> #<Set: {}>
  #     set                               #=> #<Set: {}>
  #
  def clear: () -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - collect!() { |o| ... }
  # -->
  # Replaces the elements with ones returned by `collect()`.
  # Returns an enumerator if no block is given.
  #
  def collect!: () { (A) -> A } -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - map!()
  # -->
  #
  alias map! collect!

  # <!--
  #   rdoc-file=lib/set.rb
  #   - delete(o)
  # -->
  # Deletes the given object from the set and returns self. Use
  # `subtract` to delete many items at once.
  #
  def delete: (A) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - delete?(o)
  # -->
  # Deletes the given object from the set and returns self. If the
  # object is not in the set, returns nil.
  #
  def delete?: (A) -> self?

  # <!--
  #   rdoc-file=lib/set.rb
  #   - delete_if(&block)
  # -->
  # Deletes every element of the set for which block evaluates to
  # true, and returns self. Returns an enumerator if no block is
  # given.
  #
  def delete_if: () { (A) -> untyped } -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - reject!(&block)
  # -->
  # Equivalent to Set#delete_if, but returns nil if no changes were
  # made. Returns an enumerator if no block is given.
  #
  def reject!: () { (A) -> untyped } -> self?

  # <!--
  #   rdoc-file=lib/set.rb
  #   - compare_by_identity()
  # -->
  # Makes the set compare its elements by their identity and returns
  # self. This method may not be supported by all subclasses of Set.
  #
  def compare_by_identity: () -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - disjoint?(set)
  # -->
  # Returns true if the set and the given enumerable have
  # no element in common. This method is the opposite of `intersect?`.
  #     Set[1, 2, 3].disjoint? Set[3, 4]   #=> false
  #     Set[1, 2, 3].disjoint? Set[4, 5]   #=> true
  #     Set[1, 2, 3].disjoint? [3, 4]      #=> false
  #     Set[1, 2, 3].disjoint? 4..5        #=> true
  #
  def disjoint?: (Set[A] | Enumerable[A]) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - divide(&func)
  # -->
  # Divides the set into a set of subsets according to the commonality
  # defined by the given block.
  # If the arity of the block is 2, elements o1 and o2 are in common
  # if block.call(o1, o2) is true. Otherwise, elements o1 and o2 are
  # in common if block.call(o1) == block.call(o2).
  #     require 'set'
  #     numbers = Set[1, 3, 4, 6, 9, 10, 11]
  #     set = numbers.divide { |i,j| (i - j).abs == 1 }
  #     set        #=> #<Set: {#<Set: {1}>,
  #                #           #<Set: {11, 9, 10}>,
  #                #           #<Set: {3, 4}>,
  #                #           #<Set: {6}>}>
  #
  # Returns an enumerator if no block is given.
  #
  def divide: () { (A, A) -> Hash::_Key } -> Set[self]
            | () { (A) -> Hash::_Key } -> Set[self]

  # <!--
  #   rdoc-file=lib/set.rb
  #   - each(&block)
  # -->
  # Calls the given block once for each element in the set, passing
  # the element as parameter. Returns an enumerator if no block is
  # given.
  #
  def each: () { (A) -> void } -> self
          | () -> Enumerator[A, self]

  # <!--
  #   rdoc-file=lib/set.rb
  #   - empty?()
  # -->
  # Returns true if the set contains no elements.
  #
  def empty?: () -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - flatten()
  # -->
  # Returns a new set that is a copy of the set, flattening each
  # containing set recursively.
  #
  def flatten: () -> Set[untyped]

  # <!--
  #   rdoc-file=lib/set.rb
  #   - intersect?(set)
  # -->
  # Returns true if the set and the given enumerable have at least one
  # element in common.
  #     Set[1, 2, 3].intersect? Set[4, 5]   #=> false
  #     Set[1, 2, 3].intersect? Set[3, 4]   #=> true
  #     Set[1, 2, 3].intersect? 4..5        #=> false
  #     Set[1, 2, 3].intersect? [3, 4]      #=> true
  #
  def intersect?: (Set[A] | Enumerable[A]) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - keep_if(&block)
  # -->
  # Deletes every element of the set for which block evaluates to
  # false, and returns self. Returns an enumerator if no block is
  # given.
  #
  def keep_if: () { (A) -> untyped } -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - size()
  # -->
  # Returns the number of elements.
  #
  def size: () -> Integer

  # <!--
  #   rdoc-file=lib/set.rb
  #   - length()
  # -->
  #
  alias length size

  # <!--
  #   rdoc-file=lib/set.rb
  #   - merge(*enums, **nil)
  # -->
  # Merges the elements of the given enumerable objects to the set and
  # returns self.
  #
  def merge: (*_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - subset?(set)
  # -->
  # Returns true if the set is a subset of the given set.
  #
  def subset?: (self) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - proper_subset?(set)
  # -->
  # Returns true if the set is a proper subset of the given set.
  #
  def proper_subset?: (self) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - superset?(set)
  # -->
  # Returns true if the set is a superset of the given set.
  #
  def superset?: (self) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - proper_superset?(set)
  # -->
  # Returns true if the set is a proper superset of the given set.
  #
  def proper_superset?: (self) -> bool

  # <!--
  #   rdoc-file=lib/set.rb
  #   - replace(enum)
  # -->
  # Replaces the contents of the set with the contents of the given
  # enumerable object and returns self.
  #     set = Set[1, 'c', :s]             #=> #<Set: {1, "c", :s}>
  #     set.replace([1, 2])               #=> #<Set: {1, 2}>
  #     set                               #=> #<Set: {1, 2}>
  #
  def replace: (_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - reset()
  # -->
  # Resets the internal state after modification to existing elements
  # and returns self.
  # Elements will be reindexed and deduplicated.
  #
  def reset: () -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - select!(&block)
  # -->
  # Equivalent to Set#keep_if, but returns nil if no changes were
  # made. Returns an enumerator if no block is given.
  #
  def select!: () { (A) -> untyped } -> self?

  # <!--
  #   rdoc-file=lib/set.rb
  #   - subtract(enum)
  # -->
  # Deletes every element that appears in the given enumerable object
  # and returns self.
  #
  def subtract: (_Each[A]) -> self

  # <!--
  #   rdoc-file=lib/set.rb
  #   - to_a()
  # -->
  # Returns an array containing all elements in the set.
  #     Set[1, 2].to_a                    #=> [1, 2]
  #     Set[1, 'c', :s].to_a              #=> [1, "c", :s]
  #
  def to_a: () -> Array[A]
end

%a{annotate:rdoc:skip}
module Enumerable[unchecked out Elem]
  # <!--
  #   rdoc-file=lib/set.rb
  #   - to_set(klass = Set, *args, &block)
  # -->
  # Makes a set from the enumerable object with given arguments.
  # Needs to `require "set"` to use this method.
  #
  def to_set: () -> Set[Elem]
            | [T] () { (Elem) -> T } -> Set[T]
end
