Gabungkan beberapa Koleksi menjadi satu Koleksi logis?

110

Asumsikan, saya memiliki sejumlah koleksi (misalnya 3 ArrayLists) sebagai anggota kelas. Sekarang, saya ingin mengekspos semua elemen ke kelas lain sehingga mereka dapat dengan mudah mengulang semua elemen (idealnya, hanya baca). Saya menggunakan koleksi jambu biji dan saya bertanya-tanya bagaimana saya bisa menggunakan iterabel / iterator jambu biji untuk menghasilkan tampilan logis pada koleksi internal tanpa membuat salinan sementara.

newgre
sumber
^^ Tautan rusak. Saya pikir dia menunjuk ke metode ini di Guava Javadoc
RustyTheBoyRobot

Jawaban:

113

Dengan Guava, Anda dapat menggunakannya Iterables.concat(Iterable<T> ...), ini membuat tampilan langsung dari semua iterable, digabungkan menjadi satu (jika Anda mengubah iterable, versi gabungan juga berubah). Kemudian bungkus iterable bersambung dengan Iterables.unmodifiableIterable(Iterable<T>)(saya belum melihat persyaratan hanya-baca sebelumnya).

Dari Iterables.concat( .. )JavaDocs:

Menggabungkan beberapa iterable menjadi satu iterable. Iterable yang dikembalikan memiliki iterator yang melintasi elemen dari setiap iterable dalam input. Iterator masukan tidak disurvei sampai diperlukan. Iterator iterable yang dikembalikan mendukung remove() ketika iterator input yang sesuai mendukungnya.

Meskipun ini tidak secara eksplisit mengatakan bahwa ini adalah tampilan langsung, kalimat terakhir menyiratkan bahwa itu (mendukung Iterator.remove()metode hanya jika iterator pendukung mendukungnya tidak mungkin kecuali menggunakan tampilan langsung)

Kode sampel:

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

Keluaran:

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


Edit:

Berdasarkan Permintaan dari Damian, berikut adalah metode serupa yang mengembalikan Tampilan Koleksi langsung

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
sumber
Bagaimana cara mencegah pengguna menghapus elemen? Apakah ada cara yang lebih baik daripada menggabungkan daftar menjadi unmodifiableLists?
newgre
2
@jn hanya membungkusnyaIterables.unmodifiableIterable(iterable)
Sean Patrick Floyd
2
Bagaimana dengan koleksinya? Iterables.concatmenghasilkan Iterable, bukan Collection. Saya butuh Collectionpemandangan.
Nowaker
@Damian satu-satunya fitur yang berguna adalah memiliki metode ukuran () agregat. Semua metode lain dalam antarmuka Koleksi akan memiliki semantik yang tidak ditentukan (tambah dll) atau kinerja yang buruk (berisi dll).
Sean Patrick Floyd
2
@ Sean, ya - size()adalah yang saya butuhkan. add()melempar pengecualian itu bagus - Saya tidak peduli dengan metode ini. Collections API rusak dan tidak ada yang bisa berbuat apa-apa. Collection.add(),, Iterator.remove()bla.
Nowaker
101

Solusi Plain Java 8 menggunakan file Stream.

Angka konstan

Dengan asumsi private Collection<T> c, c2, c3.

Satu solusi:

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

Solusi lain:

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

Nomor variabel

Dengan asumsi private Collection<Collection<T>> cs:

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

Jika Anda menggunakan setidaknya Java 8, lihat jawaban saya yang lain .

Jika Anda sudah menggunakan Google Guava, lihat jawaban Sean Patrick Floyd .

Jika Anda terjebak di Java 7 dan tidak ingin menyertakan Google Guava, Anda dapat menulis sendiri (hanya baca) Iterables.concat()menggunakan tidak lebih dari Iterabledan Iterator:

Angka konstan

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

Nomor variabel

@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.dll
sumber
1

Anda dapat membuat yang baru Listdan yang addAll()lain Listuntuk itu. Kemudian kembalikan daftar yang tidak dapat diubah dengan Collections.unmodifiableList().

Qwerky
sumber
3
Itu akan membuat koleksi sementara baru yang berpotensi cukup mahal
newgre
6
Mahal bagaimana , objek yang mendasari dalam daftar tidak disalin dan ArrayListhanya mengalokasikan ruang dan panggilan di System.arraycopy()bawah tenda. Tidak bisa jauh lebih efisien dari itu.
Qwerky
8
Bagaimana cara menyalin seluruh koleksi untuk setiap iterasi tidak mahal? Selain itu, Anda bisa menjadi lebih baik dari itu, lihat jawaban Seans.
newgre
Ia juga menggunakan implementasi asli untuk menyalin memori, ia tidak melakukan iterasi melalui array.
Qwerky
1
Nah jika itu menyalin array itu pasti algoritma O (n) yang tidak berskala dan memiliki kompleksitas yang sama seperti iterasi pada array sekali. Asumsikan setiap daftar berisi sejuta elemen, maka saya perlu menyalin beberapa juta elemen, hanya untuk mengulanginya. Ide buruk.
newgre
0

Inilah solusi saya untuk itu:

EDIT - ubah kode sedikit

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
sumber
Penerapan Anda membalik peran hasNext()dan next(). Yang pertama mengubah status iterator Anda sedangkan yang kedua tidak. Seharusnya sebaliknya. Menelepon next()tanpa menelepon hasNext()akan selalu berhasil null. Menelepon hasNext()tanpa menelepon next()akan membuang elemen. Anda next()juga tidak melempar NoSuchElementException, melainkan kembali null.
xehpuk