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

com.bbn.bue.common.collections.CollectionUtils Maven / Gradle / Ivy

The newest version!
package com.bbn.bue.common.collections;

import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.math.IntMath;

import java.math.RoundingMode;
import java.util.AbstractCollection;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Utilities for collections.
 *
 * @author rgabbard
 */
public final class CollectionUtils {

  private CollectionUtils() {
    throw new UnsupportedOperationException();
  }

  /**
   * Takes some collections and creates a map from their elements to which collections contain them.
   * The Collections must be disjoint or an {@link java.lang.IllegalArgumentException} will be
   * thrown.
   */
  public static > Map makeElementsToContainersMap(
      final Iterable collections) {
    final ImmutableMap.Builder ret = ImmutableMap.builder();

    for (final C collection : collections) {
      for (final T item : collection) {
        ret.put(item, collection);
      }
    }

    return ret.build();
  }

  /**
   * Takes some collections and creates a {@link com.google.common.collect.ListMultimap} from their
   * elements to which collections contain them.  Unlike {@link #makeElementsToContainersMap(Iterable)},
   * the same element may appear in multiple collections. However, {@code null} may not appear in
   * any of the collections.  The returned multimap is a {@link com.google.common.collect.ListMultimap}
   * to avoid having to do potentially expensive comparisons between the sets; we know there will be
   * no duplicates because the input collections are sets.
   */
  public static > ImmutableListMultimap makeSetElementsToContainersMultimap(
      final Iterable sets) {
    // because these are sets we can safely use a list multimap without having to worry
    // about duplicates
    final ImmutableListMultimap.Builder ret = ImmutableListMultimap.builder();

    for (final C set : sets) {
      for (final V item : set) {
        ret.put(item, set);
      }
    }

    return ret.build();
  }


  /**
   * Returns a new Multiset resulting from transforming each element of the input Multiset by a
   * function. If two or more elements are mapped to the same value by the function, their counts
   * will be summed in the new Multiset.
   */
  public static  ImmutableMultiset transformedCopy(Multiset ms,
      Function func) {
    final ImmutableMultiset.Builder ret = ImmutableMultiset.builder();

    for (final Multiset.Entry entry : ms.entrySet()) {
      final B transformedElement = func.apply(entry.getElement());
      ret.addCopies(transformedElement, entry.getCount());
    }

    return ret.build();
  }

  /**
   * Same as transformedCopy, except the returned Multiset is mutable.
   */
  public static  Multiset mutableTransformedCopy(Multiset ms,
      Function func) {
    final Multiset ret = HashMultiset.create();

    for (final Multiset.Entry entry : ms.entrySet()) {
      final B transformedElement = func.apply(entry.getElement());
      ret.add(transformedElement, entry.getCount());
    }

    return ret;
  }

  /**
   * Gets a sublist of a list, truncated at the end of the list if too many elements are selected.
   * This behaves exactly like List.subList, including all notes in its Javadoc concerning
   * structural modification of the backing List, etc. with one difference: if the end index is
   * beyond the end of the list, instead of throwing an exception, the sublist simply stops at the
   * end of the list.  After the fifth or so time writing this idiom, it seems worth having a
   * function for. :-)
   */
  public static  List truncatedSubList(List inList, int start, int end) {
    // List.sublist will do our error checking for us
    final int limit = Math.min(end, inList.size());
    return inList.subList(start, limit);
  }

  /**
   * Returns true if and only if all the collections in the provided list have the same size.
   * Returns true if the provided list is empty.
   */
  public static boolean allSameSize(List> collections) {
    if (collections.isEmpty()) {
      return true;
    }
    final int referenceSize = collections.get(0).size();
    for (final Collection col : collections) {
      if (col.size() != referenceSize) {
        return false;
      }
    }
    return true;
  }

  /**
   * Guava function to get the value of a {@link com.google.common.collect.Table} cell.
   */
  public static  Function, V> TableCellValue() {
    return new Function, V>() {
      @Override
      public V apply(Table.Cell input) {
        return input.getValue();
      }
    };
  }

