मैंने कार्यात्मक unzip() ऑपरेशन निम्नानुसार कार्यान्वित किया है:

public static <T, U, V> Tuple2<Stream<U>, Stream<V>> unzip(
        Stream<T> stream, 
        Function<T, Tuple2<U, V>> unzipper) {

    return stream.map(unzipper)
        .reduce(new Tuple2<>(Stream.<U>empty(), Stream.<V>empty()),
            (unzipped, tuple) -> new Tuple2<>(
                Stream.concat(unzipped.$1(), Stream.of(tuple.$1())),
                Stream.concat(unzipped.$2(), Stream.of(tuple.$2()))),
            (unzipped1, unzipped2) -> new Tuple2<>(
                Stream.concat(unzipped1.$1(), unzipped2.$1()),
                Stream.concat(unzipped1.$2(), unzipped2.$2())));
}

यह ठीक काम करता है, दिए गए इनपुट स्ट्रीम में बहुत सारे तत्व नहीं होते हैं। ऐसा इसलिए है क्योंकि गहराई से जुड़ी हुई स्ट्रीम के किसी तत्व तक पहुंचने से StackOverflowException हो सकता है। Stream.concat() के दस्तावेज़:

कार्यान्वयन नोट:

बार-बार संघटन से धाराओं का निर्माण करते समय सावधानी बरतें। गहराई से जुड़ी हुई स्ट्रीम के किसी तत्व तक पहुँचने के परिणामस्वरूप गहरी कॉल चेन, या StackOverflowException भी हो सकती है।

कुछ तत्वों के लिए, मेरा unzip कार्यान्वयन काम करता है। एक वर्ग Person दिया गया है:

class Person {

    public final String name;

    public final int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

अगर मेरे पास लोगों की एक धारा है:

Stream<Person> people = Stream.of(
    new Person("Joe", 52), 
    new Person("Alan", 34), 
    new Person("Peter", 42));

मैं अपने unzip() कार्यान्वयन का इस प्रकार उपयोग कर सकता हूं:

Tuple2<Stream<String>, Stream<Integer>> result = StreamUtils.unzip(people, 
        person -> new Tuple2<>(person.name, person.age));

List<String> names = result.$1()
    .collect(Collectors.toList()); // ["Joe", "Alan", "Peter"]
List<Integer> ages = result.$2()
    .collect(Collectors.toList()); // [52, 34, 42]

क्या सही है।

तो मेरा सवाल है: क्या unzip() के लिए कई तत्वों (संभावित रूप से अनंत) के साथ काम करने का कोई तरीका है?

नोट: पूर्णता के लिए, यह मेरी अपरिवर्तनीय Tuple2 कक्षा है:

public final class Tuple2<A, B> {

    private final A $1;

    private final B $2;

    public Tuple2(A $1, B $2) {
        this.$1 = $1;
        this.$2 = $2;
    }

    public A $1() {
        return $1;
    }

