LDda (java.util.stream.Streams.zip) के साथ JDK8 का उपयोग करते हुए धाराएँ


149

JDK 8 में lambda b93 के साथ b93 में एक वर्ग java.util.stream.Streams.zip था जिसे धाराओं को ज़िप करने के लिए इस्तेमाल किया जा सकता था (इसे Java8 Lambdas के ट्यूटोरियल में चित्रित किया गया है । धनंजय नेने द्वारा भाग 1 )। यह समारोह:

एक आलसी और अनुक्रमिक संयुक्त स्ट्रीम बनाता है जिसके तत्व दो धाराओं के तत्वों के संयोजन का परिणाम हैं।

हालांकि b98 में यह गायब हो गया है। सक्रिय Streamsवर्ग b98 में java.util.stream में भी सुलभ नहीं है ।

क्या इस कार्यक्षमता को स्थानांतरित कर दिया गया है, और यदि ऐसा है तो मैं b98 का ​​उपयोग करते हुए स्पष्ट रूप से कैसे स्ट्रीम करता हूं?

मेरे पास जो एप्लिकेशन है वह शेन के इस जावा कार्यान्वयन में है , जहां मैंने ज़िप कार्यक्षमता को बदल दिया है

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

बल्कि क्रिया कोड के साथ कार्य (जो b98 से कार्यक्षमता का उपयोग नहीं करता है)।


3
आह बस पता चला कि यह पूरी तरह से हटा दिया गया लगता है: mail.openjdk.java.net/pipermail/lambda-libs-spec-observers/…
Artella

"जावा 8 लैंबडास की खोज। भाग 1" - इस लेख का नया लिंक है blog.dhananjaynene.com/2013/02/exploring-java8-lambdas-part-1
अलेक्सी

जवाबों:


77

मुझे इसकी भी आवश्यकता थी इसलिए मैंने बस b93 से स्रोत कोड लिया और इसे "उपयोग" वर्ग में डाल दिया। मुझे वर्तमान एपीआई के साथ काम करने के लिए इसे थोड़ा संशोधित करना पड़ा।

यहाँ संदर्भ के लिए काम कर कोड (इसे अपने जोखिम पर लें ...):

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, दोनों नहीं?
डिडियर एल

5
मुझे ऐसा नहीं लगता। SIZEDइस कार्य को कार्यान्वित करने के लिए दोनों धाराएँ होनी चाहिए । यह वास्तव में इस बात पर निर्भर करता है कि आप ज़िपिंग को कैसे परिभाषित करते हैं। क्या आपको उदाहरण के लिए, दो धाराएँ अलग-अलग आकार की हो सकती हैं? परिणामी धारा तब कैसी दिखती होगी? मेरा मानना ​​है कि यही कारण है कि यह फ़ंक्शन वास्तव में एपीआई से छोड़ा गया था। ऐसा करने के कई तरीके हैं और उपयोगकर्ता को यह तय करना है कि व्यवहार "सही" होना चाहिए। क्या आप तत्वों को लंबी स्ट्रीम से हटा देंगे या छोटी सूची को पैड कर देंगे? यदि हां, तो किस मूल्य के साथ?
सिक्की

जब तक मैं कुछ याद कर रहा हूं, तब तक किसी भी कास्ट (जैसे Spliterator<A>) की कोई आवश्यकता नहीं है ।
जुब।

क्या कोई वेबसाइट है जहाँ Java 8 b93 स्रोत कोड होस्ट किया गया है? मुझे इसे ढूंढने में परेशानी हो रही है।
Starwarswii

42

ज़िप प्रोटोनपैक लाइब्रेरी द्वारा प्रदान किए गए कार्यों में से एक है ।

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
StreamEx में भी पाया गया: amaembo.github.io/streamex/javadoc/one/util/streamex/…
tokland

34

यदि आपके पास अपनी परियोजना में अमरूद है, तो आप Streams.zip विधि का उपयोग कर सकते हैं (यह अमरूद 21 में जोड़ा गया था):

