कोटलिन की Iterable और Sequence बिल्कुल एक जैसी दिखती हैं। दो प्रकार की आवश्यकता क्यों है?


86

ये दोनों इंटरफेस केवल एक विधि को परिभाषित करते हैं

public operator fun iterator(): Iterator<T>

दस्तावेज़ीकरण का कहना Sequenceहै कि आलसी होना है। लेकिन Iterableआलसी भी नहीं है (जब तक कि एक के द्वारा समर्थित नहीं है Collection)?

जवाबों:


136

महत्वपूर्ण अंतर शब्दार्थ में है और stdlib विस्तार कार्यों के कार्यान्वयन के लिए Iterable<T>और Sequence<T>

  • के लिए Sequence<T>, विस्तार कार्यों lazily, इसी तरह जावा स्ट्रीम करने के लिए प्रदर्शन जहां संभव मध्यवर्ती आपरेशनों। उदाहरण के लिए, Sequence<T>.map { ... }किसी अन्य को लौटाता है और Sequence<R>वास्तव में तब तक आइटम को संसाधित नहीं करता है जब तक कि टर्मिनल ऑपरेशन जैसे toListया foldकहा जाता है।

    इस कोड पर विचार करें:

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    यह प्रिंट करता है:

    before sum 1 2
    

    Sequence<T>जब आप जितना संभव हो टर्मिनल संचालन में किए गए काम को कम करना चाहते हैं, तो जावा स्ट्रीम्स के लिए आलसी उपयोग और कुशल पाइपलाइनिंग का इरादा है । हालांकि, आलस्य कुछ ओवरहेड का परिचय देता है, जो छोटे संग्रह के सामान्य सरल परिवर्तनों के लिए अवांछनीय है और उन्हें कम प्रदर्शन करता है।

    सामान्य तौर पर, यह निर्धारित करने का कोई अच्छा तरीका नहीं है कि इसकी आवश्यकता कब है, इसलिए कोटलिन में स्टडीलिब आलस को स्पष्ट किया जाता है और Sequence<T>इसे Iterableडिफ़ॉल्ट रूप से सभी एस पर उपयोग करने से बचने के लिए इंटरफ़ेस में निकाला जाता है।

  • के लिए Iterable<T>, इसके विपरीत, के साथ विस्तार कार्यों मध्यवर्ती आपरेशन अर्थ विज्ञान बेसब्री से काम करते हैं, आइटम अभी की प्रक्रिया और एक अन्य लौटने Iterable। उदाहरण के लिए, इसमें मैपिंग परिणाम के साथ Iterable<T>.map { ... }देता List<R>है।

    Iterable के लिए समान कोड:

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    यह प्रिंट करता है:

    1 2 before sum
    

    जैसा कि ऊपर कहा गया है, Iterable<T>डिफ़ॉल्ट रूप से गैर-आलसी है, और यह समाधान खुद को अच्छी तरह से दिखाता है: ज्यादातर मामलों में यह संदर्भ की अच्छी स्थानीयता है इस प्रकार सीपीयू कैश, भविष्यवाणी, प्रीफेटिंग आदि का लाभ उठाता है ताकि एक संग्रह की एकाधिक प्रतिलिपि अभी भी अच्छा काम करे। पर्याप्त और छोटे संग्रह के साथ सरल मामलों में बेहतर प्रदर्शन करता है।

    यदि आपको मूल्यांकन पाइपलाइन पर अधिक नियंत्रण की आवश्यकता है, तो Iterable<T>.asSequence()फ़ंक्शन के साथ एक आलसी अनुक्रम का एक स्पष्ट रूपांतरण है ।


3
संभवतः Java(ज्यादातर Guava) प्रशंसकों के लिए एक बड़ा आश्चर्य
वेंकट राजू

@VenkataRaju कार्यात्मक लोगों के लिए वे डिफ़ॉल्ट रूप से आलसी के विकल्प पर आश्चर्यचकित हो सकते हैं।
जैसन मिनार्ड

9
डिफ़ॉल्ट रूप से आलसी आमतौर पर छोटे और अधिक उपयोग किए जाने वाले संग्रह के लिए कम प्रदर्शन करने वाला होता है। सीपीयू कैश आदि का फायदा उठाते हुए एक कॉपी एक आलसी eval से तेज हो सकती है। इसलिए सामान्य उपयोग के मामलों के लिए आलसी न होना बेहतर है। और दुर्भाग्य से जैसे कार्यों के लिए सामान्य अनुबंध map, filterऔर अन्य लोग स्रोत संग्रह प्रकार के अलावा अन्य को तय करने के लिए पर्याप्त जानकारी नहीं रखते हैं, और चूंकि अधिकांश संग्रह Iterable भी हैं, इसलिए यह "आलसी" होने के लिए एक अच्छा मार्कर नहीं है क्योंकि यह है आमतौर पर हर कोई। आलसी को सुरक्षित होने के लिए स्पष्ट होना चाहिए।
जैसन मिनार्ड

