Combine multiple Collections into a single logical Collection?

JavaCollectionsGuava

Java Problem Overview


Assume, I have a constant number of collections (e.g. 3 ArrayLists) as members of a class. Now, I want to expose all the elements to other classes so they can simply iterate over all elements (ideally, read only). I'm using guava collections and I wonder how I could use guava iterables/iterators to generate a logical view on the internal collections without making temporary copies.

Java Solutions


Solution 1 - Java

With Guava, you can use Iterables.concat(Iterable<T> ...), it creates a live view of all the iterables, concatenated into one (if you change the iterables, the concatenated version also changes). Then wrap the concatenated iterable with Iterables.unmodifiableIterable(Iterable<T>) (I hadn't seen the read-only requirement earlier).

From the Iterables.concat( .. ) JavaDocs:

> Combines multiple iterables into a > single iterable. The returned iterable > has an iterator that traverses the > elements of each iterable in inputs. > The input iterators are not polled > until necessary. The returned > iterable's iterator supports remove() > when the corresponding input iterator > supports it.

While this doesn't explicitly say that this is a live view, the last sentence implies that it is (supporting the Iterator.remove() method only if the backing iterator supports it is not possible unless using a live view)

Sample Code:

final List<Integer> first  = Lists.newArrayList(1, 2, 3);
final List<Integer> second = Lists.newArrayList(4, 5, 6);
final List<Integer> third  = Lists.newArrayList(7, 8, 9);
final Iterable<Integer> all =
    Iterables.unmodifiableIterable(
        Iterables.concat(first, second, third));
System.out.println(all);
third.add(9999999);
System.out.println(all);

Output:

> [1, 2, 3, 4, 5, 6, 7, 8, 9]
> [1, 2, 3, 4, 5, 6, 7, 8, 9, 9999999]


Edit:

By Request from Damian, here's a similar method that returns a live Collection View

public final class CollectionsX {

	static class JoinedCollectionView<E> implements Collection<E> {

		private final Collection<? extends E>[] items;

		public JoinedCollectionView(final Collection<? extends E>[] items) {
			this.items = items;
		}

		@Override
		public boolean addAll(final Collection<? extends E> c) {
			throw new UnsupportedOperationException();
		}

		@Override
		public void clear() {
			for (final Collection<? extends E> coll : items) {
				coll.clear();
			}
		}

		@Override
		public boolean contains(final Object o) {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean containsAll(final Collection<?> c) {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean isEmpty() {
			return !iterator().hasNext();
		}

		@Override
		public Iterator<E> iterator() {
			return Iterables.concat(items).iterator();
		}

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

		@Override
		public boolean removeAll(final Collection<?> c) {
			throw new UnsupportedOperationException();
		}

		@Override
		public boolean retainAll(final Collection<?> c) {
			throw new UnsupportedOperationException();
		}

		@Override
		public int size() {
			int ct = 0;
			for (final Collection<? extends E> coll : items) {
				ct += coll.size();
			}
			return ct;
		}

		@Override
		public Object[] toArray() {
			throw new UnsupportedOperationException();
		}

		@Override
		public <T> T[] toArray(T[] a) {
			throw new UnsupportedOperationException();
		}

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

	}

	/**
	 * Returns a live aggregated collection view of the collections passed in.
	 * <p>
	 * All methods except {@link Collection#size()}, {@link Collection#clear()},
	 * {@link Collection#isEmpty()} and {@link Iterable#iterator()}
	 *  throw {@link UnsupportedOperationException} in the returned Collection.
	 * <p>
	 * None of the above methods is thread safe (nor would there be an easy way
	 * of making them).
	 */
	public static <T> Collection<T> combine(
	    final Collection<? extends T>... items) {
		return new JoinedCollectionView<T>(items);
	}

	private CollectionsX() {
	}

}

Solution 2 - Java

Plain Java 8 solutions using a Stream.

Constant number

Assuming private Collection<T> c, c2, c3.

One solution:

public Stream<T> stream() {
    return Stream.concat(Stream.concat(c.stream(), c2.stream()), c3.stream());
}

Another solution:

public Stream<T> stream() {
    return Stream.of(c, c2, c3).flatMap(Collection::stream);
}

Variable number

Assuming private Collection<Collection<T>> cs:

public Stream<T> stream() {
    return cs.stream().flatMap(Collection::stream);
}

Solution 3 - Java

If you're using at least Java 8, see my other answer.

If you're already using Google Guava, see Sean Patrick Floyd's answer.

If you're stuck at Java 7 and don't want to include Google Guava, you can write your own (read-only) Iterables.concat() using no more than Iterable and Iterator:

Constant number

public static <E> Iterable<E> concat(final Iterable<? extends E> iterable1,
									 final Iterable<? extends E> iterable2) {
	return new Iterable<E>() {
		@Override
		public Iterator<E> iterator() {
			return new Iterator<E>() {
				final Iterator<? extends E> iterator1 = iterable1.iterator();
				final Iterator<? extends E> iterator2 = iterable2.iterator();
				
				@Override
				public boolean hasNext() {
					return iterator1.hasNext() || iterator2.hasNext();
				}
				
				@Override
				public E next() {
					return iterator1.hasNext() ? iterator1.next() : iterator2.next();
				}
			};
		}
	};
}

Variable number

@SafeVarargs
public static <E> Iterable<E> concat(final Iterable<? extends E>... iterables) {
	return concat(Arrays.asList(iterables));
}

public static <E> Iterable<E> concat(final Iterable<Iterable<? extends E>> iterables) {
	return new Iterable<E>() {
		final Iterator<Iterable<? extends E>> iterablesIterator = iterables.iterator();
		
		@Override
		public Iterator<E> iterator() {
			return !iterablesIterator.hasNext() ? Collections.emptyIterator()
												: new Iterator<E>() {
				Iterator<? extends E> iterableIterator = nextIterator();

				@Override
				public boolean hasNext() {
					return iterableIterator.hasNext();
				}

				@Override
				public E next() {
					final E next = iterableIterator.next();
					findNext();
					return next;
				}

				Iterator<? extends E> nextIterator() {
					return iterablesIterator.next().iterator();
				}

				Iterator<E> findNext() {
					while (!iterableIterator.hasNext()) {
						if (!iterablesIterator.hasNext()) {
							break;
						}
						iterableIterator = nextIterator();
					}
					return this;
				}
			}.findNext();
		}
	};
}

Solution 4 - Java

You could create a new List and addAll() of your other Lists to it. Then return an unmodifiable list with Collections.unmodifiableList().

Solution 5 - Java

Here is my solution for that:

EDIT - changed code a little bit

public static <E> Iterable<E> concat(final Iterable<? extends E> list1, Iterable<? extends E> list2)
{
	return new Iterable<E>()
	{
		public Iterator<E> iterator()
		{
			return new Iterator<E>()
			{
				protected Iterator<? extends E> listIterator = list1.iterator();
				protected Boolean checkedHasNext;
				protected E nextValue;
				private boolean startTheSecond;

				public void theNext()
				{
					if (listIterator.hasNext())
					{
						checkedHasNext = true;
						nextValue = listIterator.next();
					}
					else if (startTheSecond)
						checkedHasNext = false;
					else
					{
						startTheSecond = true;
						listIterator = list2.iterator();
						theNext();
					}
				}

				public boolean hasNext()
				{
					if (checkedHasNext == null)
						theNext();
					return checkedHasNext;
				}

				public E next()
				{
	                if (!hasNext())
						throw new NoSuchElementException();
					checkedHasNext = null;
					return nextValue;

				}

				public void remove()
				{
					listIterator.remove();
				}
			};
		}
	};
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionnewgreView Question on Stackoverflow
Solution 1 - JavaSean Patrick FloydView Answer on Stackoverflow
Solution 2 - JavaxehpukView Answer on Stackoverflow
Solution 3 - JavaxehpukView Answer on Stackoverflow
Solution 4 - JavaQwerkyView Answer on Stackoverflow
Solution 5 - Javachmouel kalifaView Answer on Stackoverflow