All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.Multisets Maven / Gradle / Ivy

Go to download

Google Collections Library is a suite of new collections and collection-related goodness for Java 5.0

The newest version!
/*
 * 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;

/**
 * Provides static utility methods for creating and working with {@link
 * Multiset} instances.
 *
 * @author Kevin Bourrillion
 * @author Mike Bostock
 */
public final class Multisets {
  private static final Multiset EMPTY_MULTISET = new EmptyMultiset();
  private static final Multiset IMMUTABLE_EMPTY_MULTISET =
      unmodifiableMultiset(new EmptyMultiset());

  private Multisets() {}

  /**
   * Creates an empty {@code HashMultiset} using the default initial capacity
   * (16 distinct elements).
   */
  public static  HashMultiset newHashMultiset() {
    return new HashMultiset();
  }

  /**
   * Creates a {@code HashMultiset} containing the specified elements, using the
   * default initial capacity (16 distinct elements).
   *
   * @param elements the elements that the multiset should contain
   */
  public static  HashMultiset newHashMultiset(E... elements) {
    HashMultiset multiset = new HashMultiset();
    Collections.addAll(multiset, elements);
    return multiset;
  }

  /**
   * Creates a {@code HashMultiset} containing the specified elements.
   *
   * @param elements the elements that the multiset should contain
   */
  public static  HashMultiset newHashMultiset(
      Iterable elements) {
    return new HashMultiset(elements);
  }

  /**
   * Creates an empty {@code TreeMultiset} instance.
   *
   * @return a newly-created, initially-empty TreeMultiset
   */
  @SuppressWarnings("unchecked")  // allow ungenerified Comparable types
  public static  TreeMultiset newTreeMultiset() {
    return new TreeMultiset();
  }

  /**
   * Creates an empty {@code TreeMultiset} instance, sorted according to the
   * specified comparator.
   *
   * @return a newly-created, initially-empty TreeMultiset
   */
  public static  TreeMultiset newTreeMultiset(Comparator c) {
    return new TreeMultiset(c);
  }

  /** Creates an empty {@code EnumMultiset}. */
  public static > EnumMultiset newEnumMultiset(
      Class type) {
    return new EnumMultiset(type);
  }

  /**
   * Creates an {@code EnumMultiset} containing the specified elements.
   *
   * @throws IllegalArgumentException if {@code elements} is empty
   */
  public static > EnumMultiset newEnumMultiset(
      Iterable elements) {
    return new EnumMultiset(elements);
  }

  /**
   * Creates an {@code EnumMultiset} containing the specified elements.
   *
   * @throws IllegalArgumentException if {@code elements} is empty
   */
  public static > EnumMultiset newEnumMultiset(
      E... elements) {
    checkArgument(elements.length > 0,
        "newEnumMultiset requires at least one element");
    EnumMultiset multiset = newEnumMultiset(elements[0].getDeclaringClass());
    Collections.addAll(multiset, elements);
    return multiset;
  }

  /**
   * Returns an unmodifiable view of the specified multiset. Query operations on
   * the returned multiset "read through" to the specified multiset, and
   * attempts to modify the returned multiset result in an
   * {@link UnsupportedOperationException}.
   *
   * @param multiset the multiset for which an unmodifiable view is to be
   *     generated
   * @return an unmodifiable view of the multiset
   */
  public static  Multiset unmodifiableMultiset(Multiset multiset) {
    return new UnmodifiableMultiset(multiset);
  }

  private static class UnmodifiableMultiset extends ForwardingMultiset {
    transient Set elementSet;

    private UnmodifiableMultiset(Multiset delegate) {
      super(delegate);
    }

    @Override public Set elementSet() {
      Set es = elementSet;
      return (es == null)
          ? elementSet = Collections.unmodifiableSet(super.elementSet())
          : es;
    }

    transient Set> entrySet;

    @Override public Set> entrySet() {
      Set> es = entrySet;
      return (es == null)
          ? entrySet = Collections.unmodifiableSet(super.entrySet())
          : es;
    }

    @Override public Iterator iterator() {
      return Iterators.unmodifiableIterator(super.iterator());
    }

    @Override public boolean add(E element) {
      throw new UnsupportedOperationException();
    }

    @Override public boolean add(E element, int occurences) {
      throw new UnsupportedOperationException();
    }

