การบีบอัดสตรีมโดยใช้ JDK8 พร้อม lambda (java.util.stream.Streams.zip)


149

ใน JDK 8 ที่มีแลมบ์ดา b93 มีคลาสjava.util.stream.Streams.zip ใน b93ซึ่งสามารถใช้ในการสตรีม zip (นี่คือตัวอย่างในบทเรียนExploring Java8 Lambdas ตอนที่ 1 โดย Dhananjay Nene ) ฟังก์ชั่นนี้:

สร้างสตรีมรวมที่ขี้เกียจและต่อเนื่องกันซึ่งองค์ประกอบเป็นผลลัพธ์ของการรวมองค์ประกอบของสองสตรีม

อย่างไรก็ตามใน b98 นี้ได้หายไป Infact Streamsชั้นไม่ได้สามารถเข้าถึงได้ในjava.util.stream ใน B98

ฟังก์ชั่นนี้ได้ถูกย้ายไปแล้วหรือไม่และถ้าอย่างนั้นฉันจะซิปสตรีมอย่างกระชับโดยใช้ b98 ได้อย่างไร?

แอปพลิเคชันที่ฉันมีอยู่ในการใช้งานจาวาของ Shenซึ่งฉันได้แทนที่ฟังก์ชั่น zip ใน

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

ฟังก์ชั่นที่มีรหัสค่อนข้าง verbose (ซึ่งไม่ได้ใช้ฟังก์ชั่นจาก b98)


3
อาเพิ่งค้นพบว่าดูเหมือนว่าจะถูกลบออกอย่างสมบูรณ์: mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/ …
artella

"Exploring Java8 Lambdas. ตอนที่ 1" - ลิงก์ใหม่สำหรับบทความนี้คือblog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1
Aleksei Egorov

คำตอบ:


77

ฉันต้องการสิ่งนี้เช่นกันดังนั้นฉันเพิ่งเอาซอร์สโค้ดจาก b93 และวางในคลาส "util" ฉันต้องแก้ไขมันเล็กน้อยเพื่อให้ทำงานกับ API ปัจจุบัน

สำหรับการอ้างอิงนี่คือรหัสการใช้งาน (รับความเสี่ยงของคุณเอง ... ):

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
ไม่ควรกระแสที่เกิดขึ้นจะเป็นSIZEDถ้าทั้งกระแสคือSIZEDไม่ทั้งสอง?
Didier L

5
ฉันไม่คิดอย่างนั้น สตรีมทั้งสองต้องSIZEDใช้เพื่อการใช้งานนี้เพื่อให้ทำงานได้ มันขึ้นอยู่กับวิธีการกำหนดซิปของคุณ คุณควรจะสามารถซิปสองสตรีมที่มีขนาดต่างกันหรือไม่? กระแสข้อมูลที่ได้จะเป็นอย่างไร ฉันเชื่อว่านี่คือเหตุผลที่ฟังก์ชันนี้ถูกตัดออกจาก API จริงๆ มีหลายวิธีในการทำเช่นนี้และขึ้นอยู่กับผู้ใช้ในการตัดสินใจว่าพฤติกรรมใดควรเป็น "ที่ถูกต้อง" คุณจะละทิ้งองค์ประกอบจากสตรีมที่ยาวขึ้นหรือเพิ่มรายการที่สั้นกว่านี้หรือไม่? ถ้าใช่มีค่าอะไรบ้าง
siki

นอกจากว่าฉันจะพลาดอะไรบางอย่างไปก็ไม่จำเป็นต้องมีนักแสดงอื่น (เช่นSpliterator<A>)
jub0bs

มีเว็บไซต์ที่โฮสต์ซอร์สโค้ด Java 8 b93 อยู่หรือไม่? ฉันมีปัญหาในการค้นหา
Starwarswii

42

ซิปเป็นหนึ่งในฟังก์ชั่นที่มีให้โดยห้องสมุด 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"));


34

หากคุณมี Guava ในโครงการของคุณคุณสามารถใช้วิธีStreams.zip (เพิ่มใน Guava 21):

