Combiner plusieurs collections en une seule collection logique?


110

Supposons que j'ai un nombre constant de collections (par exemple 3 ArrayLists) en tant que membres d'une classe. Maintenant, je veux exposer tous les éléments à d'autres classes afin qu'ils puissent simplement itérer sur tous les éléments (idéalement, en lecture seule). J'utilise des collections de goyave et je me demande comment je pourrais utiliser les itérables / itérateurs de goyave pour générer une vue logique sur les collections internes sans faire de copies temporaires.


^^ Lien cassé. Je pense qu'il
désignait

Réponses:


113

Avec Guava, vous pouvez utiliser Iterables.concat(Iterable<T> ...), il crée une vue en direct de tous les itérables, concaténés en un (si vous modifiez les itérables, la version concaténée change également). Ensuite, enveloppez l'itérable concaténé avec Iterables.unmodifiableIterable(Iterable<T>)(je n'avais pas vu l'exigence en lecture seule auparavant).

À partir des Iterables.concat( .. )JavaDocs:

Combine plusieurs itérables en un seul itérable. L'itérable renvoyé a un itérateur qui parcourt les éléments de chaque itérable dans les entrées. Les itérateurs d'entrée ne sont pas interrogés tant que cela n'est pas nécessaire. L'itérateur de l'itérable renvoyé prend remove() en charge lorsque l'itérateur d'entrée correspondant le prend en charge.

Bien que cela ne dise pas explicitement qu'il s'agit d'une vue en direct, la dernière phrase implique que c'est le cas (prendre en charge la Iterator.remove()méthode uniquement si l'itérateur de support le prend en charge, ce n'est pas possible à moins d'utiliser une vue en direct)

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

Production:

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


Éditer:

À la demande de Damian, voici une méthode similaire qui renvoie une vue de collection en direct

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

}

Comment empêcher l'utilisateur de supprimer des éléments? Existe-t-il un moyen plus agréable que d'encapsuler les listes dans des listes non modifiables?
newgre


2
Et les collections? Iterables.concatproduit un Iterable, non Collection. J'aurais besoin d'une Collectionvue.
Nowaker

@Damian, la seule fonctionnalité utile de cela serait d'avoir une méthode agrégée size (). Toutes les autres méthodes de l'interface Collection auraient soit une sémantique non définie (ajouter, etc.), soit des performances médiocres (contient, etc.).
Sean Patrick Floyd

2
@Sean, oui - size()c'est ce dont j'ai besoin. add()lancer une exception, c'est bien - je me fiche de cette méthode. L'API Collections est cassée et personne ne peut rien y faire. Collection.add(), Iterator.remove(), Bla.
Nowaker

101

Solutions Java 8 simples utilisant un fichier Stream.

Nombre constant

En supposant private Collection<T> c, c2, c3.

Une solution:

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

Une autre solution:

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

Numéro de variable

En supposant private Collection<Collection<T>> cs:

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

10

Si vous utilisez au moins Java 8, consultez mon autre réponse .

Si vous utilisez déjà Google Guava, consultez la réponse de Sean Patrick Floyd .

Si vous êtes bloqué sur Java 7 et que vous ne souhaitez pas inclure Google Guava, vous pouvez écrire le vôtre (en lecture seule) en Iterables.concat()n'utilisant pas plus de Iterableet Iterator:

Nombre constant

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

Numéro de variable

@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

Vous pouvez créer un nouveau Listet addAll()de vos autres Listà lui. Puis renvoyez une liste non modifiable avec Collections.unmodifiableList().


3
Cela créerait une nouvelle collection temporaire potentiellement assez chère
newgre

6
Cher comment , les objets sous-jacents dans les listes ne sont pas copiés et ArrayListallouent simplement l'espace et les appels System.arraycopy()sous le capot. Je ne peux pas être beaucoup plus efficace que ça.
Qwerky le

8
Comment copier une collection entière pour chaque itération n'est-il pas coûteux? De plus, vous pouvez faire mieux que cela, voir la réponse de Seans.
newgre

Il utilise également une implémentation native pour copier la mémoire, il n'itère pas dans le tableau.
Qwerky

1
Eh bien, s'il copie le tableau, c'est certainement un algorithme O (n) qui ne se met pas à l' échelle et qui a la même complexité qu'une itération sur le tableau une fois. Supposons que chaque liste contienne un million d'éléments, alors je dois copier quelques millions d'éléments, uniquement pour les parcourir. Mauvaise idée.
newgre

0

Voici ma solution pour cela:

EDIT - code un peu changé

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

Votre implémentation inverse les rôles de hasNext()et next(). Le premier change l'état de votre itérateur alors que le second ne change pas. Il devrait être l'inverse. Appeler next()sans appeler hasNext()cédera toujours null. Appeler hasNext()sans appeler next()jettera des éléments. Votre next()ne lance pas non NoSuchElementExceptionplus, mais revient à la place null.
xehpuk
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.