    public B $2() {
        return $2;
    }
}
4
fps 10 मार्च 2016, 18:07
2
बहुत संबंधित: stackoverflow.com/questions/23860533/… इसे प्रभावी ढंग से करने के लिए, हमें दिए गए स्ट्रीम को 2 स्ट्रीम और उनमें से प्रत्येक पर स्वतंत्र रूप से पुनरावृति। केवल मैं देखता हूं कि कैसे (और मल्टीथ्रेडिंग का समर्थन करना) सभी तत्वों को पहले से ही एक सूची में एकत्र करना होगा ...
 – 
Tunaki
10 मार्च 2016, 19:00
उस लिंक के लिए धन्यवाद, बिंदु इसे एकत्रित किए बिना करना है, क्योंकि इनपुट स्ट्रीम अनंत हो सकती है, और इसे अनजिप करना अभी भी काम करना चाहिए।
 – 
fps
10 मार्च 2016, 19:10
2
 – 
Tunaki
10 मार्च 2016, 19:17
नहीं, मुझे उम्मीद है कि यह असंभव होगा। जावा 8 स्ट्रीम अन्य भाषाओं में स्ट्रीम की तरह नहीं हैं जहां यह संभव हो सकता है।
 – 
Louis Wasserman
10 मार्च 2016, 19:32
हां, मेरा मानना ​​है कि यह संभव नहीं है, कम से कम जिस तरह से मैं कोशिश कर रहा हूं। अगर मैं इनपुट स्ट्रीम तत्वों को रखने के लिए संग्रह का उपयोग करता हूं और फिर वहां से दो स्ट्रीम बनाता हूं, तो मैं स्मृति से बाहर हो सकता हूं। यदि मैं इनपुट स्ट्रीम (जैसा कि मेरे प्रश्न में) को कम करते हुए संयोजन के माध्यम से नई धाराएँ बनाता हूँ, तो मुझे एक स्टैक ओवरफ़्लो अपवाद मिलेगा। मुझे कोई रास्ता नजर नहीं आता...
 – 
fps
10 मार्च 2016, 20:29

1 उत्तर

सबसे बढ़िया उत्तर

आपका समाधान न केवल संभावित StackOverflowErrors के लिए प्रवण है, यह संभावित अनंत धाराओं को संभालने से बहुत दूर है, भले ही StackOverflowError का जोखिम मौजूद न हो। मुद्दा यह है कि, आप एक धारा का निर्माण कर रहे हैं, लेकिन यह स्रोत स्ट्रीम के प्रत्येक तत्व के लिए एक समेकित एकल तत्व धाराओं की एक धारा है। दूसरे शब्दों में, आपके पास unzip विधि की वापसी पर पूरी तरह से भौतिक डेटा संरचना है जो ArrayList या एक साधारण toArray() ऑपरेशन में एकत्रित होने के परिणाम से भी अधिक मेमोरी का उपभोग करेगी।

हालाँकि, जब आप collect बाद में प्रदर्शन करना चाहते हैं, तो संभावित अनंत धाराओं का समर्थन करने का विचार वैसे भी विवादास्पद है, क्योंकि संग्रह का तात्पर्य शॉर्ट-सर्किटिंग के बिना सभी तत्वों के प्रसंस्करण से है।

एक बार जब आप अनंत धाराओं का समर्थन करने के विचार को छोड़ देते हैं और संग्रह संचालन पर ध्यान केंद्रित करते हैं, तो एक आसान समाधान होता है। इस समाधान से कोड लेना, Pair को Tuple2 से बदलना और संचायक तर्क को " से बदलना सशर्त" से "दोनों", हम प्राप्त करते हैं:

public static <T, A1, A2, R1, R2> Collector<T, ?, Tuple2<R1,R2>> both(
    Collector<T, A1, R1> first, Collector<T, A2, R2> second) {

    Supplier<A1> s1=first.supplier();
    Supplier<A2> s2=second.supplier();
    BiConsumer<A1, T> a1=first.accumulator();
    BiConsumer<A2, T> a2=second.accumulator();
    BinaryOperator<A1> c1=first.combiner();
    BinaryOperator<A2> c2=second.combiner();
    Function<A1,R1> f1=first.finisher();
    Function<A2,R2> f2=second.finisher();
    return Collector.of(
        ()->new Tuple2<>(s1.get(), s2.get()),
        (p,t)->{ a1.accept(p.$1(), t); a2.accept(p.$2(), t); },
        (p1,p2)->new Tuple2<>(c1.apply(p1.$1(), p2.$1()), c2.apply(p1.$2(), p2.$2())),
        p -> new Tuple2<>(f1.apply(p.$1()), f2.apply(p.$2())));
}

इस तरह इस्तेमाल किया जा सकता है

Tuple2<List<String>, List<Integer>> namesAndAges=
    Stream.of(new Person("Joe", 52), new Person("Alan", 34), new Person("Peter", 42))
        .collect(both(
            Collectors.mapping(p->p.name, Collectors.toList()),
            Collectors.mapping(p->p.age,  Collectors.toList())));
List<String> names = namesAndAges.$1(); // ["Joe", "Alan", "Peter"]
List<Integer> ages = namesAndAges.$2(); // [52, 34, 42]

लिंक किए गए उत्तर का कथन यहां भी है। आप लगभग वह सब कुछ कर सकते हैं जो आप एक संग्राहक के भीतर एक धारा संचालन के रूप में व्यक्त कर सकते हैं।

यदि आप किसी फ़ंक्शन के साथ अपने मूल कोड के करीब होना चाहते हैं, तो स्ट्रीम एलिमेंट से Tuple2 में मैपिंग, आप उपरोक्त समाधान को लपेट सकते हैं जैसे

public static <T, T1, T2, A1, A2, R1, R2> Collector<T, ?, Tuple2<R1,R2>> both(
    Function<? super T, ? extends Tuple2<? extends T1, ? extends T2>> f,
    Collector<T1, A1, R1> first, Collector<T2, A2, R2> second) {

    return Collectors.mapping(f, both(
            Collectors.mapping(Tuple2::$1, first),
            Collectors.mapping(Tuple2::$2, second)));
}

और इसे पसंद करें

Tuple2<List<String>, List<Integer>> namesAndAges=
    Stream.of(new Person("Joe", 52), new Person("Alan", 34), new Person("Peter", 42))
        .collect(both(
            p -> new Tuple2<>(p.name, p.age), Collectors.toList(), Collectors.toList()));

आप फ़ंक्शन p -> new Tuple2<>(p.name, p.age) को पहचान सकते हैं, ठीक उसी तरह जैसे आपने अपनी unzip विधि में पास किया था। उपरोक्त समाधान आलसी हैं लेकिन संग्राहकों के रूप में व्यक्त करने के लिए "अनज़िपिंग" के बाद संचालन की आवश्यकता होती है। यदि आप इसके बजाय Streams चाहते हैं और समाधान की गैर-आलसी प्रकृति को स्वीकार करते हैं, जो आपके मूल unzip ऑपरेशन की तरह है, लेकिन इसे concat से अधिक कुशल बनाना चाहते हैं, तो आप इसका उपयोग कर सकते हैं:

public static <T, U, V> Tuple2<Stream<U>, Stream<V>> unzip(
    Stream<T> stream,  Function<T, Tuple2<U, V>> unzipper) {

    return stream.map(unzipper)
        .collect(Collector.of(()->new Tuple2<>(Stream.<U>builder(), Stream.<V>builder()),
            (unzipped, tuple) -> {
                unzipped.$1().accept(tuple.$1()); unzipped.$2().accept(tuple.$2());
            },
            (unzipped1, unzipped2) -> {
                unzipped2.$1().build().forEachOrdered(unzipped1.$1());
                unzipped2.$2().build().forEachOrdered(unzipped1.$2());
                return unzipped1;
            },
            tuple -> new Tuple2<>(tuple.$1().build(), tuple.$2().build())
        ));
}

यह आपके concat आधारित समाधान के प्रतिस्थापन में कमी के रूप में कार्य कर सकता है। यह स्ट्रीम तत्वों को भी पूरी तरह से संग्रहीत करेगा, लेकिन यह एक Stream.Builder का उपयोग करेगा जो कि एक बार (Stream ऑपरेशन में) वृद्धिशील रूप से भरे और खपत होने के उपयोग के मामले के लिए अनुकूलित है। यह एक ArrayList (कम से कम संदर्भ कार्यान्वयन के साथ) में इकट्ठा करने से भी अधिक कुशल काम करता है क्योंकि यह एक "स्पाइन्ड बफर" का उपयोग करता है जिसे क्षमता बढ़ाते समय कॉपी करने की आवश्यकता नहीं होती है। संभावित अज्ञात आकार वाली स्ट्रीम के लिए, यह सबसे कुशल समाधान है (किसी ज्ञात आकार वाली स्ट्रीम के लिए, toArray() और भी बेहतर प्रदर्शन करेगा)।

4
Community 23 मई 2017, 14:51
धन्यवाद @ होल्गर, इतने पूर्ण उत्तर के लिए। प्रारंभ में, मैंने collect() के साथ अनज़िप लिखा, लेकिन मैंने reduce() पर स्विच किया क्योंकि मुझे एक IllegalStateException (स्ट्रीम पहले से ही खपत) मिल रही थी। हालाँकि, आपके सभी कार्यान्वयन बढ़िया काम करते हैं। आप सही कह रहे हैं कि मैं ArrayList को एकत्रित नहीं करना चाहता था, लेकिन इसके बजाय, मैं बार-बार concat() का आह्वान करके अपनी खुद की लिंक की गई सूची बना रहा था :) मेरा बुरा
 – 
fps
11 मार्च 2016, 00:50