  /**
   * Guava function to map a collection to its size. Prefer {@link #sizeFunction()}.
   *
   * @deprecated
   */
  public static final Function, Integer> Size =
      new Function, Integer>() {
        @Override
        public Integer apply(Collection input) {
          return input.size();
        }
      };

  /**
   * Guava function to map a collection to its size.
   */
  public static final Function, Integer> sizeFunction() {
    return Size;
  }

  /**
   * Like {@link Collections#max(java.util.Collection)} except with a default value returned in the
   * case of an empty collection.
   */
  public static > T maxOr(Collection values, T defaultVal) {
    if (values.isEmpty()) {
      return defaultVal;
    } else {
      return Collections.max(values);
    }
  }

  /**
   * Like {@link Collections#min(java.util.Collection)} except with a default value returned in the
   * case of an empty collection.
   */
  public static > T minOr(Collection values, T defaultVal) {
    if (values.isEmpty()) {
      return defaultVal;
    } else {
      return Collections.min(values);
    }
  }

  /**
   * Provides a {@link com.google.common.base.Function} which will create a set containing the same
   * elements as the supplied collection.
   */
  public static  Function, ImmutableSet> asSetFunction() {
    return new Function, ImmutableSet>() {
      @Override
      public ImmutableSet apply(Collection input) {
        return ImmutableSet.copyOf(input);
      }
    };
  }

  /**
   * A copy of the input as an {@link com.google.common.collect.ImmutableList} which respects
   * iteration order, but where only the first occurrence of each element is kept. No input items
   * may be {@code null}.
   */
  public static  ImmutableList asUniquedList(Iterable items) {
    return ImmutableSet.copyOf(items).asList();
  }

  /**
   * Turns null into an empty list and leaves other inputs untouched.
   */
  public static  List coerceNullToEmpty(List list) {
    return MoreObjects.firstNonNull(list, ImmutableList.of());
  }

  /**
   * A Guava {@link Predicate} which calls {@link Collection#isEmpty()} on provided collections.
   */
  public static Predicate> isEmptyPredicate() {
    return new Predicate>() {
      @Override
      public boolean apply(final Collection input) {
        return input.isEmpty();
      }
    };
  }

  /**
   * Partitions a list into the specified number of partitions as evenly as is possible. The final
   * "extra" elements that cannot be evenly distributed are distributed starting with the first
   * partitions. For example, three partitions of (1, 2, 3, 4) results in ((1, 4), (2), (3)).
   * Unlike {@link Lists#partition(List, int)}, this returns {@link ImmutableList}s, not list views,
   * and computations are computed eagerly.
   *
   * @param partitions the number of partitions to divide the list into
   * @return a list of the partitions, which are themselves lists
   */
  public static  ImmutableList> partitionAlmostEvenly(final List list,
      final int partitions) {
    checkNotNull(list);
    checkArgument(partitions > 0,
        "Number of partitions must be positive");
    checkArgument(partitions <= list.size(),
        "Cannot request more partitions than there are list items");

    // Divide into partitions, with the remainder falling into the last partition
    final List> prelimPartitions =
        Lists.partition(list, IntMath.divide(list.size(), partitions, RoundingMode.DOWN));
    // Create output
    final ImmutableList.Builder> ret = ImmutableList.builder();

    // If we evenly partitioned, just do the type conversion and return. The type conversion is
    // performed because Lists#partition returns list views.
    if (prelimPartitions.size() == partitions) {
      for (List partition : prelimPartitions) {
        ret.add(ImmutableList.copyOf(partition));
      }
    } else {
      // Otherwise, distribute the extras
      final ImmutableList.Builder> builderOfBuilders =
          ImmutableList.builder();

      // Make new builder for all but the extras, which come from the last index
      final int lastIdx = prelimPartitions.size() - 1;
      final List extras = prelimPartitions.get(lastIdx);
      for (int i = 0; i < lastIdx; i++) {
        builderOfBuilders.add(ImmutableList.builder().addAll(prelimPartitions.get(i)));
      }
      final ImmutableList> builders = builderOfBuilders.build();

      // Distribute the extra elements. We cannot overrun the bounds of builders because the number
      // of extras is always at least one less than the number of builders (otherwise, we would've
      // just had larger partitions).
      int partitionIdx = 0;
      for (E item : extras) {
        builders.get(partitionIdx++).add(item);
      }

      // Fill in output
      for (ImmutableList.Builder builder : builders) {
        ret.add(builder.build());
      }
    }
    return ret.build();
  }

