Mehrere Sammlungen zu einer einzigen logischen Sammlung kombinieren?

110

Angenommen, ich habe eine konstante Anzahl von Sammlungen (z. B. 3 ArrayLists) als Mitglieder einer Klasse. Jetzt möchte ich alle Elemente anderen Klassen aussetzen, damit sie einfach über alle Elemente iterieren können (idealerweise schreibgeschützt). Ich verwende Guavensammlungen und frage mich, wie ich mit Guaven-Iterablen / Iteratoren eine logische Ansicht der internen Sammlungen erstellen kann , ohne temporäre Kopien zu erstellen.

Newgre
quelle
^^ Defekter Link. Ich denke, er hat auf diese Methode im Guava Javadoc
RustyTheBoyRobot

Antworten:

113

Mit Guava können Iterables.concat(Iterable<T> ...)Sie eine Live-Ansicht aller iterierten Elemente erstellen, die zu einer verkettet sind (wenn Sie die iterablen Elemente ändern, ändert sich auch die verkettete Version). Wickeln Sie dann die verkettete iterable mit ein Iterables.unmodifiableIterable(Iterable<T>)(ich hatte die schreibgeschützte Anforderung zuvor nicht gesehen).

Aus den Iterables.concat( .. )JavaDocs:

Kombiniert mehrere Iterables zu einem einzigen Iterable. Die zurückgegebene Iterable verfügt über einen Iterator, der die Elemente jeder Iterable in Eingaben durchläuft. Die Eingabe-Iteratoren werden erst bei Bedarf abgefragt. Der Iterator des zurückgegebenen Iterables unterstützt, remove() wenn der entsprechende Eingabe-Iterator dies unterstützt.

Dies bedeutet zwar nicht ausdrücklich, dass es sich um eine Live-Ansicht handelt, der letzte Satz impliziert jedoch, dass dies der Fall ist (die Unterstützung der Iterator.remove()Methode nur, wenn der Backing-Iterator dies unterstützt, ist nur mit einer Live-Ansicht möglich).

Beispielcode:

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

Ausgabe:

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


Bearbeiten:

Auf Anfrage von Damian gibt es eine ähnliche Methode, die eine Live-Sammlungsansicht zurückgibt

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
quelle
Wie würde ich verhindern, dass der Benutzer Elemente entfernt? Gibt es eine schönere Möglichkeit, als die Listen in nicht modifizierbare Listen zu verpacken?
Newgre
2
@jn_ wickeln Sie es einfach einIterables.unmodifiableIterable(iterable)
Sean Patrick Floyd
2
Was ist mit Sammlungen? Iterables.concatproduziert ein Iterable, nicht Collection. Ich würde eine CollectionAussicht brauchen .
Nowaker
@Damian Das einzig nützliche Feature wäre eine aggregierte size () -Methode. Alle anderen Methoden in der Collection-Oberfläche haben entweder eine undefinierte Semantik (Hinzufügen usw.) oder eine miserable Leistung (enthält usw.).
Sean Patrick Floyd
2
@ Sean, ja - size()ist was ich brauche. add()Eine Ausnahme auszulösen ist gut - diese Methode interessiert mich nicht. Die Sammlungs-API ist defekt und niemand kann etwas dagegen tun. Collection.add(), Iterator.remove(), Blah.
Nowaker
101

Einfache Java 8-Lösungen mit a Stream.

Konstante Zahl

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

Eine Lösung:

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

Eine andere Lösung:

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

Variable Nummer

Angenommen private Collection<Collection<T>> cs:

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

Wenn Sie mindestens Java 8 verwenden, lesen Sie meine andere Antwort .

Wenn Sie bereits Google Guava verwenden, lesen Sie die Antwort von Sean Patrick Floyd .

Wenn Sie mit Java 7 nicht weiterkommen und Google Guava nicht einbinden möchten, können Sie Ihre eigenen (schreibgeschützten) Iterables.concat()Dateien mit nicht mehr als Iterableund schreiben Iterator:

Konstante Zahl

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 Nummer

@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
quelle
0

Sie könnten ein neues Listund addAll()von Ihren anderen dazu erstellen List. Geben Sie dann eine nicht veränderbare Liste mit zurück Collections.unmodifiableList().

Qwerky
quelle
3
Das würde eine neue temporäre Sammlung schaffen, die möglicherweise ziemlich teuer ist
Newgre
5
Teuer wie , die zugrunde liegenden Objekte in den Listen werden nicht kopiert und ArrayListordnen nur den Speicherplatz und die Anrufe System.arraycopy()unter der Haube zu. Effizienter geht es nicht.
Qwerky
8
Wie ist das Kopieren einer ganzen Sammlung für jede Iteration nicht teuer? Darüber hinaus können Sie besser werden, siehe Seans Antwort.
Newgre
Es verwendet auch eine native Implementierung, um den Speicher zu kopieren, und iteriert nicht durch das Array.
Qwerky
1
Nun, wenn es das Array kopiert, ist es sicherlich ein O (n) -Algorithmus, der nicht skaliert und die gleiche Komplexität hat wie das einmalige Durchlaufen des Arrays. Angenommen, jede Liste enthält eine Million Elemente, dann muss ich einige Millionen Elemente kopieren, um sie zu durchlaufen. Schlechte Idee.
Newgre
0

Hier ist meine Lösung dafür:

BEARBEITEN - Code ein wenig geändert

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
quelle
Ihre Implementierung wechselt die Rollen von hasNext()und next(). Der erste ändert den Status Ihres Iterators, der zweite nicht. Es sollte umgekehrt sein. Der Aufruf next()ohne Aufruf hasNext()wird immer nachgeben null. Wenn Sie hasNext()anrufen, ohne anzurufen, next()werden Elemente weggeworfen. Sie next()werfen auch NoSuchElementExceptionnicht, sondern kehren zurück null.
Xehpuk