Nén luồng bằng cách sử dụng JDK8 với lambda (java.util.stream.Streams.zip)


149

Trong JDK 8 với lambda b93, có một lớp java.util.stream.Streams.zip trong b93 có thể được sử dụng để nén các luồng (điều này được minh họa trong hướng dẫn Khám phá Java8 Lambdas. Phần 1 của Dhananjay Nene ). Chức năng này :

Tạo một luồng kết hợp lười biếng và tuần tự có các yếu tố là kết quả của việc kết hợp các yếu tố của hai luồng.

Tuy nhiên vào năm B98, điều này đã biến mất. Nguyên vẹn Streamslớp thậm chí không thể truy cập trong java.util.stream trong b98 .

Chức năng này đã được di chuyển chưa, và nếu vậy làm cách nào để nén các luồng chính xác bằng cách sử dụng b98?

Ứng dụng mà tôi có trong tâm trí là trong triển khai java này của Shen , nơi tôi đã thay thế chức năng zip trong

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

các chức năng với mã khá dài dòng (không sử dụng chức năng từ b98).


3
Ah chỉ phát hiện ra rằng nó dường như đã bị xóa hoàn toàn: mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/
artella 15/07/13

"Khám phá Java8 Lambdas. Phần 1" - liên kết mới cho bài viết này là blog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1
Aleksei Egorov

Câu trả lời:


77

Tôi cũng cần điều này vì vậy tôi chỉ cần lấy mã nguồn từ b93 và đặt nó vào một lớp "tận dụng". Tôi đã phải sửa đổi nó một chút để làm việc với API hiện tại.

Để tham khảo ở đây, mã làm việc (tự chịu rủi ro ...):

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
Không phải luồng kết quả là SIZEDnếu một trong hai luồng SIZED, không phải cả hai?
Didier L

5
Tôi không nghĩ vậy. Cả hai luồng phải được SIZEDthực hiện để làm việc này. Nó thực sự phụ thuộc vào cách bạn xác định nén. Ví dụ, bạn có thể nén hai luồng có kích thước khác nhau không? Luồng kết quả sẽ trông như thế nào sau đó? Tôi tin rằng đây là lý do tại sao chức năng này thực sự bị bỏ qua khỏi API. Có nhiều cách để làm điều này và người dùng quyết định hành vi nào là "chính xác". Bạn sẽ loại bỏ các yếu tố từ luồng dài hơn hoặc đệm danh sách ngắn hơn? Nếu vậy, với giá trị nào (s)?
siki

Trừ khi tôi thiếu một cái gì đó, không cần bất kỳ diễn viên nào (ví dụ như Spliterator<A>).
jub0bs

Có một trang web nơi mã nguồn Java 8 b93 được lưu trữ không? Tôi gặp khó khăn khi tìm nó.
Starwarswii

42

zip là một trong những chức năng được cung cấp bởi thư viện 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"));

1
cũng được tìm thấy trong StreamEx: amaembo.github.io/streamex/javadoc/one/util/streamex/ gợi
tokland

34

Nếu bạn có Guava trong dự án của mình, bạn có thể sử dụng phương thức Streams.zip (đã được thêm vào trong Guava 21):

Trả về một luồng trong đó mỗi phần tử là kết quả của việc chuyển phần tử tương ứng của từng streamA và streamB sang hàm. Luồng kết quả sẽ chỉ dài bằng hai luồng đầu vào ngắn hơn; nếu một luồng dài hơn, các phần tử phụ của nó sẽ bị bỏ qua. Luồng kết quả không thể chia tách hiệu quả. Điều này có thể gây hại cho hiệu suất song song.

 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

Nén hai luồng bằng cách sử dụng JDK8 với lambda ( ý chính ).

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
Giải pháp tốt đẹp và (tương đối) nhỏ gọn! Yêu cầu bạn đặt import java.util.function.*;import java.util.stream.*;ở đầu tệp của bạn.
sffc

Lưu ý rằng đây là một hoạt động đầu cuối trên luồng. Điều này có nghĩa là đối với các luồng vô hạn, phương thức này bị hỏng
smac89

2
Quá nhiều hàm bao vô dụng: Ở đây () -> iteratorvà ở đây một lần nữa : iterable.spliterator(). Tại sao không thực hiện trực tiếp Spliteratorhơn là một Iterator? Kiểm tra @Doradus câu trả lời stackoverflow.com/a/46230233/1140754
Miguel Gamboa

20

Vì tôi không thể hình dung được việc sử dụng nén trên các bộ sưu tập ngoài các bộ sưu tập được lập chỉ mục (Danh sách) và tôi là một fan hâm mộ lớn của sự đơn giản, đây sẽ là giải pháp của tôi:

<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
Tôi nghĩ mapToObjectnên được mapToObj.
seanf

nếu danh sách không RandomAccess(ví dụ như trong danh sách được liên kết) thì điều này sẽ rất chậm
avmohan