    @Override public boolean addAll(Collection elementsToAdd) {
      throw new UnsupportedOperationException();
    }

    @Override public boolean remove(Object element) {
      throw new UnsupportedOperationException();
    }

    @Override public int remove(Object element, int occurrences) {
      throw new UnsupportedOperationException();
    }

    @Override public int removeAllOccurrences(Object element) {
      throw new UnsupportedOperationException();
    }

    @Override public boolean removeAll(Collection elementsToRemove) {
      throw new UnsupportedOperationException();
    }

    @Override public boolean retainAll(Collection elementsToRetain) {
      throw new UnsupportedOperationException();
    }

    @Override public void clear() {
      throw new UnsupportedOperationException();
    }

    private static final long serialVersionUID = 0;    
  }

  /**
   * Returns a synchronized (thread-safe) multiset backed by the specified
   * multiset. In order to guarantee serial access, all access to the
   * backing multiset must go through the returned multiset.
   *
   * 

It is imperative that the user manually synchronize on the returned * multiset when iterating over any of its collection views: * *

  Multiset<E> m = Multisets.synchronizedMultiset(
   *      new HashMultiset<E>());
   *   ...
   *  Set<E> s = m.elementSet(); // Needn't be in synchronized block
   *   ...
   *  synchronized (m) { // Synchronizing on m, not s!
   *    Iterator<E> i = s.iterator(); // Must be in synchronized block
   *    while (i.hasNext()) {
   *      foo(i.next());
   *    }
   *  }
* * Failure to follow this advice may result in non-deterministic behavior. * *

For a greater degree of concurrency, you may use a {@link * ConcurrentMultiset}. * * @param multiset the multiset to be wrapped * @return a synchronized view of the specified multiset */ public static Multiset synchronizedMultiset(Multiset multiset) { return Synchronized.multiset(multiset, null); } /** Returns the empty multiset (immutable). This multiset is serializable. */ @SuppressWarnings("unchecked") public static Multiset emptyMultiset() { return (Multiset) EMPTY_MULTISET; } /** @see Multisets#emptyMultiset */ private static class EmptyMultiset extends AbstractCollection implements Multiset, Serializable { @Override public int size() { return 0; } @Override public Iterator iterator() { return Iterators.emptyIterator(); } public int count(Object element) { return 0; } public boolean add(E element, int occurrences) { throw new UnsupportedOperationException(); } /* * The remove methods return 0, for consistency with * Collections.emptySet().remove(). */ public int remove(Object element, int occurrences) { return 0; } public int removeAllOccurrences(Object element) { return 0; } public Set elementSet() { return Collections.emptySet(); } public Set> entrySet() { return Collections.emptySet(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Multiset)) { return false; } return ((Multiset) obj).isEmpty(); } @Override public int hashCode() { return 0; } @Override public String toString() { return "[]"; } private Object readResolve() { return EMPTY_MULTISET; // preserve singleton property } private static final long serialVersionUID = -4387083049544049902L; } /** * Returns an immutable multiset entry with the specified element and count. * * @param e the element to be associated with the returned entry * @param n the count to be associated with the returned entry * @throws IllegalArgumentException if {@code n} is negative */ public static Multiset.Entry immutableEntry(final E e, final int n) { checkArgument(n >= 0); return new AbstractMultisetEntry() { public E getElement() { return e; } public int getCount() { return n; } }; } /** * Returns a multiset view of the specified set. The multiset is backed by the * set, so changes to the set are reflected in the multiset, and vice versa. * If the set is modified while an iteration over the multiset is in progress * (except through the iterator's own {@code remove} operation) the results of * the iteration are undefined. * *

The multiset supports element removal, which removes the corresponding * element from the set. It does not support the {@code add} or {@code addAll} * operations. * *

The returned multiset will be serializable if the specified set is * serializable. * * @param set the backing set for the returned multiset view */ public static Multiset forSet(Set set) { return new SetMultiset(set); } /** @see Multisets#forSet */ private static class SetMultiset extends ForwardingCollection implements Multiset, Serializable { transient volatile Set elementSet; transient volatile Set> entrySet; SetMultiset(Set set) { super(set); } @Override protected Set delegate() { return (Set) super.delegate(); } public int count(Object element) { return delegate().contains(element) ? 1 : 0; } public boolean add(E element, int occurrences) { throw new UnsupportedOperationException(); } public int remove(Object element, int occurrences) { if (occurrences == 0) { return 0; } checkArgument(occurrences > 0); return removeAllOccurrences(element); } public int removeAllOccurrences(Object element) { return delegate().remove(element) ? 1 : 0; } public Set elementSet() { if (elementSet == null) { elementSet = new ElementSet(); } return elementSet; } public Set> entrySet() { if (entrySet == null) { entrySet = new EntrySet(); } return entrySet; } @Override public boolean add(E o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof Multiset)) { return false; } Multiset m = (Multiset) o; return size() == m.size() && delegate().equals(m.elementSet()); } @Override public int hashCode() { int sum = 0; for (E e : this) { sum += ((e == null) ? 0 : e.hashCode()) ^ 1; } return sum; } /** @see SetMultiset#elementSet */ class ElementSet extends NonSerializableForwardingSet { ElementSet() { super(SetMultiset.this.delegate()); } @Override public boolean add(E o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } } /** @see SetMultiset#entrySet */ class EntrySet extends AbstractSet> { @Override public int size() { return delegate().size(); } @Override public Iterator> iterator() { return new Iterator>() { final Iterator elements = delegate().iterator(); public boolean hasNext() { return elements.hasNext(); } public Entry next() { return immutableEntry(elements.next(), 1); } public void remove() { elements.remove(); } }; } // TODO: faster contains, remove? } private static final long serialVersionUID = 7787490547740866319L; } /** * Returns the expected number of distinct elements given the specified * elements. The number of distinct elements is only computed if {@code * elements} is an instance of {@code Multiset}; otherwise the default value * of 11 is returned. */ static int inferDistinctElements(Iterable elements) { if (elements instanceof Multiset) { return ((Multiset) elements).elementSet().size(); } return 11; // initial capacity will be rounded up to 16 } /** * Returns an immutable empty {@code Multiset}. Equivalent to {@link * Multisets#emptyMultiset}, except that the returned multiset's * {@code remove} methods throw an {@link UnsupportedOperationException}. */ @SuppressWarnings("unchecked") public static Multiset immutableMultiset() { return (Multiset) IMMUTABLE_EMPTY_MULTISET; } /** * Returns an immutable {@code Multiset} containing the specified elements. * *

Unlike an unmodifiable multiset such as that returned by {@link * Multisets#unmodifiableMultiset}, which provides a read-only view of an * underlying multiset which may itself be mutable, an immutable * multiset makes a copy of the original mappings, so that the returned * multiset is guaranteed never to change. This is critical, for * example, if the multiset is an element of a {@code HashSet} or a key in a * {@code HashMap}. * * @param elements the elements that the returned multiset should contain */ public static Multiset immutableMultiset(E... elements) { return (elements.length == 0) ? Multisets.immutableMultiset() : unmodifiableMultiset(newHashMultiset(elements)); } /** * Returns a comparator that orders elements according to their increasing * frequency in a multiset. For example, suppose {@code m} is a non-empty * multiset of strings. Then: * *

  Collections.max(m.elementSet(), frequencyOrder(m));
* * returns a string that occurs most frequently in {@code m}. * (Warning: in this example, {@code Collections.max} throws * {@code NoSuchElementException} when {@code m} is empty.) * *

The returned comparator is a view into the backing multiset, so the * comparator's behavior will change if the backing multiset changes. This can * be dangerous; for example, if the comparator is used by a {@code TreeSet} * and the backing multiset changes, the behavior of the {@code TreeSet} * becomes undefined. Use a copy of the multiset to isolate against such * changes when necessary. * * @param multiset the multiset specifying the frequencies of the objects to * compare */ public static Comparator frequencyOrder(Multiset multiset) { return new FrequencyOrder(multiset); } /** @see Multisets#frequencyOrder(Multiset) */ private static class FrequencyOrder implements SerializableComparator { final Multiset multiset; FrequencyOrder(Multiset multiset) { this.multiset = checkNotNull(multiset); } public int compare(T left, T right) { int leftCount = multiset.count(left); int rightCount = multiset.count(right); return Comparators.compare(leftCount, rightCount); } @Override public boolean equals(Object object) { if (object instanceof FrequencyOrder) { FrequencyOrder that = (FrequencyOrder) object; return (this.multiset).equals(that.multiset); } return false; } @Override public int hashCode() { return multiset.hashCode(); } private static final long serialVersionUID = -6424503578659119387L; } }