एक ऐसी धारा लौटाता है जिसमें प्रत्येक तत्व प्रवाह और स्ट्रीमबी के प्रत्येक तत्व के संबंधित तत्व को कार्य करने का परिणाम है। परिणामी धारा केवल तब तक रहेगी जब तक कि दो इनपुट धाराओं में से छोटी; यदि एक धारा लंबी है, तो इसके अतिरिक्त तत्वों को नजरअंदाज कर दिया जाएगा। परिणामस्वरूप स्ट्रीम कुशलता से विभाजित नहीं है। यह समानांतर प्रदर्शन को नुकसान पहुंचा सकता है।

 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 का उपयोग कर धाराओं सार )।

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 पर
मिगुएल

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
सीन

अगर सूची नहीं है RandomAccess(उदाहरण के लिए लिंक की गई सूचियों के लिए) तो यह बहुत धीमी होगी
avmohan

निश्चित रूप से। लेकिन अधिकांश जावा डेवलपर्स अच्छी तरह से जानते हैं कि लिंक्डलिस्ट में इंडेक्स एक्सेस ऑपरेशन के लिए खराब प्रदर्शन है।
राफेल

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आप फ़िल्टर को पारित कर दिया स्टेटफुल ? यह विधि अनुबंध का उल्लंघन करता है और विशेष रूप से धारा को समानांतर में संसाधित करते समय काम नहीं करेगा।
एंड्रियास

2
@ संकेत: यहां कोई भी समाधान समानांतर प्रसंस्करण का समर्थन नहीं करता है। चूंकि मेरे तरीके एक धारा नहीं लौटाते हैं, वे सुनिश्चित करते हैं कि धाराएं समानांतर में न चलें। इसी तरह, स्वीकृत उत्तर का कोड एक स्ट्रीम देता है जिसे समानांतर में बदल दिया जा सकता है लेकिन वास्तव में समानांतर में कुछ भी नहीं होगा। उस ने कहा, राज्यभोग पूर्वक को हतोत्साहित किया जाता है लेकिन अनुबंध का उल्लंघन नहीं किया जाता है। यदि आप यह सुनिश्चित करते हैं कि राज्य अद्यतन थ्रेड-सुरक्षित है, तो उन्हें समानांतर संदर्भ में भी उपयोग किया जा सकता है। कुछ स्थितियों में वे अपरिहार्य हैं, उदाहरण के लिए एक धारा को अलग-अलग करना राज्य प्रति राज्य विधेय है
होल्गर

2
@Andreas: तुम क्यों इन आपरेशनों ... जावा एपीआई से हटा दिया गया है लगता है कि हो सकता है
होल्गर

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
मिगुएल गंबा

@MiguelGamboa - आपकी टिप्पणी के लिए धन्यवाद। मुझे यकीन नहीं है कि "डिफ़ॉल्ट रूप से सीमित समानता" से आपका क्या मतलब है - क्या आपके पास कुछ डॉक्स का लिंक है?
डोरैडस

6

लेज़ी-सेक लाइब्रेरी ज़िप कार्यक्षमता प्रदान करती है।

https://github.com/nurkiewicz/LazySeq

यह पुस्तकालय काफी हद तक प्रेरित है scala.collection.immutable.Streamऔर इसका उद्देश्य अपरिवर्तनीय, थ्रेड-सुरक्षित और आसानी से आलसी अनुक्रम कार्यान्वयन का उपयोग करना है, संभवतः अनंत।


5

नवीनतम अमरूद पुस्तकालय ( 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

एओएल की साइक्लॉप्स-प्रतिक्रिया , जिसमें मैं योगदान देता हूं, दोनों एक विस्तारित स्ट्रीम कार्यान्वयन के माध्यम से, ज़िपिंग कार्यक्षमता भी प्रदान करता है , जो प्रतिक्रियाशील-धाराओं इंटरफ़ेस रिएक्टिवसेक को भी लागू करता है, और स्ट्रीमयूटिल्स के माध्यम से जो कि स्टैटिक विधियों के माध्यम से मानक जावा धाराओं के माध्यम से समान कार्यक्षमता प्रदान करता है।

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

आउटपुट: {ए = एप्पल, बी = केला, सी = गाजर}

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.