Zipping stream menggunakan JDK8 dengan lambda (java.util.stream.Streams.zip)

149

Dalam JDK 8 dengan lambda b93 ada kelas java.util.stream.Streams.zip di b93 yang dapat digunakan untuk zip stream (ini diilustrasikan dalam tutorial Menjelajahi Java8 Lambdas. Bagian 1 oleh Dhananjay Nene ). Fungsi ini:

Membuat aliran gabungan malas dan berurutan yang unsur-unsurnya adalah hasil dari menggabungkan unsur-unsur dari dua aliran.

Namun pada b98 ini telah menghilang. Infact Streamskelas bahkan tidak dapat diakses di java.util.stream di b98 .

Apakah fungsi ini telah dipindahkan, dan jika demikian bagaimana cara zip stream secara ringkas menggunakan b98?

Aplikasi yang ada dalam pikiran saya adalah dalam implementasi java dari Shen , di mana saya mengganti fungsi zip di

  • static <T> boolean every(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred)
  • static <T> T find(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred)

fungsi dengan kode yang agak verbose (yang tidak menggunakan fungsionalitas dari b98).

artella
sumber
3
Ah baru mengetahui bahwa tampaknya telah dihapus sepenuhnya: mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/…
artella
"Exploring Java8 Lambdas. Bagian 1" - tautan baru untuk artikel ini adalah blog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1
Aleksei Egorov

Jawaban:

77

Saya membutuhkan ini juga jadi saya hanya mengambil kode sumber dari b93 dan memasukkannya ke dalam kelas "util". Saya harus memodifikasinya sedikit agar berfungsi dengan API saat ini.

Untuk referensi, inilah kode kerjanya (ambil risiko Anda sendiri ...):

public static<A, B, C> Stream<C> zip(Stream<? extends A> a,
                                     Stream<? extends B> b,
                                     BiFunction<? super A, ? super B, ? extends C> zipper) {
    Objects.requireNonNull(zipper);
    Spliterator<? extends A> aSpliterator = Objects.requireNonNull(a).spliterator();
    Spliterator<? extends B> bSpliterator = Objects.requireNonNull(b).spliterator();

    // Zipping looses DISTINCT and SORTED characteristics
    int characteristics = aSpliterator.characteristics() & bSpliterator.characteristics() &
            ~(Spliterator.DISTINCT | Spliterator.SORTED);

    long zipSize = ((characteristics & Spliterator.SIZED) != 0)
            ? Math.min(aSpliterator.getExactSizeIfKnown(), bSpliterator.getExactSizeIfKnown())
            : -1;

    Iterator<A> aIterator = Spliterators.iterator(aSpliterator);
    Iterator<B> bIterator = Spliterators.iterator(bSpliterator);
    Iterator<C> cIterator = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return aIterator.hasNext() && bIterator.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(aIterator.next(), bIterator.next());
        }
    };

    Spliterator<C> split = Spliterators.spliterator(cIterator, zipSize, characteristics);
    return (a.isParallel() || b.isParallel())
           ? StreamSupport.stream(split, true)
           : StreamSupport.stream(split, false);
}
siki
sumber
1
Bukankah seharusnya aliran yang dihasilkan SIZEDjika salah satu aliran SIZED, bukan keduanya?
Didier L
5
Saya kira tidak. Kedua aliran harus agar SIZEDimplementasi ini berfungsi. Ini sebenarnya tergantung pada bagaimana Anda mendefinisikan zipping. Haruskah Anda dapat meng-zip dua aliran dengan ukuran yang berbeda, misalnya? Seperti apa aliran yang dihasilkan nantinya? Saya percaya inilah mengapa fungsi ini sebenarnya dihilangkan dari API. Ada banyak cara untuk melakukan ini dan terserah kepada pengguna untuk memutuskan perilaku apa yang harus menjadi "benar". Apakah Anda akan membuang elemen dari aliran yang lebih panjang atau mengisi daftar yang lebih pendek? Jika demikian, dengan nilai apa?
siki
Kecuali jika saya melewatkan sesuatu, tidak perlu ada pemeran (misalnya untuk Spliterator<A>).
jub0bs
Apakah ada situs web tempat kode sumber Java 8 b93 dihosting? Saya mengalami kesulitan menemukannya.
Starwarswii
42