  /**
   * Returns a view of the concatenation of the two provided collections. Iterating through this
   * view will provide all the elements of {@code left} according to its own iteration order,
   * followed by the elements of {@code right} according to its.  The size of this collection view
   * is the sum of the sizes of {@code left} and {@code right}.
   *
   * Equality and hashCode on a concatted view are undefined.
   */
  public static  Collection concat(Collection left,
      Collection right) {
    return new ConcattedCollectionView(left, right);
  }

  private static final class ConcattedCollectionView extends AbstractCollection {

    private final Collection left;
    private final Collection right;

    private ConcattedCollectionView(final Collection left,
        final Collection right) {
      this.left = checkNotNull(left);
      this.right = checkNotNull(right);
    }

    @Override
    public Iterator iterator() {
      return Iterators.concat(left.iterator(), right.iterator());
    }

    @Override
    public int size() {
      return left.size() + right.size();
    }
  }

  /**
   * Checks if two {@link Iterable}s contain the same elements regardless of order or number of
   * occurrences. If they do, nothing happens. If they don't an {@link IllegalStateException} is
   * raised describing the difference. The exception's message will be prefixed by {@code
   * msgIntroduction} and {@code leftNamePlural} and {@code rightNamePlural} will be used to
   * describe the left and right element types.
   *
   * Example: 
   *  assertSameElementsOrIllegalState(mySet1, mySet2, "Document IDs do not match: " ,
   *       "gold docIDs", "test docIDs");
   * 
*/ public static void assertSameElementsOrIllegalState(Iterable left, Iterable right, String msgIntroduction, String leftName, String rightName) { final Optional exceptionMessage = assertSameElementsCommon(left, right, msgIntroduction, leftName, rightName); if (exceptionMessage.isPresent()) { throw new IllegalStateException(exceptionMessage.get()); } } public static void assertSameElementsOrIllegalArgument(Iterable left, Iterable right, String msgIntroduction, String leftName, String rightName) { final Optional exceptionMessage = assertSameElementsCommon(left, right, msgIntroduction, leftName, rightName); if (exceptionMessage.isPresent()) { throw new IllegalArgumentException(exceptionMessage.get()); } } private static Optional assertSameElementsCommon(final Iterable left, final Iterable right, final String msgIntroduction, final String leftName, final String rightName) { final Optional exceptionMessage; final ImmutableSet leftSet = ImmutableSet.copyOf(left); final ImmutableSet rightSet = ImmutableSet.copyOf(right); if (!leftSet.equals(rightSet)) { final StringBuilder exceptionMsg = new StringBuilder(); final ImmutableSet leftOnly = Sets.difference(leftSet, rightSet).immutableCopy(); final ImmutableSet rightOnly = Sets.difference(rightSet, leftSet).immutableCopy(); exceptionMsg.append(msgIntroduction); if (!leftOnly.isEmpty()) { exceptionMsg.append(" ").append(leftOnly.size()).append(" ").append(leftName) .append(" only: ").append(leftOnly).append(". "); } if (!rightOnly.isEmpty()) { exceptionMsg.append(" ").append(rightOnly.size()).append(" ").append(rightName) .append(" only: ").append(rightOnly).append(". "); } exceptionMessage = Optional.of(exceptionMsg.toString()); } else { exceptionMessage = Optional.absent(); } return exceptionMessage; } }