कम विधि के लिए एक कंबाइनर की आवश्यकता क्यों है जो कि जावा 8 में प्रकार को परिवर्तित करता है


142

मुझे पूरी तरह से उस भूमिका को समझने में परेशानी हो रही है combinerजो स्ट्रीम reduceविधि में पूरी होती है।

उदाहरण के लिए, निम्नलिखित कोड संकलित नहीं करता है:

int length = asList("str1", "str2").stream()
            .reduce(0, (accumulatedInt, str) -> accumulatedInt + str.length());

संकलित त्रुटि कहती है: (तर्क बेमेल; int को java.lang.String में परिवर्तित नहीं किया जा सकता)

लेकिन यह कोड संकलित करता है:

int length = asList("str1", "str2").stream()  
    .reduce(0, (accumulatedInt, str ) -> accumulatedInt + str.length(), 
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2);

मैं समझता हूं कि कंबाइन विधि का उपयोग समानांतर धाराओं में किया जाता है - इसलिए मेरे उदाहरण में यह एक साथ दो मध्यवर्ती संचित चींटियों को जोड़ रहा है।

लेकिन मुझे समझ में नहीं आता है कि पहला उदाहरण कॉम्बिनेटर के बिना संकलित क्यों नहीं है या कॉम्बिनेशन स्ट्रिंग के रूपांतरण को इंट में कैसे हल कर रहा है क्योंकि यह सिर्फ दो ints को जोड़ रहा है।

क्या कोई इसे स्पष्ट कर सकता है?


संबंधित प्रश्न: stackoverflow.com/questions/24202473/…
nosid

2
अहा, यह समानांतर धाराओं के लिए है ... मैं टपका हुआ अमूर्त कहता हूं!
एंडी

जवाबों:


77

जिन दो और तीन तर्क संस्करणों का reduceआपने उपयोग करने का प्रयास किया है, वे उसी प्रकार के लिए स्वीकार नहीं करते हैं accumulator

दो तर्क reduceके रूप में परिभाषित किया गया है :

T reduce(T identity,
         BinaryOperator<T> accumulator)

आपके मामले में, टी स्ट्रिंग है, इसलिए BinaryOperator<T>दो स्ट्रिंग तर्कों को स्वीकार करना चाहिए और एक स्ट्रिंग वापस करना चाहिए। लेकिन आप इसे एक इंट और स्ट्रिंग के पास भेजते हैं, जिसके परिणामस्वरूप आपको जो संकलन त्रुटि मिली - argument mismatch; int cannot be converted to java.lang.String। दरअसल, मुझे लगता है कि 0 पास करना क्योंकि पहचान मूल्य भी यहां गलत है, क्योंकि एक स्ट्रिंग की उम्मीद है (टी)।

यह भी ध्यान दें कि कम करने का यह संस्करण टीएस की एक धारा को संसाधित करता है और एक टी लौटाता है, इसलिए आप स्ट्रिंग के एक स्ट्रीम को एक इंट में कम करने के लिए इसका उपयोग नहीं कर सकते।

तीन तर्क reduceके रूप में परिभाषित किया गया है :

<U> U reduce(U identity,
             BiFunction<U,? super T,U> accumulator,
             BinaryOperator<U> combiner)

आपके मामले में यू इंटेगर है और टी स्ट्रिंग है, इसलिए इस पद्धति से स्ट्रिंग की एक धारा कम हो जाएगी।

के लिए BiFunction<U,? super T,U>संचायक आप दो अलग अलग प्रकार (यू और? सुपर टी) है, जो अपने मामले में पूर्णांक और स्ट्रिंग हैं के मापदंडों पारित कर सकते हैं। इसके अलावा, पहचान मूल्य U आपके मामले में एक इंटेगर को स्वीकार करता है, इसलिए इसे पास करना ठीक है।

दूसरा तरीका जो आप चाहते हैं उसे प्राप्त करने के लिए:

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .reduce(0, (accumulatedInt, len) -> accumulatedInt + len);

यहां स्ट्रीम का प्रकार रिटर्न प्रकार से मेल खाता है reduce, इसलिए आप दो पैरामीटर संस्करण का उपयोग कर सकते हैं reduce

बेशक आप का उपयोग करने की जरूरत नहीं है reduce:

int length = asList("str1", "str2").stream().mapToInt (s -> s.length())
            .sum();

8
अपने अंतिम कोड में दूसरे विकल्प के रूप में, आप mapToInt(String::length)ओवर का उपयोग भी कर सकते हैं mapToInt(s -> s.length()), निश्चित नहीं कि कोई दूसरे पर बेहतर होगा, लेकिन मैं पठनीयता के लिए पूर्व को प्राथमिकता देता हूं।
skiwi

