// =================================================================================================
// 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.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import com.google.common.base.Function;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
* Utility functions for dealing with iterables.
*
* @author William Farner
*/
public final class Iterables2 {
private Iterables2() {
// Utility class.
}
/**
* An iterator that zips multiple iterables into a single list iterator, filling missing values
* with a provided default.
*
* @param <T> The value type for the iterator.
*/
private static class ZippingIterator<T> implements Iterator<List<T>> {
private final Iterable<Iterable<T>> iterables;
private final T defaultValue;
private List<Iterator<T>> iterators = null;
private final LoadingCache<Iterator<T>, Boolean> overflowing = CacheBuilder.newBuilder().build(
new CacheLoader<Iterator<T>, Boolean>() {
@Override public Boolean load(Iterator<T> iterator) {
return false;
}
});
ZippingIterator(Iterable<Iterable<T>> iterables, T defaultValue) {
this.iterables = iterables;
this.defaultValue = defaultValue;
}
private void init() {
if (iterators == null) {
// Iterables -> Iterators.
iterators = ImmutableList.copyOf(Iterables.transform(iterables,
new Function<Iterable<T>, Iterator<T>>() {
@Override public Iterator<T> apply(Iterable<T> it) { return it.iterator(); }
}));
}
}
@Override public boolean hasNext() {
init();
for (Iterator<T> it : iterators) {
if (it.hasNext()) {
return true;
}
}
return false;
}
@Override public List<T> next() {
init();
List<T> data = new ArrayList<T>(iterators.size());
for (Iterator<T> it : iterators) {
if (it.hasNext()) {
data.add(it.next());
} else {
overflowing.asMap().put(it, true);
data.add(defaultValue);
}
}
return data;
}
@Override public void remove() {
init();
for (Iterator<T> it : iterators) {
if (!overflowing.getUnchecked(it)) {
it.remove();
}
}
}
@Override public String toString() {
return Lists.newArrayList(iterables).toString();
}
}
/**
* Zips multiple iterables into one iterable that will return iterators to step over
* rows of the input iterators (columns). The order of the returned values within each row will
* match the ordering of the input iterables. The iterators will iterate the length of the longest
* input iterable, filling other columns with {@code defaultValue}.
* The returned iterator is lazy, in that 'rows' are constructed as they are requested.
*
* @param iterables Columns to iterate over.
* @param defaultValue Default fill value when an input iterable is exhausted.
* @param <T> Type of value being iterated over.
* @return An iterator that iterates over rows of the input iterables.
*/
public static <T> Iterable<List<T>> zip(final Iterable<Iterable<T>> iterables,
final T defaultValue) {
return new Iterable<List<T>>() {
@Override public Iterator<List<T>> iterator() {
return new ZippingIterator<T>(iterables, defaultValue);
}
};
}
/**
* Varargs convenience function to call {@link #zip(Iterable, Object)}.
*
* @param defaultValue Default fill value when an input iterable is exhausted.
* @param iterables Columns to iterate over.
* @param <T> Type of value being iterated over.
* @return An iterator that iterates over rows of the input iterables.
*/
public static <T> Iterable<List<T>> zip(T defaultValue, Iterable<T>... iterables) {
return zip(Arrays.asList(iterables), defaultValue);
}
}