Połączyć wiele kolekcji w jedną logiczną kolekcję?

110

Załóżmy, że mam stałą liczbę kolekcji (np. 3 ArrayLists) jako członków klasy. Teraz chcę ujawnić wszystkie elementy innym klasom, aby mogły po prostu iterować po wszystkich elementach (najlepiej tylko do odczytu). Używam kolekcji guawy i zastanawiam się, jak mógłbym użyć iteracji / iteratorów guawy, aby wygenerować logiczny widok na wewnętrzne kolekcje bez tworzenia tymczasowych kopii.

newgre
źródło
^^ Zepsuty link. Myślę, że wskazywał na tę metodę w Guava Javadoc
RustyTheBoyRobot

Odpowiedzi:

113

Dzięki Guava, możesz użyć Iterables.concat(Iterable<T> ...), tworzy widok na żywo wszystkich iterowalnych, połączonych w jedną (jeśli zmienisz iterowalne, połączona wersja również się zmieni). Następnie zawiń połączoną iterację z Iterables.unmodifiableIterable(Iterable<T>)(nie widziałem wcześniej wymagania tylko do odczytu).

Z Iterables.concat( .. )JavaDocs:

Łączy wiele elementów iteracyjnych w jedną iterowalną. Zwrócona iterowalna ma iterator, który przechodzi przez elementy każdej iterowalnej w danych wejściowych. Iteratory wejściowe nie są odpytywane, dopóki nie są potrzebne. Iterator zwracanej iteracji obsługuje, remove() gdy obsługuje ją odpowiedni iterator wejściowy.

Chociaż nie oznacza to wyraźnie, że jest to podgląd na żywo, ostatnie zdanie sugeruje, że tak jest (obsługa Iterator.remove()metody tylko wtedy, gdy iterator wspierający obsługuje, nie jest możliwa, chyba że używa się podglądu na żywo)

Przykładowy kod:

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);

Wynik:

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


Edytować:

Na żądanie od Damiana, oto podobna metoda, która zwraca aktywny widok kolekcji

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() {
    }

}
Sean Patrick Floyd
źródło
Jak zapobiec usuwaniu elementów przez użytkownika? Czy jest lepszy sposób niż zawijanie list w listy niemodyfikowalne?
newgre
2
@jn_ po prostu zawińIterables.unmodifiableIterable(iterable)
Sean Patrick Floyd
2
A co z kolekcjami? Iterables.concatprodukuje Iterable, nie Collection. Potrzebowałbym Collectionwidoku.
Nowaker
@Damian jedyną przydatną funkcją byłoby posiadanie zagregowanej metody size (). Wszystkie inne metody w interfejsie Collection miałyby niezdefiniowaną semantykę (dodawanie itp.) Lub mizerną wydajność (zawiera itp.).
Sean Patrick Floyd
2
@Sean, tak - size()to jest to, czego potrzebuję. add()rzucenie wyjątku jest dobre - nie obchodzi mnie ta metoda. Collection API jest zepsuta i nikt nie może nic na to poradzić. Collection.add(), Iterator.remove()bla.
Nowaker
101

Zwykłe rozwiązania Java 8 wykorzystujące rozszerzenie Stream.

Stała liczba

Zakładając private Collection<T> c, c2, c3.

Jedno rozwiązanie:

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

Inne rozwiązanie:

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

Zmienna liczba

Zakładając private Collection<Collection<T>> cs:

public Stream<T> stream() {
    return cs.stream().flatMap(Collection::stream);
}
xehpuk
źródło
10

Jeśli używasz przynajmniej Java 8, zobacz moją drugą odpowiedź .

Jeśli korzystasz już z Google Guava, zobacz odpowiedź Seana Patricka Floyda .

Jeśli utknąłeś w Javie 7 i nie chcesz dołączać Google Guava, możesz napisać własną (tylko do odczytu), Iterables.concat()używając nie więcej niż Iterablei Iterator:

Stała liczba

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();
                }
            };
        }
    };
}

Zmienna liczba

@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();
        }
    };
}
xehpuk
źródło
1

Możesz stworzyć nowe Listi addAll()swoje inne List. Następnie zwróć niemodyfikowalną listę za pomocą Collections.unmodifiableList().

Qwerky
źródło
3
Stworzyłoby
6
Kosztowne jak , obiekty bazowe na listach nie są kopiowane i ArrayListpo prostu przydziela miejsce i wywołania System.arraycopy()pod maską. Nie można być bardziej wydajnym niż to.
Qwerky
8
Dlaczego kopiowanie całej kolekcji dla każdej iteracji nie jest kosztowne? Co więcej, możesz być lepszy, zobacz odpowiedź Seans.
newgre
Używa również natywnej implementacji do kopiowania pamięci, nie wykonuje iteracji po tablicy.
Qwerky
1
Cóż, jeśli kopiuje tablicę, z pewnością jest to algorytm O (n), który nie skaluje się i ma taką samą złożoność, jak jednorazowe iterowanie po tablicy. Załóżmy, że każda lista zawiera milion elementów, a następnie muszę skopiować kilka milionów elementów tylko po to, aby je powtórzyć. Kiepski pomysł.
newgre
0

Oto moje rozwiązanie:

EDYCJA - trochę zmienił kod

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();
                }
            };
        }
    };
}
chmouel kalifa
źródło
Twoja implementacja zamienia role hasNext()i next(). Pierwsza zmienia stan twojego iteratora, a druga nie. Powinno być na odwrót. Wołanie next()bez wołania hasNext()zawsze ustąpinull . Wywołanie hasNext()bez wywołania next()spowoduje odrzucenie elementów. Twój next()też nie rzuca NoSuchElementException, ale wraca null.
xehpuk