Chắc chắn rồi. Nhưng hầu hết các nhà phát triển Java đều biết rằng LinkedList có hiệu năng kém cho các hoạt động truy cập chỉ mục.
Rafael

11

Các phương thức của lớp mà bạn đề cập đã được chuyển sang Streamchính giao diện có lợi cho các phương thức mặc định. Nhưng dường như zipphương pháp đã được gỡ bỏ. Có thể vì không rõ hành vi mặc định cho các luồng có kích thước khác nhau là gì. Nhưng thực hiện hành vi mong muốn là đơn giản:

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

Không phải predicatebạn đã chuyển qua bộ lọc có trạng thái sao? Điều đó vi phạm hợp đồng phương thức và đặc biệt sẽ không hoạt động khi xử lý luồng song song.
Andreas

2
@Andreas: không có giải pháp nào ở đây hỗ trợ xử lý song song. Vì các phương thức của tôi không trả về một luồng, nên chúng đảm bảo rằng các luồng không chạy song song. Tương tự, mã của câu trả lời được chấp nhận trả về một luồng có thể được chuyển thành song song nhưng thực tế sẽ không làm gì song song. Điều đó nói rằng, các vị từ đầy đủ được khuyến khích nhưng không vi phạm hợp đồng. Chúng thậm chí có thể được sử dụng trong ngữ cảnh song song nếu bạn đảm bảo rằng cập nhật trạng thái là an toàn cho chuỗi. Trong một số tình huống, chúng không thể tránh khỏi, ví dụ: biến một luồng thành khác biệt là một vị từ hoàn hảo cho mỗi se .
Holger

2
@Andreas: bạn có thể đoán tại sao các hoạt động này đã bị xóa khỏi API API
Holger

8

Tôi khiêm tốn đề nghị thực hiện này. Luồng kết quả được cắt ngắn hơn hai luồng đầu vào.

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

Tôi thích đề xuất của bạn. Nhưng tôi không hoàn toàn đồng ý với cuối cùng .., leftStream.isParallel() || rightStream.isParallel(). Tôi nghĩ rằng nó không có hiệu lực bởi vì AbstractSpliteratorcung cấp song song hạn chế theo mặc định. Vì vậy, tôi nghĩ rằng kết quả cuối cùng sẽ giống như vượt qua false.
Miguel Gamboa

@MiguelGamboa - cảm ơn bình luận của bạn. Tôi không chắc ý của bạn là "song song hạn chế theo mặc định" - bạn có liên kết đến một số tài liệu không?
Doradus

6

Thư viện Lazy-Seq cung cấp chức năng zip.

https://github.com/nurkiewicz/LazySeq

Thư viện này được truyền cảm hứng mạnh mẽ scala.collection.immutable.Streamvà nhằm mục đích cung cấp việc thực hiện chuỗi lười biếng bất biến, an toàn và dễ sử dụng, có thể là vô hạn.


5

Sử dụng thư viện Guava mới nhất (cho Streamslớp), bạn sẽ có thể làm

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

2

Liệu nó có giúp hiệu quả với anh không? Đây là một hàm ngắn, đánh giá một cách lười biếng qua các luồng mà nó đang nén, do đó bạn có thể cung cấp cho nó các luồng vô hạn (không cần phải lấy kích thước của các luồng được nén).

Nếu các luồng là hữu hạn, nó dừng lại ngay khi một trong các luồng hết các phần tử.

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

Đây là một số mã kiểm tra đơn vị (dài hơn nhiều so với chính mã!)

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

Tôi đã phải bỏ đi takeWhileở cuối là dường như không có trong java8 nhưng nó không phải là vấn đề vì callee có thể lọc bất kỳ null nào xảy ra khi các luồng được nén không cùng kích thước. Tôi nghĩ rằng câu trả lời này nên là câu trả lời số 1 vì nó phù hợp và dễ hiểu. công việc tuyệt vời cảm ơn một lần nữa.
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

Phản ứng cyclops của AOL , mà tôi đóng góp, cũng cung cấp chức năng nén, cả thông qua triển khai Stream mở rộng , cũng thực hiện giao diện Reactive-stream ReactiveSeq và thông qua StreamUtils cung cấp nhiều chức năng tương tự thông qua các phương thức tĩnh cho các luồng Java tiêu chuẩn.

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

Nó cũng cung cấp nén dựa trên ứng dụng tổng quát hơn. Ví dụ

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

Và thậm chí khả năng ghép mọi mục trong một luồng với mọi mục trong một mục khác

   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

Nếu bất cứ ai cần điều này, có StreamEx.zipWithchức năng trong thư viện 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"

-1

Điều đó thật tuyệt. Tôi đã phải nén hai luồng vào Bản đồ với một luồng là khóa và luồng khác là giá trị

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

Đầu ra: {A = Apple, B = Banana, C = Carrot}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.