com.google.common.collect.SortedArraySet Maven / Gradle / Ivy
/*
* Copyright (C) 2007 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.collect;
import com.google.common.base.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.SortedSet;
/**
* A sorted set that keeps its elements in a sorted {@code ArrayList}. Null
* elements are allowed when the {@code SortedArraySet} is constructed with an
* explicit comparator that supports nulls.
*
* This class is useful when you may have many sorted sets that only have
* zero or one elements each. The performance of this implementation does not
* scale to large numbers of elements as well as {@link java.util.TreeSet}, but
* it is much more memory-efficient per entry.
*
*
Each {@code SortedArraySet} has a capacity, because it is backed by
* an {@code ArrayList}. The capacity is the size of the array used to store the
* elements in the set. It is always at least as large as the set size. As
* elements are added to the set, its capacity grows automatically. The details
* of the growth policy are not specified beyond the fact that adding an element
* has O(lg n) amortized time cost.
*
*
An application can increase the capacity of the set before adding a large
* number of elements using the {@code ensureCapacity} operation. This may
* reduce the amount of incremental reallocation.
*
*
This implementation is not synchronized. As with {@code ArrayList},
* external synchronization is needed if multiple threads access a {@code
* SortedArraySet} instance concurrently and at least one adds or deletes any
* elements.
*
* @author Matthew Harris
* @author Mike Bostock
*/
public final class SortedArraySet extends AbstractSet
implements SortedSet, Serializable {
private ArrayList contents; // initialized lazily
private final Comparator super E> comparator;
/**
* Constructs a new empty sorted set, sorted according to the element's
* natural order, with an initial capacity of ten. All elements inserted into
* the set must implement the {@code Comparable} interface. Furthermore, all
* such elements must be mutally comparable: {@code e1.compareTo(e2)}
* must not throw a {@code ClassCastException} for any elements {@code e1} and
* {@code e2} in the set. If the user attempts to add an element to the set
* that violates this constraint (for example, the user attempts to add a
* string element to a set whose elements are integers), the {@code add}
* method may throw a {@code ClassCastException}.
*
* @see Comparable
* @see Comparators#naturalOrder
*/
public SortedArraySet() {
this(10);
}
/**
* Constructs a new empty sorted set, sorted according to the element's
* natural order, with the specified initial capacity. All elements inserted
* into the set must implement the {@code Comparable} interface. Furthermore,
* all such elements must be mutually comparable: {@code
* e1.compareTo(e2)} must not throw a {@code ClassCastException} for any
* elements {@code e1} and {@code e2} in the set. If the user attempts to add
* an element to the set that violates this constraint (for example, the user
* attempts to add a string element to a set whose elements are integers), the
* {@code add} method may throw a {@code ClassCastException}.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if {@code initialCapacity} is negative
* @see Comparators#naturalOrder
*/
public SortedArraySet(int initialCapacity) {
checkArgument(initialCapacity >= 0);
comparator = orNaturalOrder(null);
if (initialCapacity > 0) {
contents = new ArrayList(initialCapacity);
}
}
/**
* Creates a new empty sorted set, sorted according to the specified
* comparator, with the initial capacity of ten. All elements inserted into
* the set must be mutually comparable by the specified comparator:
* {@code comparator.compare(e1,e2)} must not throw a {@code
* ClassCastException} for any elements {@code e1} and {@code e2} in the set.
* If the user attempts to add an element to the set that violates this
* constraint, the {@code add} method may throw a {@code ClassCastException}.
*
* @param comparator the comparator used to sort elements in this set
*/
public SortedArraySet(Comparator super E> comparator) {
this(comparator, 10);
}
/**
* Creates a new empty sorted set, sorted according to the specified
* comparator, with the specified initial capacity. All elements inserted into
* the set must be mutually comparable by the specified comparator:
* {@code comparator.compare(e1,e2)} must not throw a {@code
* ClassCastException} for any elements {@code e1} and {@code e2} in the set.
* If the user attempts to add an element to the set that violates this
* constraint, the {@code add} method may throw a {@code ClassCastException}.
*
* @param comparator the comparator used to sort elements in this set
*/
public SortedArraySet(Comparator super E> comparator, int initialCapacity) {
checkNotNull(comparator);
this.comparator = comparator;
if (initialCapacity > 0) {
contents = new ArrayList(initialCapacity);
}
}
/**
* Creates a new sorted set with the same elements as the specified
* collection. If the specified collection is a {@code SortedSet} instance,
* this constructor behaves identically to {@link #SortedArraySet(SortedSet)}.
* Otherwise, the elements are sorted according to the elements' natural
* order; see {@link #SortedArraySet()}.
*
* @param collection the elements that will comprise the new set
* @throws ClassCastException if the elements in the specified collection are
* not mutually comparable
*/
@SuppressWarnings("unchecked")
public SortedArraySet(Collection extends E> collection) {
if (collection instanceof SortedSet>) {
comparator = orNaturalOrder(((SortedSet) collection).comparator());
} else {
comparator = orNaturalOrder(null);
}
addAll(collection); // careful if we make this class non-final
}
/**
* Creates a new sorted set with the same elements and the same ordering as
* the specified sorted set.
*
* @param set the set whose elements will comprise the new set
*/
public SortedArraySet(SortedSet set) {
comparator = orNaturalOrder(set.comparator());
addAll(set); // careful if we make this class non-final
}
/**
* Increases the capacity of this sorted set, if necessary, to ensure that it
* can hold at least the number of elements specified by the minimum capacity
* argument.
*
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
if (contents == null) {
if (minCapacity > 0) {
contents = new ArrayList(minCapacity);
}
} else {
contents.ensureCapacity(minCapacity);
}
}
/**
* Trims the capacity of this sorted set to be the set's current size. An
* application can use this operation to minimize the storage of a sorted
* set.
*/
public void trimToSize() {
if (size() == 0) {
contents = null;
} else {
contents.trimToSize();
}
}
@Override public boolean add(E o) {
if (contents == null) {
contents = new ArrayList(1);
contents.add(o);
return true;
}
int pos = binarySearch(o);
if (pos < 0) {
contents.add(-pos - 1, o);
return true;
}
return false;
}
@Override public boolean addAll(Collection extends E> c) {
// optimize the case where c is sorted and we're empty
if (((contents == null) || contents.isEmpty())
&& !c.isEmpty() && (c instanceof SortedSet>)) {
Comparator> comparator2 = ((SortedSet>) c).comparator();
if (((comparator == Comparators.naturalOrder())
&& (comparator2 == null)) ||
(comparator == comparator2)) {
if (contents == null) {
contents = new ArrayList(c);
} else {
contents.addAll(c);
}
return true;
}
}
return super.addAll(c);
}
@Override public void clear() {
contents = null;
}
@Override public boolean contains(Object o) {
return binarySearch(o) >= 0;
}
@Override public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof SortedArraySet>) {
SortedArraySet> set = (SortedArraySet>) o;
if (comparator == set.comparator) {
int n = size();
return (n == set.size()) // beware of null contents
&& ((n == 0) || contents.equals(set.contents));
}
}
return super.equals(o);
}
@Override public Iterator iterator() {
return (contents == null)
? Iterators.emptyIterator() : contents.iterator();
}
@Override public boolean remove(Object o) {
int pos = binarySearch(o);
if (pos < 0) {
return false;
}
contents.remove(pos);
return true;
}
@Override public int size() {
return (contents == null) ? 0 : contents.size();
}
/**
* Returns the comparator associated with this sorted set, or {@code
* Comparators.naturalOrder} if it uses its elements' natural ordering.
*/
public Comparator super E> comparator() {
return comparator;
}
public SortedSet subSet(E fromElement, E toElement) {
checkArgument(comparator.compare(toElement, fromElement) >= 0);
return new SubSet(fromElement, toElement, true, true);
}
public SortedSet headSet(E toElement) {
return new SubSet(null, toElement, false, true);
}
public SortedSet tailSet(E fromElement) {
return new SubSet(fromElement, null, true, false);
}
public E first() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return get(0);
}
public E last() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return get(size() - 1);
}
/**
* Searches the backing list of the specified object using the binary search
* algorithm.
*
* This method runs in O(lg n) time.
*
* @param o the object to be searched for
* @return the index of the found object, if it is contained in the list;
* otherwise, {@code (-(insertionpoint) - 1)}. The insertion point
* is defined as the point at which the object would be inserted into the
* list: the index of the first element greater than the key, or {@code
* list.size()}, if all elements in the list are less than the specified
* key. Note that this guarantees that the return value will be >= 0 if
* and only if the key is found.
* @throws ClassCastException if the list contains elements that are not
* mutually comparable (for example, strings and integers), or the
* specified object is not mutually comparable with the elements of the
* list
* @see Collections#binarySearch(java.util.List, Object, Comparator)
*/
private int binarySearch(Object o) {
if (contents == null) {
return -1;
}
@SuppressWarnings("unchecked")
E e = (E) o;
return Collections.binarySearch(contents, e, comparator);
}
/**
* Returns the element at the specified position in the backing list.
*
* @param index the index of the element to return
* @throws NoSuchElementException if the specified index is out of bounds
*/
private E get(int index) {
if (contents == null) {
throw new NoSuchElementException();
}
try {
return contents.get(index);
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
/**
* Returns the specified comparator if not null; otherwise returns {@code
* Comparators.naturalOrder}. This method is an abomination of generics; the
* only purpose of this method is to contain the ugly type-casting in one
* place.
*/
@SuppressWarnings("unchecked")
private Comparator super E> orNaturalOrder(
@Nullable Comparator super E> comparator) {
if (comparator != null) { // can't use ? : because of javac bug 5080917
return comparator;
}
return (Comparator) Comparators.naturalOrder();
}
/** @see #subSet */
private class SubSet extends AbstractSet implements SortedSet {
final E head;
final E tail;
final boolean hasHead;
final boolean hasTail;
/**
* Constructs a subset view into the SortedArraySet.
*
* @param fromElement the low endpoint (inclusive) of the subset, or
* {@code null}
* @param toElement the high endpoint (exclusive) of the subset, or
* {@code null}
* @param hasHead whether this subset has a lower bound
* @param hasTail whether this subset has an upper bound
*/
SubSet(@Nullable E fromElement, @Nullable E toElement,
boolean hasHead, boolean hasTail) {
this.head = fromElement;
this.tail = toElement;
this.hasHead = hasHead;
this.hasTail = hasTail;
}
/**
* Returns the index of the low endpoint (inclusive) of the subset, or zero
* if the low endpoint is undefined.
*/
int headIndex() {
if (!hasHead) {
return 0;
}
int pos = binarySearch(head);
return (pos < 0) ? (-pos - 1) : pos;
}
/**
* Returns the position of the high endpoint (exclusive) of the subset, or
* the size of the list if the high endpoint is undefined.
*/
int tailIndex() {
if (contents == null) {
return 0;
}
if (!hasTail) {
return contents.size();
}
int pos = binarySearch(tail);
return (pos < 0) ? (-pos - 1) : pos;
}
/**
* Throws an {@code IllegalArgumentException} if the head of this subset
* does not precede or equal the specified element.
*
* @param fromElement the element to compare to the head
*/
void checkHead(E fromElement) {
if (hasHead) {
checkArgument(comparator.compare(fromElement, head) >= 0);
}
}
/**
* Throws an {@code IllegalArgumentException} if the specified element does
* not precede the tail of this subset.
*
* @param toElement the element to compare to the tail
*/
void checkTail(E toElement) {
if (hasTail) {
checkArgument(comparator.compare(tail, toElement) > 0);
}
}
@Override public int size() {
return tailIndex() - headIndex();
}
public Comparator super E> comparator() {
return comparator;
}
@Override public Iterator iterator() {
return (contents == null) ?
Iterators.emptyIterator() :
contents.subList(headIndex(), tailIndex()).iterator();
}
@Override public boolean contains(Object o) {
@SuppressWarnings("unchecked") // throws ClassCastException
E e = (E) o;
if ((hasHead && (comparator.compare(e, head) < 0))
|| (hasTail && (comparator.compare(tail, e) <= 0))) {
return false;
}
return SortedArraySet.this.contains(o);
}
public SortedSet subSet(E fromElement, E toElement) {
checkArgument(comparator.compare(toElement, fromElement) >= 0);
checkHead(fromElement);
checkTail(toElement);
return new SubSet(fromElement, toElement, true, true);
}
public SortedSet headSet(E toElement) {
checkHead(toElement);
checkTail(toElement);
return new SubSet(head, toElement, hasHead, true);
}
public SortedSet tailSet(E fromElement) {
checkHead(fromElement);
checkTail(fromElement);
return new SubSet(fromElement, tail, true, hasTail);
}
public E first() {
E o = get(headIndex());
if (hasTail && (comparator.compare(tail, o) <= 0)) {
throw new NoSuchElementException();
}
return o;
}
public E last() {
E o = get(tailIndex() - 1);
if (hasHead && (comparator.compare(o, head) < 0)) {
throw new NoSuchElementException();
}
return o;
}
}
private static final long serialVersionUID = -296929484947694088L;
}