Combina più raccolte in un'unica raccolta logica?


110

Supponiamo che io abbia un numero costante di raccolte (ad esempio 3 ArrayLists) come membri di una classe. Ora, voglio esporre tutti gli elementi ad altre classi in modo che possano semplicemente iterare su tutti gli elementi (idealmente, di sola lettura). Sto usando le raccolte guava e mi chiedo come posso utilizzare iterabili / iteratori guava per generare una vista logica sulle raccolte interne senza fare copie temporanee.


^^ Collegamento interrotto. Penso che stesse indicando questo metodo nel Guava Javadoc
RustyTheBoyRobot

Risposte:


113

Con Guava, puoi usare Iterables.concat(Iterable<T> ...), crea una visualizzazione live di tutti gli iterabili, concatenati in uno (se cambi gli iterabili, cambia anche la versione concatenata). Quindi avvolgere l'iterabile concatenato con Iterables.unmodifiableIterable(Iterable<T>)(non avevo visto il requisito di sola lettura prima).

Dai Iterables.concat( .. )JavaDocs:

Combina più iterabili in un unico iterabile. L'iterabile restituito ha un iteratore che attraversa gli elementi di ogni iterabile negli input. Gli iteratori di input non vengono sottoposti a polling finché non è necessario. L'iteratore dell'iterabile restituito supporta remove() quando l'iteratore di input corrispondente lo supporta.

Anche se questo non dice esplicitamente che si tratta di una visualizzazione live, l'ultima frase implica che lo sia (supportare il Iterator.remove()metodo solo se l'iteratore di supporto lo supporta non è possibile a meno che non si utilizzi una visualizzazione live)

Codice di esempio:

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

Produzione:

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


Modificare:

Su richiesta di Damian, ecco un metodo simile che restituisce una visualizzazione raccolta dal vivo

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

}

Come impedirei all'utente di rimuovere elementi? C'è un modo più carino che avvolgere gli elenchi in elenchi non modificabili?
newgre


2
E le collezioni? Iterables.concatproduce un Iterable, no Collection. Avrei bisogno di una Collectionvista.
Nowaker

@Damian l'unica caratteristica utile sarebbe quella di avere un metodo size () aggregato. Tutti gli altri metodi nell'interfaccia Collection avrebbero una semantica indefinita (aggiungi ecc.) O prestazioni misere (contiene ecc.).
Sean Patrick Floyd

2
@ Sean, sì, size()è ciò di cui ho bisogno. add()lanciare un'eccezione è positivo - non mi interessa questo metodo. L'API delle collezioni è rotta e nessuno può fare nulla al riguardo. Collection.add(), Iterator.remove(), Bla.
Nowaker

101

Semplici soluzioni Java 8 che utilizzano un file Stream.

Numero costante

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

Una soluzione:

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

Un'altra soluzione:

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

Numero variabile

Supponendo private Collection<Collection<T>> cs:

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

10

Se stai usando almeno Java 8, vedi la mia altra risposta .

Se stai già utilizzando Google Guava, vedi la risposta di Sean Patrick Floyd .

Se sei bloccato su Java 7 e non desideri includere Google Guava, puoi scrivere il tuo (di sola lettura) Iterables.concat()utilizzando non più di Iterablee Iterator:

Numero costante

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

Numero variabile

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

1

Potresti crearne uno nuovo Liste addAll()dei tuoi altri List. Quindi restituisci un elenco non modificabile con Collections.unmodifiableList().


3
Ciò creerebbe una nuova collezione temporanea che è potenzialmente piuttosto costosa
newgre

6
Costoso come , gli oggetti sottostanti negli elenchi non vengono copiati e ArrayListalloca solo lo spazio e le chiamate System.arraycopy()sotto il cofano. Non posso essere molto più efficiente di così.
Qwerky

8
In che modo copiare un'intera raccolta per ogni iterazione non è costoso? Inoltre, puoi fare di meglio, vedi la risposta di Seans.
newgre

Utilizza anche un'implementazione nativa per copiare la memoria, non itera attraverso l'array.
Qwerky

1
Bene, se sta copiando l'array, è certamente un algoritmo O (n) che non scala e ha la stessa complessità dell'iterazione sull'array una volta. Supponiamo che ogni elenco contenga un milione di elementi, quindi ho bisogno di copiare alcuni milioni di elementi, solo per iterarli su di essi. Cattiva idea.
newgre

0

Ecco la mia soluzione per questo:

EDIT - cambiato un po 'il codice

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

La tua implementazione ribalta i ruoli di hasNext()e next(). Il primo cambia lo stato del tuo iteratore mentre il secondo no. Dovrebbe essere il contrario. Chiamare next()senza chiamare hasNext()darà sempre risultati null. Chiamare hasNext()senza chiamare next()getterà via gli elementi. Il tuo next()non lancia NoSuchElementExceptionneanche, ma invece ritorna null.
xehpuk
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.