"An iterable collection of elements of finite [[size]], with 
 a well-defined notion of [[value equality|equals]]. 
 `Collection` is the abstract supertype of [[List]], [[Map]], 
 and [[Set]].
 
 A `Collection` forms a [[Category]] of its elements, and is 
 [[Iterable]]. The elements of a collection are not
 necessarily distinct when compared using [[Object.equals]].
 
 A `Collection` may be [[cloned|clone]]. If a collection is
 immutable, it is acceptable that `clone()` produce a
 reference to the collection itself. If a collection is
 mutable, `clone()` should produce a collection containing 
 references to the same elements, with the same structure as 
 the original collection—that is, it should produce a 
 shallow copy of the collection.
 
 All `Collection`s are required to support a well-defined
 notion of [[value equality|Object.equals]], but the
 definition of equality depends upon the kind of collection.
 Equality for `Map`s and `Set`s has a quite different
 definition to equality for `List`s. Instances of two 
 different kinds of collection are never equal—for
 example, a `Map` is never equal to a `List`."
see (`interface List`, `interface Map`, `interface Set`)
tagged("Collections")
shared interface Collection<out Element=Anything>
        satisfies {Element*} {
    
    "A shallow copy of this collection, that is, a 
     collection with identical elements which does not
     change if this collection changes. If this collection
     is immutable, it is acceptable to return a reference to
     this collection. If this collection is mutable, a newly
     instantiated collection must be returned."
    shared formal Collection<Element> clone();
    
    "Determine if the collection is empty, that is, if it 
     has no elements."
    shared actual default Boolean empty => size==0;
    
    "Return `true` if the given object is an element of this 
     collection. In this default implementation, and in most 
     refining implementations, return `false` otherwise. An 
     acceptable refining implementation may return `true` 
     for objects which are not elements of the collection, 
     but this is not recommended. (For example, the 
     `contains()` method of `String` returns `true` for any 
     substring of the string.)"
    shared actual default Boolean contains(Object element) {
        for (elem in this) {
            if (exists elem, elem==element) {
                return true;
            }
        }
        else {
            return false;
        }
    }
    
    "A string of form `\"{ x, y, z }\"` where `x`, `y`, and 
     `z` are the `string` representations of the elements of 
     this collection, as produced by the iterator of the 
     collection, or the string `\"{}\"` if this collection 
     is empty. If the collection iterator produces the value 
     `null`, the string representation contains the string 
     `\"<null>\"`."
    shared actual default String string
            => empty then "{}" else "{ ``commaList(this)`` }";
    
    "The permutations of this collection, as a stream of
     nonempty [[sequences|Sequence]]. That is, a stream
     producing every distinct ordering of the elements of
     this collection.
     
     For example,
     
         \"ABC\".permutations.map(String)
     
     is the stream of strings
     `{ \"ABC\", \"ACB\", \"BAC\", \"BCA\", \"CAB\", \"CBA\" }`.
     
     If this collection is empty, the resulting stream is
     empty.
     
     The permutations are enumerated lexicographically
     according to the order in which each distinct element 
     of this collection is first produced by its iterator.
     No permutation is repeated.
     
     Two elements are considered distinct if either:
     
     - they are both instances of `Object`, and are 
       [[unequal|Object.equals]], or
     - one element is an `Object` and the other is `null`."
    since("1.2.0")
    shared {[Element+]*} permutations 
            => object satisfies {[Element+]*} {
        value multiset =
            outer
            .indexed
            .group((_->item) => item else nullElement)
            .items
            .sort((x,y) => x.first.key<=>y.first.key)
            .indexed
            .flatMap((index->entries) 
                => entries.map((_->item) => index->item));
        
        empty => multiset.empty;
        
        iterator() 
                => object satisfies Iterator<[Element+]> {
            value elements = Array(multiset);
            value size = elements.size;
            
            variable value initial = true;
            
            shared actual [Element+]|Finished next() {
                if (initial) {
                    initial = false;
                }
                else if (exists i -> [key->_, __]
                        = elements.paired.locateLast(
                            ([m->_, n->__]) => m < n)) {
                    assert (exists j
                        = elements.lastIndexWhere(
                            (k->_) => k > key));
                    elements.swap(i,j);
                    for (k in 0 : (size-i-1)/2) {
                        elements.swap(i+1+k, size-1-k);
                    }
                }
                else {
                    return finished;
                }
                return
                    if (nonempty permutation 
                        = elements*.item) 
                    then permutation 
                    else finished;
            }
        };
    };
    
    "The combinations of elements of this collection, of the
     given positive [[size|length]], as a stream of nonempty 
     [[sequences|Sequence]]. That is, a stream producing 
     every distinct selection of `length` elements of this 
     collection.
     
     For example,
     
         \"ABCD\".combinations(2).map(String)
     
     is the stream of strings
     `{ \"AB\", \"AC\", \"AD\", \"BC\", \"BD\", \"CD\" }`.
     
     If this collection is empty, the resulting stream is
     empty.
     
     The combinations are enumerated lexicographically
     according to the order in which each distinct element 
     of this collection is first produced by its iterator.
     No combination is repeated.
     
     Two elements are considered distinct if either:
     
     - they are both instances of `Object`, and are 
       [[unequal|Object.equals]], or
     - one element is an `Object` and the other is `null`."
    throws (`class AssertionError`, 
            "if [[length]] is nonpositive or if `length` is
             larger than the number of distinct elements of
             this collection")
    since("1.3.0")
    shared {[Element+]*} combinations(
                "The number of distinct elements in each
                 combination"
                Integer length) {
        "length must be strictly positive"
        assert (length>0);
        return object satisfies {[Element+]*} {
            value distinctElements = outer.distinct;
            
            empty => outer.empty;
            
            iterator()
                    => object satisfies Iterator<[Element+]> {
                
                value elements = distinctElements.sequence();
                value size = elements.size;
                
                "length is larger than the number of distinct elements"
                assert (length <= size);
                
                value selection = Array(0:length);
                variable value done = elements.empty;
                
                shared actual [Element+]|Finished next() {
                    if (done) {
                        return finished;
                    }
                    value current = selection.collect((i) {
                        if (exists e 
                            = elements.getFromFirst(i)) {
                            return e;
                        }
                        else {
                            assert (is Element null);
                            return null;
                        }
                    });
                    assert (nonempty current);
                    
                    variable value i = length-1;
                    while (true) {
                        if (i<0) {
                            done = true;
                            break;
                        }
                        assert (exists s 
                                = selection.getFromFirst(i));
                        if (s == size-length+i) {
                            i--;
                        }
                        else {
                            variable value j = s;
                            while (i<length) {
                                selection.set(i++, ++j);
                            }
                            break;
                        }
                    }
                    
                    return current;
                }
                
            };
            
        };
    }
    
}

"Used by [[Collection.permutations]] to group nulls together."
object nullElement {}