ส่งคืนกระแสข้อมูลที่แต่ละองค์ประกอบเป็นผลลัพธ์ของการส่งผ่านองค์ประกอบที่สอดคล้องกันของแต่ละ streamA และ streamB เพื่อทำงาน สตรีมที่ได้นั้นจะยาวตราบเท่าที่สตรีมอินพุตทั้งสองสั้นลงเท่านั้น หากสตรีมหนึ่งยาวกว่าองค์ประกอบพิเศษจะถูกละเว้น กระแสที่ได้นั้นไม่สามารถแยกได้อย่างมีประสิทธิภาพ สิ่งนี้อาจเป็นอันตรายต่อประสิทธิภาพการทำงานแบบขนาน

 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

การบีบอัดสตรีมสองรายการโดยใช้ JDK8 กับ lambda ( gist )

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
ทางออกที่ดีและกะทัดรัด (ค่อนข้าง)! ต้องการให้คุณวางimport java.util.function.*;และimport java.util.stream.*;ที่ด้านบนของไฟล์ของคุณ
sffc

โปรดทราบว่านี่เป็นการดำเนินการของเทอร์มินัลบนสตรีม ซึ่งหมายความว่าสำหรับสตรีมแบบไม่สิ้นสุดวิธีนี้จะหยุดทำงาน
smac89

2
ห่อที่ไร้ประโยชน์มาก: ที่นี่() -> iteratorและที่นี่อีกครั้ง: iterable.spliterator(). ทำไมไม่ดำเนินการโดยตรงSpliteratorมากกว่าIterator? ตรวจสอบ @Doradus คำตอบstackoverflow.com/a/46230233/1140754
Miguel Gamboa

20

เนื่องจากฉันไม่สามารถใช้การซิปในคอลเลกชันอื่น ๆ นอกเหนือจากรายการที่จัดทำดัชนี (รายการ) และฉันเป็นแฟนตัวยงของความเรียบง่ายนี่จะเป็นทางออกของฉัน:

<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
ผมคิดว่าควรจะเป็นmapToObject mapToObj
seanf

หากรายการไม่RandomAccess(สำหรับเช่นในรายการที่เชื่อมโยง) สิ่งนี้จะช้ามาก
avmohan

อย่างแน่นอน. แต่นักพัฒนา Java ส่วนใหญ่ทราบดีว่า LinkedList มีประสิทธิภาพต่ำสำหรับการเข้าถึงดัชนี
Rafael

11

วิธีการของคลาสที่คุณกล่าวถึงถูกย้ายไปยังStreamอินเทอร์เฟซของตัวเองในความโปรดปรานกับวิธีการเริ่มต้น แต่ดูเหมือนว่าzipวิธีการที่ถูกลบ อาจเป็นเพราะยังไม่ชัดเจนว่าพฤติกรรมเริ่มต้นสำหรับสตรีมขนาดต่างกันควรเป็นอย่างไร แต่การใช้พฤติกรรมที่ต้องการนั้นตรงไปตรงมา:

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

ไม่ได้เป็นpredicateคุณส่งไปกรองstateful ? ที่ละเมิดสัญญาวิธีการและโดยเฉพาะอย่างยิ่งจะไม่ทำงานเมื่อประมวลผลสตรีมในแบบคู่ขนาน
Andreas

2
@Andreas: ไม่มีวิธีการแก้ปัญหาที่นี่รองรับการประมวลผลแบบขนาน เนื่องจากวิธีการของฉันไม่ส่งคืนกระแสข้อมูลพวกเขาตรวจสอบให้แน่ใจว่ากระแสไม่ทำงานขนาน ในทำนองเดียวกันรหัสของคำตอบที่ได้รับการยอมรับจะส่งคืนสตรีมที่สามารถเปลี่ยนเป็นแบบขนานได้ แต่จะไม่ทำสิ่งใดขนานกัน ที่กล่าวว่า predicates รัฐจะท้อแท้ แต่ไม่ละเมิดสัญญา อาจถูกใช้ในบริบทแบบขนานหากคุณมั่นใจว่าการอัพเดตสถานะนั้นปลอดภัยต่อเธรด ในบางสถานการณ์พวกเขาจะหลีกเลี่ยงไม่ได้เช่นการเปลี่ยนกระแสเป็นความชัดเจนเป็นคำกริยาสถานะต่อเต็ม
โฮล

