Об’єднати кілька колекцій в одну логічну колекцію?


110

Припустимо, у мене є постійне число колекцій (наприклад, 3 ArrayLists) як членів класу. Тепер я хочу розкрити всі елементи іншим класам, щоб вони могли просто перебрати всі елементи (в ідеалі лише для читання). Я використовую колекції guava і мені цікаво, як я міг би використовувати ітератори / ітератори guava для створення логічного перегляду внутрішніх колекцій без створення тимчасових копій.


^^ Перервана посилання. Я думаю, що він вказував на цей метод у Guava Javadoc
RustyTheBoyRobot

Відповіді:


113

За допомогою програми Guava ви можете використовувати Iterables.concat(Iterable<T> ...), вона створює перегляд у реальному часі всіх ітерабелів, об'єднаних в один (якщо ви змінюєте ітерабелі, також змінюється і конкатенована версія). Потім оберніть з'єднаний ітерабельний Iterables.unmodifiableIterable(Iterable<T>)(я раніше не бачив вимоги лише для читання).

З Iterables.concat( .. )JavaDocs:

Поєднує кілька ітерабелів в один ітерабельний. Повертається ітерабельний файл має ітератор, який переміщує елементи кожного ітерабельного введення. Ітератори введення не опитуються до необхідності. Ітератор, що повертається, підтримує ітератор, remove() коли його підтримує відповідний ітератор введення.

Хоча це прямо не говорить про те, що це перегляд у прямому ефірі, з останнього речення випливає, що воно є (підтримка Iterator.remove()методу лише в тому випадку, якщо підтримуючий ітератор підтримує його неможливо, якщо не використовувати прямий перегляд)

Приклад коду:

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

Вихід:

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


Редагувати:

За запитом від Damian, ось подібний метод, який повертає прямий перегляд колекції

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

}

Як я можу запобігти користувачу видаляти елементи? Чи є кращий спосіб, ніж загортати списки в незмінні списки?
newgre

2
@jn_ просто заверніть йогоIterables.unmodifiableIterable(iterable)
Шон Патрік Флойд

2
Що з колекціями? Iterables.concatвиробляє Iterable, не Collection. Мені знадобиться Collectionогляд.
Nowaker

@Damian єдиною корисною особливістю цього може бути метод агрегованого розміру (). Усі інші методи в інтерфейсі колекції мали б або невизначену семантику (додавання тощо), або жалюгідну продуктивність (містить тощо).
Шон Патрік Флойд

2
@Sean, так - size()це те, що мені потрібно. add()кидати виняток - це добре, я не переймаюся цим методом. API колекцій порушений, і ніхто нічого з цим не може зробити. Collection.add(), Iterator.remove()Бла.
Nowaker

101

Прості рішення Java 8, використовуючи a Stream.

Постійне число

Припускаючи private Collection<T> c, c2, c3.

Одне рішення:

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

Ще одне рішення:

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

Змінне число

Припустимо private Collection<Collection<T>> cs:

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

10

Якщо ви використовуєте принаймні Java 8, дивіться іншу мою відповідь .

Якщо ви вже використовуєте Google Guava, дивіться відповідь Шона Патріка Флойда .

Якщо ви застрягли в Java 7 і не хочете включати Google Guava, ви можете написати свій власний (лише для читання), Iterables.concat()використовуючи не більше ніж Iterableта Iterator:

Постійне число

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

Змінне число

@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

Ви можете створити нове Listта addAll()інше Listдля нього. Потім поверніть немодифікуваний список за допомогою Collections.unmodifiableList().


3
Це створило б нову тимчасову колекцію, яка потенційно є досить дорогою
newgre

6
Дороге як , основні об'єкти у списках не копіюються, а ArrayListпросто виділяють простір та дзвінки System.arraycopy()під капотом. Не може бути набагато ефективнішим за це.
Qwerky

8
Як копіювати цілу колекцію для кожної ітерації не дорого? Більше того, ви можете стати кращим за це, дивіться відповідь Шона.
newgre

Він також використовує нативну реалізацію для копіювання пам'яті, вона не повторюється через масив.
Qwerky

1
Добре, якщо він копіює масив, це, безумовно, алгоритм O (n), який не масштабує і має ту ж складність, що ітерація над масивом один раз. Припустимо, що кожен список містить мільйон елементів, тоді мені потрібно скопіювати кілька мільйонів елементів, лише щоб перебрати їх. Погана ідея.
newgre

0

Ось моє рішення для цього:

EDIT - трохи змінив код

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

Ваша реалізація відбиває ролі hasNext()та next(). Перша змінює стан вашого ітератора, а друга -. Це повинно бути навпаки. Виклик next()без дзвінків hasNext()завжди поступатиметьсяnull . Виклик hasNext()без дзвінків next()викине елементи. І ваш next()не кидає NoSuchElementException, а натомість повертається null.
xehpuk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.