Lambda ile JDK8 kullanarak akışları sıkıştırma (java.util.stream.Streams.zip)


149

Lambda b93 ile JDK 8'de, b93'te akışları sıkıştırmak için kullanılabilecek bir java.util.stream.Streams.zip sınıfı vardı (bu, Java8 Lambdas'ı Keşfetme dersinde gösterilmiştir. Dhananjay Nene tarafından Bölüm 1 ). Bu işlev:

Öğeleri iki akışın öğelerini birleştirmenin sonucu olan tembel ve sıralı bir birleşik Akış oluşturur.

Ancak b98'de bu ortadan kayboldu. Infact, b98'de java.util.stream'deStreams sınıfa bile erişilemez .

Bu işlevsellik taşındı ve b98 kullanarak akışları kısaca nasıl sıkıştırırım?

Aklımdaki uygulama Shen bu java uygulaması , nerede zip işlevselliği yerine

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

oldukça ayrıntılı kod içeren işlevler (b98'den itibaren işlevselliği kullanmaz)


3
Ah, tamamen kaldırıldığını öğrendi
artella

"Java8 Lambdas'ı Keşfetmek. Bölüm 1" - Bu makale için yeni bağlantı blog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1
Aleksei Egorov

Yanıtlar:


77

Ben de bu yüzden gerekli b93 kaynak kodu aldı ve bir "util" sınıf koymak. Mevcut API ile çalışmak için biraz değiştirmek zorunda kaldım.

Referans için çalışma kodu (kendi sorumluluğunuzdadır ...):

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

1
Sonuçta elde edilen akış SIZED, her iki akıştan biri SIZEDdeğilse, değil mi?
Didier L

5
Ben öyle düşünmüyorum. SIZEDBu uygulamanın çalışması için her iki akış da olmalıdır . Aslında sıkıştırmayı nasıl tanımladığınıza bağlıdır. Örneğin, farklı büyüklükteki iki akışı sıkıştırabiliyor musunuz? Ortaya çıkan akış o zaman nasıl görünür? Ben bu yüzden bu işlev aslında API atlandı inanıyorum. Bunu yapmanın birçok yolu vardır ve kullanıcının hangi davranışın "doğru" olması gerektiğine karar vermesi gerekir. Öğeleri daha uzun akıştan atar mı yoksa daha kısa listeyi doldurur musunuz? Öyleyse, hangi değerlerle?
siki

Bir şeyi kaçırmadıkça, herhangi bir oyuncuya (örneğin Spliterator<A>) gerek yoktur .
jub0bs

Java 8 b93 kaynak kodunun barındırıldığı bir web sitesi var mı? Onu bulmakta zorlanıyorum.
Starwarswii

42

zip, protonpack kütüphanesi tarafından sağlanan işlevlerden biridir .

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


34

Projenizde Guava varsa, Streams.zip yöntemini kullanabilirsiniz (Guava 21'de eklenmiştir):

Her öğenin, streamA ve streamB öğelerinin her birinin karşılık gelen öğesinin işlevine geçirilmesinin sonucu olduğu bir akış döndürür. Ortaya çıkan akış sadece iki giriş akışından daha kısa olduğu sürece olacaktır; bir akış daha uzunsa, ekstra öğeleri yok sayılır. Elde edilen akış verimli bir şekilde bölünemez. Bu paralel performansa zarar verebilir.

 public class Streams {
     ...

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

26

Lambda ile (JDK8 kullanılarak Sıkıştırma iki akım ana fikri ).

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

2
Güzel çözüm ve (nispeten) kompakt! Eğer koymak gerektirir import java.util.function.*;ve import java.util.stream.*;dosyanızın üstünde.
sffc

Bunun akıştaki bir terminal işlemi olduğunu unutmayın. Bu, sonsuz akışlar için bu yöntemin parçalandığı anlamına gelir
smac89

2
O kadar işe yaramaz sarmalayıcılar: Burada () -> iteratorve burada tekrar: iterable.spliterator(). Neden bir Spliteratoryerine doğrudan uygulama Iterator? @Doradus cevabını kontrol edin stackoverflow.com/a/46230233/1140754
Miguel Gamboa

20

Dizine alınmış olanlar (Listeler) dışındaki koleksiyonlar üzerinde sıkıştırma kullanamadığım ve basitliğin büyük bir hayranıyım, bu benim çözümüm olacak:

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

1
Bence mapToObjectolmalı mapToObj.
seanf

liste değilse RandomAccess(örneğin bağlantılı listelerde) bu çok yavaş olacaktır
avmohan

Kesinlikle. Ancak Java geliştiricilerinin çoğu LinkedList'in dizin erişim işlemleri için düşük performansa sahip olduğunun farkındadır.
Rafael

11

Belirttiğiniz sınıfın Streamyöntemleri varsayılan yöntemlere göre arabirimin kendisine taşınmıştır . Ancak, zipyöntemin kaldırıldığı anlaşılıyor . Belki de farklı büyüklükteki akışlar için varsayılan davranışın ne olması gerektiği açık değildir. Ancak istenen davranışı uygulamak basittir:

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

Değil mi predicateFiltre geçirilen durum bilgisi ? Bu yöntem sözleşmesini ihlal eder ve özellikle akışı paralel olarak işlerken işe yaramaz.
Andreas

2
@Andreas: Buradaki çözümlerin hiçbiri paralel işlemeyi desteklemiyor. Yöntemlerim bir akış döndürmediği için akışların paralel çalışmadığından emin olurlar. Benzer şekilde, kabul edilen cevabın kodu paralel olarak çevrilebilen ancak paralel olarak hiçbir şey yapmayacak bir akış döndürür. Bununla birlikte, durum tahminleri cesaret kırıldı, ancak sözleşmeyi ihlal etmedi. Durum güncellemesinin iş parçacığı açısından güvenli olduğundan emin olursanız, paralel bağlamda bile kullanılabilirler. Bazı durumlarda kaçınılmazdır, örneğin bir akışın farklı hale getirilmesi kendi başına geçerli bir yüklemdir .
Holger

2
@Andreas: Bu işlemlerin neden Java API'sinden kaldırıldığını tahmin edebilirsiniz…
Holger

8

Alçakgönüllülükle bu uygulamayı öneririm. Ortaya çıkan akış, iki giriş akışından daha kısa olacak şekilde kesilir.

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

Teklifini beğendim. Ama sonuncuya tamamen katılmıyorum .., leftStream.isParallel() || rightStream.isParallel(). Bunun bir etkisi olmadığını düşünüyorum çünkü AbstractSpliteratorvarsayılan olarak sınırlı paralellik sunuyor. Bu yüzden son sonucun geçmeyle aynı olacağını düşünüyorum false.
Miguel Gamboa

@MiguelGamboa - yorumunuz için teşekkürler. "Varsayılan olarak sınırlı paralellik" ile ne demek istediğinizden emin değilim - bazı dokümanlara bağlantınız var mı?
Doradus

6

Lazy-Seq kütüphanesi zip işlevselliği sağlar.

https://github.com/nurkiewicz/LazySeq

Bu kütüphane, büyük ölçüde esinlenilmiş scala.collection.immutable.Streamve değişmez, iş parçacığı açısından güvenli ve kullanımı kolay tembel dizi uygulaması, muhtemelen sonsuz sağlamayı amaçlamaktadır.


5

En son Guava kütüphanesini ( Streamssınıf için) kullanarak

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

2

Bu senin için işe yarar mı? Sıkıştırdığı akışları tembel olarak değerlendiren kısa bir işlevdir, böylece sonsuz akışları sağlayabilirsiniz (sıkıştırılmış akışların boyutunu almanıza gerek yoktur).

Eğer akışlar sonlu ise, akışlardan biri elemanlar biterse durur.

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

İşte bazı birim test kodu (kodun kendisinden çok daha uzun!)

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

takeWhilesonunda damla vardı java8 görünmüyor ama callee sıkıştırılmış akışları aynı boyutta olmadığında oluşan herhangi bir nulls filtre edebilirsiniz bir sorun değil oldu. Bu cevabın tutarlı ve anlaşılır olduğu için 1 numaralı cevap olması gerektiğini düşünüyorum. iyi iş tekrar teşekkürler.
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));
    }
}

1

AOL Tepegöz-tepki de hem bir yoluyla, işlevsellik sıkıştırma sağlar, ben katkıda hangi, genişletilmiş Akış uygulanması StreamUtils da ReactiveSeq arayüzü reaktif-akışları uygulayan, ve üzeri o kadar standart Java Akışları için statik yöntemlerle aynı işlevselliği sunuyor.

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

Ayrıca daha genelleştirilmiş Uygulamalı tabanlı sıkıştırma sunar. Örneğin

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

Ve hatta bir akıştaki her öğeyi başka bir öğedeki her öğeyle eşleştirme yeteneği

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

0

Henüz buna ihtiyaç duyan biri varsa, streamex kütüphanesinde StreamEx.zipWithfonksiyon vardır :

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"

-1

Bu harika. Bir akışı anahtar ve diğeri değer olmak üzere iki akışı bir Haritaya sıkıştırmak zorunda kaldım

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

Çıktı: {A = Elma, B = Muz, C = Havuç}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.