2
@Andreas: คุณอาจเดาได้ว่าเหตุใดการดำเนินการเหล่านี้จึงถูกลบออกจาก Java API ...
โฮล

8

ฉันเจียมแนะนำการใช้งานนี้ สตรีมผลลัพธ์ถูกตัดให้สั้นลงของสตรีมอินพุตสองรายการที่สั้นกว่า

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

ฉันชอบข้อเสนอของคุณ แต่ฉันไม่เห็นด้วยกับที่ผ่านมา.., leftStream.isParallel() || rightStream.isParallel()ทั้งหมด ฉันคิดว่ามันไม่มีผลเพราะAbstractSpliteratorมีการ จำกัด ความเท่าเทียมโดยปริยาย falseดังนั้นผมคิดว่าผลสุดท้ายจะเป็นเช่นเดียวกับการส่งผ่าน
Miguel Gamboa

@MiguelGamboa - ขอบคุณสำหรับความคิดเห็นของคุณ ฉันไม่แน่ใจว่าสิ่งที่คุณหมายถึงโดย "จำกัด ขนานโดยปริยาย" - คุณมีลิงค์ไปยังเอกสารบางอย่าง?
Doradus

6

ไลบรารี Lazy-Seq มีฟังก์ชันการทำงานของ zip

https://github.com/nurkiewicz/LazySeq

ไลบรารีนี้ได้รับแรงบันดาลใจอย่างมากจากscala.collection.immutable.Streamและมีจุดมุ่งหมายเพื่อให้การใช้งานชุดลำดับขี้เกียจไม่เปลี่ยนรูปและใช้งานง่ายซึ่งอาจไม่มีที่สิ้นสุด


5

การใช้ห้องสมุด Guava ล่าสุด (สำหรับStreamsชั้นเรียน) คุณควรจะสามารถทำได้

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

2

สิ่งนี้จะใช้ได้สำหรับคุณหรือไม่ มันเป็นฟังก์ชั่นสั้น ๆ ซึ่งประเมินผลสตรีมอย่างซิปอย่างอิสระเพื่อให้คุณสามารถจัดหาให้กับสตรีมแบบไม่มีที่สิ้นสุด (ไม่จำเป็นต้องใช้ขนาดของสตรีมที่ซิป)

หากกระแสมี จำกัด มันจะหยุดทันทีที่หนึ่งในลำธารไหลออกจากองค์ประกอบ

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

นี่คือรหัสทดสอบหน่วย (ยาวกว่ารหัสมาก!)

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

ฉันต้องวางtakeWhileในตอนท้ายก็ไม่ได้ดูเหมือนจะอยู่ใน java8 แต่มันก็ไม่เป็นปัญหาเพราะผู้ใช้งานสามารถกรองโมฆะใด ๆ ที่เกิดขึ้นเมื่อกระแสซิปไม่ขนาดเท่ากัน ฉันคิดว่าคำตอบนี้ควรเป็นหมายเลข 1 คำตอบเพราะมันประกอบด้วยและเข้าใจได้ ขอบคุณมากอีกครั้ง
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 ซึ่งฉันมีส่วนร่วมยังให้ฟังก์ชันการซิปทั้งผ่านการใช้งาน Stream ที่เพิ่มขึ้นซึ่งยังใช้ Reactive-stream interface ReactiveSeq และ StreamUtils ที่มีฟังก์ชั่นเดียวกันมากมายผ่านวิธีการแบบคงที่กับ 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));

นอกจากนี้ยังมีการบีบอัดที่ใช้งานทั่วไปเพิ่มเติม เช่น

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

และแม้แต่ความสามารถในการจับคู่ทุกรายการในสตรีมเดียวกับทุกรายการในอีกสตรีม

   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

หากใครต้องการสิ่งนี้ยังมีStreamEx.zipWithฟังก์ชั่นในไลบรารี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

มันเยี่ยมมาก ฉันต้องซิปสองสตรีมลงในแผนที่โดยสตรีมหนึ่งเป็นกุญแจและอีกอันเป็นค่า

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

ผลลัพธ์: {A = Apple, B = Banana, C = Carrot}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.