zip adalah salah satu fungsi yang disediakan oleh perpustakaan protonpack .

Stream<String> streamA = Stream.of("A", "B", "C");
Stream<String> streamB  = Stream.of("Apple", "Banana", "Carrot", "Doughnut");

List<String> zipped = StreamUtils.zip(streamA,
                                      streamB,
                                      (a, b) -> a + " is for " + b)
                                 .collect(Collectors.toList());

assertThat(zipped,
           contains("A is for Apple", "B is for Banana", "C is for Carrot"));
Dominic Fox
sumber
1
juga ditemukan di StreamEx: amaembo.github.io/streamex/javadoc/one/util/streamex/…
tokland
34

Jika Anda memiliki Guava di proyek Anda, Anda dapat menggunakan metode Streams.zip (ditambahkan dalam Guava 21):

Mengembalikan aliran di mana setiap elemen adalah hasil dari melewati elemen yang sesuai dari masing-masing streamA dan streamB berfungsi. Aliran yang dihasilkan hanya akan sepanjang aliran kedua input yang lebih pendek; jika satu aliran lebih panjang, elemen tambahannya akan diabaikan. Aliran yang dihasilkan tidak dapat dipisah secara efisien. Ini dapat merusak kinerja paralel.

 public class Streams {
     ...

     public static <A, B, R> Stream<R> zip(Stream<A> streamA,
             Stream<B> streamB, BiFunction<? super A, ? super B, R> function) {
         ...
     }
 }
ZhekaKozlov
sumber
26

Zip dua aliran menggunakan JDK8 dengan lambda ( inti ).

public static <A, B, C> Stream<C> zip(Stream<A> streamA, Stream<B> streamB, BiFunction<A, B, C> zipper) {
    final Iterator<A> iteratorA = streamA.iterator();
    final Iterator<B> iteratorB = streamB.iterator();
    final Iterator<C> iteratorC = new Iterator<C>() {
        @Override
        public boolean hasNext() {
            return iteratorA.hasNext() && iteratorB.hasNext();
        }

        @Override
        public C next() {
            return zipper.apply(iteratorA.next(), iteratorB.next());
        }
    };
    final boolean parallel = streamA.isParallel() || streamB.isParallel();
    return iteratorToFiniteStream(iteratorC, parallel);
}

public static <T> Stream<T> iteratorToFiniteStream(Iterator<T> iterator, boolean parallel) {
    final Iterable<T> iterable = () -> iterator;
    return StreamSupport.stream(iterable.spliterator(), parallel);
}
Karol Król
sumber
2
Solusi yang bagus dan ringkas (relatif)! Mengharuskan Anda meletakkan import java.util.function.*;dan import java.util.stream.*;di atas file Anda.
sffc
Perhatikan bahwa ini adalah operasi terminal di sungai. Ini berarti bahwa untuk aliran tanpa batas, metode ini rusak
smac89
2
Begitu banyak pembungkus berguna: Berikut () -> iteratordan di sini lagi: iterable.spliterator(). Mengapa tidak menerapkan langsung Spliteratordaripada Iterator? Periksa @Doradus jawab stackoverflow.com/a/46230233/1140754
Miguel Gamboa
20

Karena saya tidak bisa membayangkan penggunaan zip pada koleksi selain yang diindeks (Daftar) dan saya penggemar berat kesederhanaan, ini akan menjadi solusi saya:

<A,B,C>  Stream<C> zipped(List<A> lista, List<B> listb, BiFunction<A,B,C> zipper){
     int shortestLength = Math.min(lista.size(),listb.size());
     return IntStream.range(0,shortestLength).mapToObj( i -> {
          return zipper.apply(lista.get(i), listb.get(i));
     });        
}
Rafael
sumber
1
Saya pikir mapToObjectseharusnya begitu mapToObj.
seanf
jika daftar tidak RandomAccess(misalnya pada daftar tertaut) ini akan sangat lambat
avmohan
Pastinya. Tetapi sebagian besar pengembang Java menyadari bahwa LinkedList memiliki kinerja yang buruk untuk operasi akses indeks.
Rafael
11