20
बहुतों को यह उत्तर मिलेगा क्योंकि उन्हें combinerइसकी आवश्यकता नहीं है, क्यों accumulatorपर्याप्त नहीं है। उस मामले में: कंघी को केवल समानांतर धाराओं के लिए आवश्यक है, थ्रेड्स के "संचित" परिणामों को संयोजित करने के लिए।
ddekany

1
मुझे आपका उत्तर विशेष उपयोगी नहीं लगता - क्योंकि आप इस बात की व्याख्या नहीं करते हैं कि कंबाइनर को क्या करना चाहिए और मैं इसके बिना कैसे काम कर सकता हूं! मेरे मामले में मैं एक टाइप टू यू को कम करना चाहता हूं लेकिन ऐसा कोई तरीका नहीं है जो कभी भी समानांतर में किया जा सकता है। यह बस संभव नहीं है। आप उस प्रणाली को कैसे बता सकते हैं जो मुझे नहीं चाहिए / समानता की आवश्यकता है और इस प्रकार कॉम्बिनर को छोड़ दें?
ज़ॉर्डिड

@Zordid स्ट्रीम्स एपीआई में एक कंबाइन पास किए बिना टाइप टी को यू में कम करने का विकल्प शामिल नहीं है।
एरन

216

एरण के उत्तर ने दो-आरजी और तीन-आरजी संस्करण के बीच के अंतरों का वर्णन किया reduceहै कि पूर्व कम हो Stream<T>जाता है Tजबकि बाद में कम हो Stream<T>जाता है U। हालांकि, यह वास्तव में जब कम करने के Stream<T>लिए अतिरिक्त combiner समारोह की आवश्यकता की व्याख्या नहीं की U

स्ट्रीम एपीआई के डिजाइन सिद्धांतों में से एक यह है कि एपीआई को अनुक्रमिक और समानांतर धाराओं के बीच अंतर नहीं होना चाहिए, या दूसरा रास्ता नहीं होना चाहिए, एक विशेष एपीआई को स्ट्रीम को क्रमिक रूप से या समानांतर में सही ढंग से चलने से नहीं रोकना चाहिए। यदि आपके लैम्ब्डा में सही गुण हैं (साहचर्य, गैर-हस्तक्षेप, आदि) एक धारा क्रमिक रूप से चलती है या समानांतर में समान परिणाम देना चाहिए।

आइए सबसे पहले कमी के दो-arg संस्करण पर विचार करें:

T reduce(I, (T, T) -> T)

अनुक्रमिक कार्यान्वयन सीधा है। Iपरिणाम देने के लिए पहचान मूल्य शून्य धारा तत्व के साथ "संचित" है। यह परिणाम पहली स्ट्रीम तत्व के साथ एक और परिणाम देने के लिए जमा होता है, जो बदले में दूसरी स्ट्रीम तत्व के साथ जमा होता है, और आगे। अंतिम तत्व जमा होने के बाद, अंतिम परिणाम वापस आ जाता है।

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

अब आइए एक काल्पनिक द्वि-आर्गन कमी ऑपरेशन पर विचार करें जो कम हो Stream<T>जाता है U। अन्य भाषाओं में, इसे "फोल्ड" या "फोल्ड-लेफ्ट" ऑपरेशन कहा जाता है, इसलिए मैं इसे यहां कहूंगा। ध्यान दें कि यह जावा में मौजूद नहीं है।

U foldLeft(I, (U, T) -> U)

(ध्यान दें कि पहचान मूल्य IU प्रकार का है)

का अनुक्रमिक संस्करण foldLeftकेवल अनुक्रमिक संस्करण की तरह है reduceसिवाय इसके कि मध्यवर्ती मान टाइप T के बजाय U प्रकार के हैं। लेकिन यह अन्यथा समान है। (एक काल्पनिक foldRightऑपरेशन समान होगा सिवाय इसके कि ऑपरेशन बाएं-से-दाएं के बजाय दाएं-बाएं किया जाएगा।)

अब के समानांतर संस्करण पर विचार करें foldLeft। खंडों में धारा को विभाजित करके शुरू करते हैं। इसके बाद, हम एन थ्रेड्स में से प्रत्येक को अपने सेगमेंट में टी मानों को एन यू के मध्यवर्ती मूल्यों में घटा सकते हैं। अब क्या? U प्रकार के एकल परिणाम के नीचे U के प्रकार के N मान कैसे मिलते हैं?

