मैंने कार्यात्मक 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;
}
}
1 उत्तर
आपका समाधान न केवल संभावित StackOverflowError
s के लिए प्रवण है, यह संभावित अनंत धाराओं को संभालने से बहुत दूर है, भले ही 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
विधि में पास किया था। उपरोक्त समाधान आलसी हैं लेकिन संग्राहकों के रूप में व्यक्त करने के लिए "अनज़िपिंग" के बाद संचालन की आवश्यकता होती है। यदि आप इसके बजाय Stream
s चाहते हैं और समाधान की गैर-आलसी प्रकृति को स्वीकार करते हैं, जो आपके मूल 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()
और भी बेहतर प्रदर्शन करेगा)।
collect()
के साथ अनज़िप लिखा, लेकिन मैंने reduce()
पर स्विच किया क्योंकि मुझे एक IllegalStateException
(स्ट्रीम पहले से ही खपत) मिल रही थी। हालाँकि, आपके सभी कार्यान्वयन बढ़िया काम करते हैं। आप सही कह रहे हैं कि मैं ArrayList
को एकत्रित नहीं करना चाहता था, लेकिन इसके बजाय, मैं बार-बार concat()
का आह्वान करके अपनी खुद की लिंक की गई सूची बना रहा था :) मेरा बुरा
संबंधित सवाल
जुड़े हुए प्रश्न
नए सवाल
java
जावा एक उच्च स्तरीय प्रोग्रामिंग भाषा है। इस टैग का उपयोग तब करें जब आपको भाषा का उपयोग करने या समझने में समस्या हो। इस टैग का उपयोग शायद ही कभी किया जाता है और इसका उपयोग अक्सर [वसंत], [वसंत-बूट], [जकार्ता-ई], [Android], [javafx], [हडूप], [श्रेणी] और [मावेन] के साथ किया जाता है।