Metode kelas yang Anda sebutkan telah dipindahkan ke Streamantarmuka itu sendiri yang mendukung metode default. Tetapi tampaknya zipmetode ini telah dihapus. Mungkin karena tidak jelas apa perilaku default untuk aliran ukuran yang berbeda seharusnya. Tetapi menerapkan perilaku yang diinginkan sangat mudah:

static <T> boolean every(
  Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred) {
    Iterator<T> it=c2.iterator();
    return c1.stream().allMatch(x->!it.hasNext()||pred.test(x, it.next()));
}
static <T> T find(Collection<T> c1, Collection<T> c2, BiPredicate<T, T> pred) {
    Iterator<T> it=c2.iterator();
    return c1.stream().filter(x->it.hasNext()&&pred.test(x, it.next()))
      .findFirst().orElse(null);
}
Holger
sumber
Bukankah predicateAnda lulus ke filter stateful ? Itu melanggar kontrak metode dan terutama tidak akan berfungsi saat memproses aliran secara paralel.
Andreas
2
@ Andreas: tidak ada solusi di sini yang mendukung pemrosesan paralel. Karena metode saya tidak mengembalikan aliran, mereka memastikan aliran tidak berjalan secara paralel. Demikian pula, kode jawaban yang diterima mengembalikan aliran yang dapat diubah menjadi paralel tetapi tidak akan benar-benar melakukan apa pun secara paralel. Yang mengatakan, predikat statefull berkecil hati tetapi tidak melanggar kontrak. Mereka bahkan dapat digunakan dalam konteks paralel jika Anda memastikan bahwa pembaruan status aman-utas. Dalam beberapa situasi mereka tidak dapat dihindari, misalnya mengubah aliran menjadi berbeda adalah predikat penuh per se .
Holger
2
@Andreas: Anda mungkin menebak mengapa operasi ini telah dihapus dari Java API…
Holger
8

Saya dengan rendah hati menyarankan implementasi ini. Aliran yang dihasilkan dipotong ke yang lebih pendek dari dua aliran input.

public static <L, R, T> Stream<T> zip(Stream<L> leftStream, Stream<R> rightStream, BiFunction<L, R, T> combiner) {
    Spliterator<L> lefts = leftStream.spliterator();
    Spliterator<R> rights = rightStream.spliterator();
    return StreamSupport.stream(new AbstractSpliterator<T>(Long.min(lefts.estimateSize(), rights.estimateSize()), lefts.characteristics() & rights.characteristics()) {
        @Override
        public boolean tryAdvance(Consumer<? super T> action) {
            return lefts.tryAdvance(left->rights.tryAdvance(right->action.accept(combiner.apply(left, right))));
        }
    }, leftStream.isParallel() || rightStream.isParallel());
}
Doradus
sumber
Saya suka proposal Anda. Tapi saya tidak sepenuhnya setuju dengan yang terakhir .., leftStream.isParallel() || rightStream.isParallel(). Saya pikir itu tidak berpengaruh karena AbstractSpliteratormenawarkan paralelisme terbatas secara default. Jadi saya pikir hasil akhirnya akan sama dengan passing false.
Miguel Gamboa
@MiguelGamboa - terima kasih atas komentar Anda. Saya tidak yakin apa yang Anda maksud dengan "paralelisme terbatas secara default" - apakah Anda memiliki tautan ke beberapa dokumen?
Doradus
6

Perpustakaan Lazy-Seq menyediakan fungsionalitas zip.

https://github.com/nurkiewicz/LazySeq

Perpustakaan ini sangat terinspirasi oleh scala.collection.immutable.Streamdan bertujuan untuk menyediakan implementasi urutan lazy yang tidak berubah, aman dan mudah digunakan, mungkin tak terbatas.

