// ================================================================================================= // Copyright 2011 Twitter, Inc. // ------------------------------------------------------------------------------------------------- // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this work except in compliance with the License. // You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.collections; import java.util.Collection; import java.util.Set; import java.util.SortedSet; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; /** * Utility class for functions related to Multimaps in the java collections library. * * @author William Farner */ public final class Multimaps { private Multimaps() { // Utility. } /** * Prunes a multimap based on a predicate, returning the pruned values. The input map will be * modified. * * @param map The multimap to prune. * @param filterRule The pruning rule. When the predicate returns {@code false} for an entry, it * will be pruned, otherwise it will be retained. * @param <K> The key type in the multimap. * @param <V> The value type in the multimap. * @return A new multimap, containing the pruned keys/values. */ public static <K, V> Multimap<K, V> prune(Multimap<K, V> map, Predicate<Collection<V>> filterRule) { Preconditions.checkNotNull(map); Preconditions.checkNotNull(filterRule); Set<K> prunedKeys = Sets.newHashSet(); for (K key : map.keySet()) { if (!filterRule.apply(map.get(key))) { prunedKeys.add(key); } } Multimap<K, V> pruned = ArrayListMultimap.create(); for (K key : prunedKeys) { pruned.putAll(key, map.removeAll(key)); } return pruned; } /** * Convenience method to prune key/values pairs where the size of the value collection is below a * threshold. * * @param map The multimap to prune. * @param minSize The minimum size for retained value collections. * @param <K> The key type in the multimap. * @param <V> The value type in the multimap. * @return A new multimap, containing the pruned keys/values. */ public static <K, V> Multimap<K, V> prune(Multimap<K, V> map, final int minSize) { return prune(map, new Predicate<Collection<V>>() { @Override public boolean apply(Collection<V> input) { return input.size() >= minSize; } }); } /** * Returns the set of keys associated with groups of a size greater than or equal to a given size. * * @param map The multimap to search. * @param minSize The minimum size to return associated keys for. * @param <K> The key type for the multimap. * @return The keys associated with groups of size greater than or equal to {@code minSize}. */ public static <K> Set<K> getLargeGroups(Multimap<K, ?> map, int minSize) { Set<K> largeKeys = Sets.newHashSet(); for (K key : map.keySet()) { if (map.get(key).size() >= minSize) { largeKeys.add(key); } } return largeKeys; } /** * Returns the set of keys associated with the largest values in the multimap. * * @param map The multimap to search. * @param topValues Number of groupings to find the keys for. * @return The keys associated with the largest groups, of maximum size {@code topValues}. */ public static <K> Set<K> getLargestGroups(Multimap<K, ?> map, int topValues) { /** * A grouping of values in the multimap. */ class Grouping implements Comparable<Grouping> { private K key; private int size; public Grouping(K key, int size) { this.key = key; this.size = size; } @Override public int hashCode() { return size; } @Override public int compareTo(Grouping grouping) { return size - grouping.size; } @Override public boolean equals(Object o) { if (!(o instanceof Grouping)) { return false; } Grouping other = (Grouping) o; return key.equals(other.key); } } SortedSet<Grouping> topGroups = Sets.newTreeSet(); for (K key : map.keySet()) { topGroups.add(new Grouping(key, map.get(key).size())); // Remove the smallest value. if (topGroups.size() > topValues) { topGroups.remove(topGroups.first()); } } Set<K> topKeys = Sets.newHashSet(); for (Grouping group : topGroups) { topKeys.add(group.key); } return topKeys; } }