क्या गायब है एक और फ़ंक्शन है जो टाइप यू के एक ही परिणाम में यू के कई मध्यवर्ती परिणामों को जोड़ता है । यदि हमारे पास एक फ़ंक्शन है जो दो यू मूल्यों को एक में जोड़ता है, तो यह किसी भी संख्या के मूल्यों को एक से कम करने के लिए पर्याप्त है - जैसे ऊपर मूल कमी। इस प्रकार, कमी ऑपरेशन जो एक अलग प्रकार के परिणाम देता है उसे दो कार्यों की आवश्यकता होती है:

U reduce(I, (U, T) -> U, (U, U) -> U)

या, जावा सिंटैक्स का उपयोग कर:

<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)

सारांश में, एक अलग परिणाम प्रकार के समानांतर कमी करने के लिए, हमें दो कार्यों की आवश्यकता है: एक जो कि यू तत्वों को मध्यवर्ती यू मानों में जमा करता है, और एक दूसरा जो एक यू परिणाम में मध्यवर्ती यू मूल्यों को जोड़ता है। यदि हम प्रकार स्विच नहीं कर रहे हैं, तो यह पता चलता है कि संचायक फ़ंक्शन कॉम्बिनर फ़ंक्शन के समान है। इसलिए एक ही प्रकार की कमी में केवल संचायक कार्य होता है और एक अलग प्रकार में कमी के लिए अलग संचयकर्ता और कॉम्बिनर फ़ंक्शन की आवश्यकता होती है।

अंत में, जावा प्रदान नहीं करता है foldLeftऔर foldRightसंचालन नहीं करता है क्योंकि वे परिचालन के एक विशेष आदेश का पालन करते हैं जो स्वाभाविक रूप से अनुक्रमिक है। यह डिज़ाइन सिद्धांत के साथ टकराव प्रदान करता है जो एपीआई प्रदान करने के ऊपर है जो समान रूप से अनुक्रमिक और समानांतर संचालन का समर्थन करता है।


7
इसलिए अगर आप की जरूरत है तो आप क्या कर सकते हैं foldLeftक्योंकि गणना पिछले परिणाम पर निर्भर करती है और इसे समानांतर नहीं किया जा सकता है?
amoebe

5
@amoebe आप अपने स्वयं के foldLeft का उपयोग करके कार्यान्वित कर सकते हैं forEachOrdered। मध्यवर्ती राज्य को एक कैप्चर किए गए चर में रखा जाना चाहिए, हालांकि।
स्टुअर्ट मार्क्स

@StuartMarks धन्यवाद, मैंने jOOλ का उपयोग करके समाप्त किया। उनका साफ-सुथरा कार्यान्वयन हैfoldLeft
amoebe

1
इस उत्तर को प्यार करो! अगर मैं गलत हूं तो मुझे सुधारें: यह बताता है कि ओपी का रनिंग उदाहरण (दूसरा वाला) कभी भी कंघी को नहीं चलाएगा, जब रन होगा, तो स्ट्रीम अनुक्रमिक होगा।
लुइगी कोरटेस

2
यह लगभग सब कुछ समझाता है ... सिवाय: क्यों यह क्रमिक रूप से आधारित कमी को बाहर करना चाहिए। मेरे मामले में यह मेरे समानांतर समानांतर में करना है क्योंकि मेरी कमी प्रत्येक पूर्ववर्ती परिणाम के मध्यवर्ती परिणाम पर प्रत्येक फ़ंक्शन को कॉल करके यू में कार्यों की एक सूची को कम करती है। यह बिल्कुल समानांतर में नहीं किया जा सकता है और एक कॉम्बिनेटर का वर्णन करने का कोई तरीका नहीं है। इसे पूरा करने के लिए मैं किस विधि का उपयोग कर सकता हूं?
ज़ॉर्डिड

116

चूंकि मैं अवधारणाओं को स्पष्ट करने के लिए डूडल और तीर पसंद करता हूं ... चलो शुरू करें!

स्ट्रिंग से स्ट्रिंग तक (अनुक्रमिक धारा)

मान लीजिए कि 4 स्ट्रिंग्स हैं: आपका लक्ष्य ऐसे स्ट्रिंग्स को एक में समेटना है। आप मूल रूप से एक प्रकार से शुरू करते हैं और उसी प्रकार से समाप्त करते हैं।

आप इसे हासिल कर सकते हैं

String res = Arrays.asList("one", "two","three","four")
        .stream()
        .reduce("",
                (accumulatedStr, str) -> accumulatedStr + str);  //accumulator

और यह आपको कल्पना करने में मदद करता है कि क्या हो रहा है:

यहां छवि विवरण दर्ज करें