Nick Siderakis
sumber
5

Menggunakan perpustakaan Guava terbaru (untuk Streamskelas) yang harus Anda lakukan

final Map<String, String> result = 
    Streams.zip(
        collection1.stream(), 
        collection2.stream(), 
        AbstractMap.SimpleEntry::new)
    .collect(Collectors.toMap(e -> e.getKey(), e  -> e.getValue()));
Dan Borza
sumber
2

Apakah ini akan berhasil untuk Anda? Ini adalah fungsi pendek, yang dengan malas mengevaluasi aliran yang zip, sehingga Anda dapat menyediakannya dengan aliran yang tak terbatas (tidak perlu mengambil ukuran aliran yang di-zip).

Jika aliran terbatas, ia berhenti segera setelah salah satu aliran kehabisan elemen.

import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Stream;

class StreamUtils {
    static <ARG1, ARG2, RESULT> Stream<RESULT> zip(
            Stream<ARG1> s1,
            Stream<ARG2> s2,
            BiFunction<ARG1, ARG2, RESULT> combiner) {
        final var i2 = s2.iterator();
        return s1.map(x1 -> i2.hasNext() ? combiner.apply(x1, i2.next()) : null)
                .takeWhile(Objects::nonNull);
    }
}

Berikut adalah beberapa kode uji unit (lebih lama dari kode itu sendiri!)

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;

class StreamUtilsTest {
    @ParameterizedTest
    @MethodSource("shouldZipTestCases")
    <ARG1, ARG2, RESULT>
    void shouldZip(
            String testName,
            Stream<ARG1> s1,
            Stream<ARG2> s2,
            BiFunction<ARG1, ARG2, RESULT> combiner,
            Stream<RESULT> expected) {
        var actual = StreamUtils.zip(s1, s2, combiner);

        assertEquals(
                expected.collect(Collectors.toList()),
                actual.collect(Collectors.toList()),
                testName);
    }

    private static Stream<Arguments> shouldZipTestCases() {
        return Stream.of(
                Arguments.of(
                        "Two empty streams",
                        Stream.empty(),
                        Stream.empty(),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.empty()),
                Arguments.of(
                        "One singleton and one empty stream",
                        Stream.of(1),
                        Stream.empty(),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.empty()),
                Arguments.of(
                        "One empty and one singleton stream",
                        Stream.empty(),
                        Stream.of(1),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.empty()),
                Arguments.of(
                        "Two singleton streams",
                        Stream.of("blah"),
                        Stream.of(1),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("blah", 1))),
                Arguments.of(
                        "One singleton, one multiple stream",
                        Stream.of("blob"),
                        Stream.of(2, 3),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("blob", 2))),
                Arguments.of(
                        "One multiple, one singleton stream",
                        Stream.of("foo", "bar"),
                        Stream.of(4),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("foo", 4))),
                Arguments.of(
                        "Two multiple streams",
                        Stream.of("nine", "eleven"),
                        Stream.of(10, 12),
                        (BiFunction<Object, Object, Object>) StreamUtilsTest::combine,
                        Stream.of(pair("nine", 10), pair("eleven", 12)))
        );
    }

    private static List<Object> pair(Object o1, Object o2) {
        return List.of(o1, o2);
    }

    static private <T1, T2> List<Object> combine(T1 o1, T2 o2) {
        return List.of(o1, o2);
    }

    @Test
    void shouldLazilyEvaluateInZip() {
        final var a = new AtomicInteger();
        final var b = new AtomicInteger();
        final var zipped = StreamUtils.zip(
                Stream.generate(a::incrementAndGet),
                Stream.generate(b::decrementAndGet),
                (xa, xb) -> xb + 3 * xa);

        assertEquals(0, a.get(), "Should not have evaluated a at start");
        assertEquals(0, b.get(), "Should not have evaluated b at start");

        final var takeTwo = zipped.limit(2);

        assertEquals(0, a.get(), "Should not have evaluated a at take");
        assertEquals(0, b.get(), "Should not have evaluated b at take");

        final var list = takeTwo.collect(Collectors.toList());

        assertEquals(2, a.get(), "Should have evaluated a after collect");
        assertEquals(-2, b.get(), "Should have evaluated b after collect");
        assertEquals(List.of(2, 4), list);
    }
}
Dominikus
sumber
saya harus drop takeWhilepada akhirnya adalah yang tampaknya tidak berada di java8 tetapi itu tidak masalah karena callee dapat menyaring nol yang terjadi ketika zip zip bukan ukuran yang sama. Saya pikir jawaban ini harus menjadi jawaban nomor 1 karena terdiri dan dapat dimengerti. kerja bagus terima kasih lagi.
simbo1905
1
public class Tuple<S,T> {
    private final S object1;
    private final T object2;