1
@naki एक हालिया अपाचे स्पार्क घोषणा से एक उदाहरण, वे इस बारे में स्पष्ट रूप से चिंता कर रहे हैं, databricks.com/blog/2015/04/28/… पर "कैश-जागरूक कम्प्यूटेशन" अनुभाग देखें ... लेकिन वे अरबों के बारे में चिंतित हैं चीजों को पुनरावृत्त करना ताकि उन्हें पूर्ण चरम पर जाने की आवश्यकता हो।
जेसन मिनार्ड

3
इसके अतिरिक्त, आलसी मूल्यांकन के साथ एक आम नुकसान संदर्भ पर कब्जा कर रहा है और सभी कैप्चर किए गए स्थानीय लोगों के साथ एक क्षेत्र में आलसी अभिकलन का भंडारण करता है और जो कुछ भी वे पकड़ते हैं। इसलिए, स्मृति रिसाव को डीबग करना कठिन है।
इल्या रायजेनकोव

49

हॉटकी का उत्तर पूर्ण करना:

यह देखना महत्वपूर्ण है कि आपके तत्वों में अनुक्रम और Iterable कैसे पुनरावृत्त होते हैं:

अनुक्रम उदाहरण:

list.asSequence().filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

लॉग रिजल्ट:

फ़िल्टर - मानचित्र - प्रत्येक; फ़िल्टर - मानचित्र - प्रत्येक

उदाहरण योग्य उदाहरण:

list.filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

फ़िल्टर - फ़िल्टर - मानचित्र - मानचित्र - प्रत्येक - प्रत्येक


5
यह दोनों के बीच अंतर का एक उत्कृष्ट उदाहरण है।
एलेक्सी सोशिन

यह एक बेहतरीन उदाहरण है।
frye3k

2

Iterableको java.lang.Iterableइंटरफ़ेस पर मैप किया जाता है JVM, और सूची या सेट जैसे आमतौर पर उपयोग किए जाने वाले संग्रह द्वारा कार्यान्वित किया जाता है। इन पर संग्रह विस्तार कार्यों का उत्सुकता से मूल्यांकन किया जाता है, जिसका अर्थ है कि वे सभी तुरंत अपने इनपुट में सभी तत्वों को संसाधित करते हैं और परिणाम सहित एक नया संग्रह लौटाते हैं।

यहां सूची में पहले पांच लोगों के नाम पाने के लिए संग्रह कार्यों का उपयोग करने का एक सरल उदाहरण है, जिनकी आयु कम से कम 21 है:

val people: List<Person> = getPeople()
val allowedEntrance = people
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)

लक्ष्य प्लेटफ़ॉर्म: JVMRunning पर kotlin v। 1.3.61 सबसे पहले, सूची में हर एक व्यक्ति के लिए उम्र की जाँच की जाती है, जिसके परिणाम एकदम नए सूची में डाले जाते हैं। फिर, उनके नाम की मैपिंग प्रत्येक व्यक्ति के लिए की जाती है, जो फ़िल्टर ऑपरेटर के बाद बने रहे, एक और नई सूची (यह अब है List<String>) में समाप्त हो जाएगी । अंत में, पिछली सूची के पहले पांच तत्वों को शामिल करने के लिए बनाई गई एक आखिरी नई सूची है।

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

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

val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)
    .toList()

टारगेट प्लेटफ़ॉर्म: JVMRunning on kotlin v। 1.3.61 इस मामले में, Sequence में व्यक्ति के उदाहरण उनकी उम्र के लिए प्रत्येक चेक किए जाते हैं, यदि वे पास होते हैं, तो उनका नाम निकाला जाता है, और फिर परिणाम सूची में जोड़ा जाता है। यह मूल सूची में प्रत्येक व्यक्ति के लिए दोहराया जाता है जब तक कि पांच लोग नहीं मिलते। इस बिंदु पर, टॉलिस्ट फ़ंक्शन एक सूची देता है, और बाकी लोगों को Sequenceसंसाधित नहीं किया जाता है।

इसमें कुछ अतिरिक्त भी है जो अनुक्रम में सक्षम है: इसमें कई प्रकार की वस्तुएं हो सकती हैं। इस परिप्रेक्ष्य में, यह समझ में आता है कि ऑपरेटर जिस तरह से काम करते हैं - अनंत अनुक्रम पर एक ऑपरेटर कभी नहीं लौट सकता है अगर यह अपने काम को उत्सुकता से करता है।

एक उदाहरण के रूप में, यहां एक अनुक्रम है जो अपने टर्मिनल ऑपरेटर द्वारा आवश्यक के रूप में 2 की कई शक्तियों को उत्पन्न करेगा (इस तथ्य की अनदेखी करते हुए कि यह जल्दी से प्रवाह होगा):

generateSequence(1) { n -> n * 2 }
    .take(20)
    .forEach(::println)

आप यहां अधिक पा सकते हैं ।

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