संचायक फ़ंक्शन धर्मान्तरित, चरण दर चरण, आपके (लाल) स्ट्रीम में तत्वों को अंतिम कम (हरा) मान देता है। संचायक फ़ंक्शन बस किसी Stringऑब्जेक्ट को दूसरे में बदल देता है String

स्ट्रिंग से इंट (समानांतर धारा) तक

मान लें कि समान 4 तार हैं: आपका नया लक्ष्य उनकी लंबाई को योग करना है, और आप अपनी धारा को समानांतर बनाना चाहते हैं।

आपको कुछ इस तरह की आवश्यकता है:

int length = Arrays.asList("one", "two","three","four")
        .parallelStream()
        .reduce(0,
                (accumulatedInt, str) -> accumulatedInt + str.length(),                 //accumulator
                (accumulatedInt, accumulatedInt2) -> accumulatedInt + accumulatedInt2); //combiner

और यह क्या हो रहा है की एक योजना है

यहां छवि विवरण दर्ज करें

यहां संचायक फ़ंक्शन (ए BiFunction) आपको अपने Stringडेटा को डेटा में बदलने की अनुमति देता है int। धारा के समानांतर होने के कारण, इसे दो (लाल) भागों में विभाजित किया गया है, जिनमें से प्रत्येक को प्रत्येक अभिभावक से स्वतंत्र रूप से विस्तृत किया गया है और कई आंशिक (नारंगी) परिणामों के रूप में उत्पन्न होता है। एक intअंतिम (हरा) में आंशिक परिणामों के विलय के लिए एक नियम प्रदान करने के लिए एक कंबाइन को परिभाषित करना आवश्यक है int

स्ट्रिंग से इंट तक (अनुक्रमिक धारा)

क्या होगा यदि आप अपनी धारा को समानांतर नहीं बनाना चाहते हैं? वैसे, एक कंघी को वैसे भी प्रदान करने की आवश्यकता होती है, लेकिन इसे कभी भी लागू नहीं किया जाएगा, यह देखते हुए कि कोई आंशिक परिणाम नहीं होगा।


7
इसके लिए धन्यवाद। मुझे पढ़ने की भी जरूरत नहीं थी। मुझे लगता है वे सिर्फ एक तह गुना समारोह जोड़ा होगा चाहते हैं।
लोदीविज्क बोगार्ड्स 14

1
@LodewijkBogaards को खुशी हुई कि इससे मदद मिली! यहाँ जावाडॉक वास्तव में बहुत ही गूढ़ है
लुइगी कॉर्टेज

@LuigiCortese समानांतर धारा में यह हमेशा तत्वों को जोड़े में विभाजित करता है?
TheLogicGuy

1
मैं आपके स्पष्ट और उपयोगी उत्तर की सराहना करता हूं। मैं आपके द्वारा कही गई बातों को थोड़ा दोहराना चाहता हूं: "वैसे, एक कंघी को वैसे भी प्रदान करने की आवश्यकता होती है, लेकिन इसे कभी भी लागू नहीं किया जाएगा।" यह जावा कार्यात्मक प्रोग्रामिंग के बहादुर नई दुनिया का हिस्सा है, जिसे मुझे अनगिनत बार आश्वासन दिया गया है, "आपके कोड को अधिक संक्षिप्त और पढ़ने में आसान बनाता है।" आइए आशा करते हैं कि (उंगली उद्धरण) के उदाहरण स्पष्ट रूप से स्पष्ट होते हैं जैसे कि यह कुछ और दूर रहता है।
1948

आठ स्ट्रिंग्स के साथ कम करने के लिए बेहतर होगा MUCH ...
एकातेरिना इवानोवा iceja.net

0

कोई कम संस्करण नहीं है जो दो अलग-अलग प्रकार के कॉम्बिनेटर के बिना लेता है क्योंकि इसे समानांतर में निष्पादित नहीं किया जा सकता है (यह सुनिश्चित नहीं है कि यह एक आवश्यकता क्यों है)। तथ्य यह है कि संचयकर्ता सहयोगी होना चाहिए इस इंटरफ़ेस को बहुत बेकार बना देता है:

list.stream().reduce(identity,
                     accumulator,
                     combiner);

के रूप में एक ही परिणाम का उत्पादन:

list.stream().map(i -> accumulator(identity, i))
             .reduce(identity,
                     combiner);

ऐसी mapचाल विशेष पर निर्भर करती है accumulatorऔर combinerचीजों को बहुत धीमा कर सकती है।
टैगिर वलेव

या, इसे काफी तेज़ करें क्योंकि अब आप accumulatorपहले पैरामीटर को छोड़ कर सरल बना सकते हैं ।
क्विज123

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