इस समस्या को समझने की कुंजी यह महसूस करना है कि संग्रह पुस्तकालय में संग्रह के साथ निर्माण और काम करने के दो अलग-अलग तरीके हैं । एक अपने सभी अच्छे तरीकों के साथ सार्वजनिक संग्रह इंटरफ़ेस है। अन्य, जो संग्रह पुस्तकालय बनाने में बड़े पैमाने पर उपयोग किया जाता है , लेकिन जो इसके बाहर कभी उपयोग नहीं किए जाते हैं, वे बिल्डरों हैं।
समृद्ध करने में हमारी समस्या ठीक वैसी ही है जैसी संग्रह लाइब्रेरी का सामना करना पड़ता है जब उसी प्रकार के संग्रह को वापस करने की कोशिश की जाती है। यही है, हम संग्रह बनाना चाहते हैं, लेकिन जब हम उदारतापूर्वक काम करते हैं, तो हमारे पास "उसी प्रकार को संदर्भित करने का एक तरीका नहीं है जो संग्रह पहले से ही है"। इसलिए हमें बिल्डरों की जरूरत है ।
अब सवाल यह है कि हम अपने बिल्डरों को कहां से लाएं? स्पष्ट स्थान संग्रह से ही है। यह काम नहीं करता है । हमने पहले से ही तय कर लिया था, एक सामान्य संग्रह की ओर बढ़ते हुए, कि हम संग्रह के प्रकार को भूलते जा रहे हैं। इसलिए भले ही संग्रह एक बिल्डर को वापस कर सकता है जो उस प्रकार के अधिक संग्रह उत्पन्न करेगा जो हम चाहते हैं, यह नहीं जानता होगा कि प्रकार क्या था।
इसके बजाय, हम अपने बिल्डरों को उन CanBuildFrom
इम्प्लिट्स से प्राप्त करते हैं जो चारों ओर तैर रहे हैं। ये विशेष रूप से इनपुट और आउटपुट प्रकारों के मिलान और आपको उचित रूप से टाइप किए गए बिल्डर को देने के उद्देश्य से मौजूद हैं।
तो, हमारे पास बनाने के लिए दो वैचारिक छलांग हैं:
- हम मानक संग्रह संचालन का उपयोग नहीं कर रहे हैं, हम बिल्डरों का उपयोग कर रहे हैं।
- हम इन बिल्डरों को निहितार्थ से प्राप्त करते हैं
CanBuildFrom
, न कि सीधे हमारे संग्रह से।
आइए एक उदाहरण देखें।
class GroupingCollection[A, C[A] <: Iterable[A]](ca: C[A]) {
import collection.generic.CanBuildFrom
def groupedWhile(p: (A,A) => Boolean)(
implicit cbfcc: CanBuildFrom[C[A],C[A],C[C[A]]], cbfc: CanBuildFrom[C[A],A,C[A]]
): C[C[A]] = {
val it = ca.iterator
val cca = cbfcc()
if (!it.hasNext) cca.result
else {
val as = cbfc()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda,a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
implicit def iterable_has_grouping[A, C[A] <: Iterable[A]](ca: C[A]) = {
new GroupingCollection[A,C](ca)
}
चलिए इसे अलग लेते हैं। सबसे पहले, संग्रह-के-संग्रह के निर्माण के लिए, हम जानते हैं कि हमें दो प्रकार के संग्रह बनाने की आवश्यकता होगी: C[A]
प्रत्येक समूह के लिए, और C[C[A]]
वह सभी समूहों को एक साथ इकट्ठा करता है। इस प्रकार, हमें दो बिल्डरों की आवश्यकता है, एक जो A
एस लेता है और C[A]
एस बनाता है , और एक जो C[A]
एस लेता है और C[C[A]]
एस बनाता है । के प्रकार को CanBuildFrom
देखते हुए, हम देखते हैं
CanBuildFrom[-From, -Elem, +To]
जिसका अर्थ है कि CanBuildFrom हमारे संग्रह के प्रकार को जानना चाहता है - हमारे मामले में, यह C[A]
, और फिर जनरेट किए गए संग्रह के तत्व और उस संग्रह का प्रकार। इसलिए हम उन्हें निहित मापदंडों के रूप में भरते हैंcbfcc
और cbfc
।
यह महसूस करने के बाद, यह सबसे काम है। हम CanBuildFrom
बिल्डरों को देने के लिए अपने एस का उपयोग कर सकते हैं (आपको केवल उन्हें लागू करने की आवश्यकता है)। और एक बिल्डर के साथ एक संग्रह का निर्माण कर सकते हैं +=
, इसे उस संग्रह में परिवर्तित कर सकते हैं जिसे अंततः माना जाता है result
, और खुद को खाली करना और फिर से शुरू करने के लिए तैयार होना चाहिए clear
। बिल्डरों को खाली करना शुरू हो जाता है, जो हमारी पहली संकलन त्रुटि को हल करता है, और चूंकि हम बिल्डरों का उपयोग पुनरावृत्ति के बजाय कर रहे हैं, दूसरी त्रुटि भी दूर हो जाती है।
एक अंतिम थोड़ा विस्तार - एल्गोरिथ्म के अलावा जो वास्तव में काम करता है - निहित रूपांतरण में है। ध्यान दें कि हम उपयोग new GroupingCollection[A,C]
नहीं करते हैं [A,C[A]]
। इसका कारण यह है कि वर्ग की घोषणा C
एक पैरामीटर के साथ थी , जिसे वह इसे स्वयं A
पास करता है। तो हम बस इसे टाइप करते हैंC
, और इसे बनाने की अनुमति देते हैं C[A]
। यदि आप दूसरा तरीका आजमाते हैं, तो माइनर डिटेल, लेकिन आपको कंपाइल-टाइम त्रुटियाँ मिलेंगी।
यहां, मैंने विधि को "समान तत्वों" संग्रह की तुलना में थोड़ा अधिक सामान्य बना दिया है - बल्कि, जब भी अनुक्रमिक तत्वों का परीक्षण विफल हो जाता है, तो विधि मूल संग्रह को काट देती है।
आइये देखते हैं हमारी विधि:
scala> List(1,2,2,2,3,4,4,4,5,5,1,1,1,2).groupedWhile(_ == _)
res0: List[List[Int]] = List(List(1), List(2, 2, 2), List(3), List(4, 4, 4),
List(5, 5), List(1, 1, 1), List(2))
scala> Vector(1,2,3,4,1,2,3,1,2,1).groupedWhile(_ < _)
res1: scala.collection.immutable.Vector[scala.collection.immutable.Vector[Int]] =
Vector(Vector(1, 2, 3, 4), Vector(1, 2, 3), Vector(1, 2), Vector(1))
यह काम करता हैं!
एकमात्र समस्या यह है कि हम आम तौर पर सरणियों के लिए ये तरीके उपलब्ध नहीं हैं, क्योंकि इसके लिए एक पंक्ति में दो निहित रूपांतरणों की आवश्यकता होगी। इसके आसपास आने के कई तरीके हैं, जिसमें एरेज़ के लिए अलग-अलग निहित रूपांतरण लिखना, कास्टिंग करना WrappedArray
, और इसी तरह शामिल हैं।
संपादित करें: सरणियों और तारों से निपटने के लिए मेरा पसंदीदा दृष्टिकोण और इस तरह कोड को और भी अधिक सामान्य बनाना है और फिर उन्हें फिर से इस तरह से और अधिक विशिष्ट बनाने के लिए उपयुक्त निहितार्थ रूपांतरण का उपयोग करना है जो सरणियाँ भी काम करती हैं। इस विशेष मामले में:
class GroupingCollection[A, C, D[C]](ca: C)(
implicit c2i: C => Iterable[A],
cbf: CanBuildFrom[C,C,D[C]],
cbfi: CanBuildFrom[C,A,C]
) {
def groupedWhile(p: (A,A) => Boolean): D[C] = {
val it = c2i(ca).iterator
val cca = cbf()
if (!it.hasNext) cca.result
else {
val as = cbfi()
var olda = it.next
as += olda
while (it.hasNext) {
val a = it.next
if (p(olda,a)) as += a
else { cca += as.result; as.clear; as += a }
olda = a
}
cca += as.result
}
cca.result
}
}
यहाँ हम एक अंतर्निहित जोड़ दिया है हमें एक देता है कि Iterable[A]
से C
--FOr सबसे संग्रह यह सिर्फ पहचान होगी (जैसे List[A]
पहले से ही एक है Iterable[A]
), लेकिन सरणियों के लिए यह एक वास्तविक अंतर्निहित रूपांतरण किया जाएगा। और, फलस्वरूप, हमने इस आवश्यकता को छोड़ दिया है कि - C[A] <: Iterable[A]
हमने मूल रूप से केवल <%
स्पष्ट करने के लिए आवश्यकता को पूरा किया है , इसलिए हम कंपाइलर को हमारे लिए भरने के बजाय इसका स्पष्ट रूप से उपयोग कर सकते हैं। इसके अलावा, हमने इस प्रतिबंध को शिथिल कर दिया है कि हमारा संग्रह-ऑफ-कलेक्शन है - C[C[A]]
यह कोई भी है D[C]
, जिसे हम बाद में भरना चाहते हैं। क्योंकि हम इसे बाद में भरने जा रहे हैं, इसलिए हमने इसे विधि के स्तर के बजाय कक्षा स्तर तक धकेल दिया है। अन्यथा, यह मूल रूप से एक ही है।
अब सवाल यह है कि इसका उपयोग कैसे किया जाए। नियमित संग्रह के लिए, हम कर सकते हैं:
implicit def collections_have_grouping[A, C[A]](ca: C[A])(
implicit c2i: C[A] => Iterable[A],
cbf: CanBuildFrom[C[A],C[A],C[C[A]]],
cbfi: CanBuildFrom[C[A],A,C[A]]
) = {
new GroupingCollection[A,C[A],C](ca)(c2i, cbf, cbfi)
}
अब हम कहाँ और किस C[A]
लिए प्लग इन करते हैं । ध्यान दें कि हमें कॉल पर स्पष्ट सामान्य प्रकारों की आवश्यकता है ताकि यह सीधे रख सके कि किस प्रकार के अनुरूप हैं। इसके लिए धन्यवाद , यह स्वचालित रूप से सरणियों को संभालता है।C
C[C[A]]
D[C]
new GroupingCollection
implicit c2i: C[A] => Iterable[A]
लेकिन रुको, अगर हम तार का उपयोग करना चाहते हैं तो क्या होगा? अब हम मुसीबत में हैं, क्योंकि आपके पास "तार का तार" नहीं हो सकता है। यह वह जगह है जहाँ अतिरिक्त अमूर्त मदद करता है: हम D
कुछ ऐसा कह सकते हैं जो तार को पकड़ने के लिए उपयुक्त है। चलो लेने Vector
, और निम्नलिखित करें:
val vector_string_builder = (
new CanBuildFrom[String, String, Vector[String]] {
def apply() = Vector.newBuilder[String]
def apply(from: String) = this.apply()
}
)
implicit def strings_have_grouping(s: String)(
implicit c2i: String => Iterable[Char],
cbfi: CanBuildFrom[String,Char,String]
) = {
new GroupingCollection[Char,String,Vector](s)(
c2i, vector_string_builder, cbfi
)
}
हमें CanBuildFrom
एक वेक्टर के निर्माण को संभालने के लिए एक नया चाहिए (लेकिन यह वास्तव में आसान है, क्योंकि हमें बस कॉल करने की आवश्यकता है Vector.newBuilder[String]
), और फिर हमें सभी प्रकारों को भरने की आवश्यकता है ताकि GroupingCollection
समझदारी से टाइप किया जाए। ध्यान दें कि हम पहले से ही [String,Char,String]
CanBuildFrom के चारों ओर तैर रहे हैं , इसलिए तार को संग्रह से बनाया जा सकता है।
आइये इसे आजमाते हैं:
scala> List(true,false,true,true,true).groupedWhile(_ == _)
res1: List[List[Boolean]] = List(List(true), List(false), List(true, true, true))
scala> Array(1,2,5,3,5,6,7,4,1).groupedWhile(_ <= _)
res2: Array[Array[Int]] = Array(Array(1, 2, 5), Array(3, 5, 6, 7), Array(4), Array(1))
scala> "Hello there!!".groupedWhile(_.isLetter == _.isLetter)
res3: Vector[String] = Vector(Hello, , there, !!)