    public Tuple(S object1, T object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    public S getObject1() {
        return object1;
    }

    public T getObject2() {
        return object2;
    }
}


public class StreamUtils {

    private StreamUtils() {
    }

    public static <T> Stream<Tuple<Integer,T>> zipWithIndex(Stream<T> stream) {
        Stream<Integer> integerStream = IntStream.range(0, Integer.MAX_VALUE).boxed();
        Iterator<Integer> integerIterator = integerStream.iterator();
        return stream.map(x -> new Tuple<>(integerIterator.next(), x));
    }
}
robby_pelssers
sumber
1

Siklus-reaksi AOL , yang saya sumbangkan, juga menyediakan fungsionalitas zipping, baik melalui implementasi Stream yang diperluas , yang juga mengimplementasikan antarmuka reaktif-aliran ReactiveSeq, dan melalui StreamUtils yang menawarkan banyak fungsi yang sama melalui metode statis ke standar Java Streams.

 List<Tuple2<Integer,Integer>> list =  ReactiveSeq.of(1,2,3,4,5,6)
                                                  .zip(Stream.of(100,200,300,400));


  List<Tuple2<Integer,Integer>> list = StreamUtils.zip(Stream.of(1,2,3,4,5,6),
                                                  Stream.of(100,200,300,400));

Ini juga menawarkan zip berbasis Aplikasi yang lebih umum. Misalnya

   ReactiveSeq.of("a","b","c")
              .ap3(this::concat)
              .ap(of("1","2","3"))
              .ap(of(".","?","!"))
              .toList();

   //List("a1.","b2?","c3!");

   private String concat(String a, String b, String c){
    return a+b+c;
   }

Dan bahkan kemampuan untuk memasangkan setiap item dalam satu aliran dengan setiap item dalam yang lain

   ReactiveSeq.of("a","b","c")
              .forEach2(str->Stream.of(str+"!","2"), a->b->a+"_"+b);

   //ReactiveSeq("a_a!","a_2","b_b!","b_2","c_c!","c2")
John McClean
sumber
0

Jika ada yang membutuhkan ini, ada StreamEx.zipWithfungsi di perpustakaan streamex :

StreamEx<String> givenNames = StreamEx.of("Leo", "Fyodor")
StreamEx<String> familyNames = StreamEx.of("Tolstoy", "Dostoevsky")
StreamEx<String> fullNames = givenNames.zipWith(familyNames, (gn, fn) -> gn + " " + fn);

fullNames.forEach(System.out::println);  // prints: "Leo Tolstoy\nFyodor Dostoevsky\n"
const.grigoryev
sumber
-1

Ini bagus. Saya harus memasukkan dua aliran ke dalam Peta dengan satu aliran menjadi kunci dan lainnya menjadi nilainya

Stream<String> streamA = Stream.of("A", "B", "C");
Stream<String> streamB  = Stream.of("Apple", "Banana", "Carrot", "Doughnut");    
final Stream<Map.Entry<String, String>> s = StreamUtils.zip(streamA,
                    streamB,
                    (a, b) -> {
                        final Map.Entry<String, String> entry = new AbstractMap.SimpleEntry<String, String>(a, b);
                        return entry;
                    });

System.out.println(s.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue())));

Output: {A = Apple, B = Banana, C = Wortel}

Gnana
sumber