2010-10-12 19 views
46

मुझे ऑब्जेक्ट्स की एक सूची मिली है List[Object] जो सभी एक ही कक्षा से तत्काल हैं। इस वर्ग में एक फ़ील्ड है जो अद्वितीय Object.property होना चाहिए। वस्तुओं की सूची को फिर से शुरू करने और एक ही संपत्ति के साथ सभी वस्तुओं (लेकिन पहले) को हटाने का सबसे साफ तरीका क्या है?स्कैला: ऑब्जेक्ट्स की सूची में डुप्लीकेट हटाएं

+0

बजाय एक सेट का उपयोग कर एक सूची के बारे में क्या? इसके अलावा, आप ऑब्जेक्ट से क्यों व्यवहार कर रहे हैं, यानी कक्षा पदानुक्रम के शीर्ष पर? –

उत्तर

109
list.groupBy(_.property).map(_._2.head) 

स्पष्टीकरण: समूह द्वारा विधि एक फ़ंक्शन स्वीकार करती है जो समूह के लिए किसी तत्व को कुंजी में परिवर्तित करती है। _.propertyelem: Object => elem.property के लिए सिर्फ लघुरूप है (संकलक एक अद्वितीय नाम उत्पन्न करता है, x$1 जैसा कुछ)। तो अब हमारे पास नक्शा Map[Property, List[Object]] है। Map[K,V]Traversable[(K,V)] बढ़ाता है। तो इसे एक सूची की तरह घुमाया जा सकता है, लेकिन तत्व एक tuple हैं। यह जावा के Map#entrySet() के समान है। नक्शा विधि प्रत्येक तत्व को पुन: सक्रिय करके और इसमें एक फ़ंक्शन लागू करके एक नया संग्रह बनाता है। इस मामले में फ़ंक्शन _._2.head है जो elem: (Property, List[Object]) => elem._2.head के लिए शॉर्टेंड है। _2 सिर्फ ट्यूपल का एक तरीका है जो दूसरा तत्व देता है। दूसरा तत्व की सूची [वस्तु] और head पहला तत्व

रिटर्न परिणाम प्राप्त करने के लिए एक प्रकार आप चाहते हैं हो रहा है:

import collection.breakOut 
val l2: List[Object] = list.groupBy(_.property).map(_._2.head)(breakOut) 

संक्षिप्त व्याख्या करने के लिए, map वास्तव में दो तर्क, एक समारोह और एक उम्मीद ऑब्जेक्ट जिसका उपयोग परिणाम बनाने के लिए किया जाता है। पहले कोड स्निपेट में आपको दूसरा मान नहीं दिखाई देता है क्योंकि इसे निहित के रूप में चिह्नित किया जाता है और इसलिए कंपाइलर द्वारा पूर्वनिर्धारित मानों की सूची से प्रदान किया जाता है। नतीजा आमतौर पर मैप किए गए कंटेनर से प्राप्त होता है। यह आमतौर पर एक अच्छी बात है। सूची पर नक्शा सूची लौटाएगा, ऐरे पर नक्शा ऐरे वापस लौटाएगा। इस मामले में, हम कंटेनर को व्यक्त करना चाहते हैं जिसके परिणामस्वरूप हम चाहते हैं। यह वह जगह है जहां ब्रेकऑट विधि का उपयोग किया जाता है। यह वांछित परिणाम प्रकार को देखकर एक निर्माता (वह चीज जो परिणाम बनाता है) बनाता है। यह एक सामान्य तरीका है और संकलक इसके जेनेरिक प्रकार infers क्योंकि हम स्पष्ट रूप से l2 टाइप किया List[Object] होने के लिए या, क्रम बनाए रखने के (यह मानते हुए Object#property प्रकार Property की है):

list.foldRight((List[Object](), Set[Property]())) { 
    case (o, [email protected](objects, props)) => 
    if (props(o.property)) cum else (o :: objects, props + o.property)) 
}._1 

foldRight एक विधि है कि एक प्रारंभिक परिणाम को स्वीकार करता है और एक ऐसा फ़ंक्शन जो तत्व को स्वीकार करता है और एक अद्यतन परिणाम देता है। विधि प्रत्येक तत्व को पुन: लागू करती है, प्रत्येक तत्व को फ़ंक्शन को लागू करने और अंतिम परिणाम लौटने के परिणामस्वरूप परिणाम अपडेट करती है। हम दाएं से बाएं (foldLeft के साथ बाएं से दाएं के बजाय) जाते हैं क्योंकि हम objects पर आगे बढ़ रहे हैं - यह ओ (1) है, लेकिन संलग्न करना ओ (एन) है। यहां अच्छी स्टाइल का भी निरीक्षण करें, हम तत्व निकालने के लिए एक पैटर्न मैच का उपयोग कर रहे हैं।

इस मामले में, प्रारंभिक परिणाम एक खाली सूची और एक सेट की एक जोड़ी (tuple) है। सूची वह परिणाम है जिसमें हम रुचि रखते हैं और सेट का उपयोग यह जानने के लिए किया जाता है कि हम किन संपत्तियों का सामना कर चुके हैं। प्रत्येक यात्रा में हम जाँच करता है, तो सेट props पहले से ही प्रॉपर्टी वाला (स्काला में, obj(x)obj.apply(x) लिए अनुवाद किया है। Set में, विधि applydef apply(a: A): Boolean है। यही कारण है कि है, एक तत्व स्वीकार करता है और सही/गलत रिटर्न यदि वह मौजूद है या नहीं)। यदि संपत्ति मौजूद है (पहले से ही सामना किया गया है), परिणाम के रूप में वापस आ गया है।अन्यथा परिणाम वस्तु (o :: objects) को रोकने के लिए अद्यतन किया जाता है और संपत्ति दर्ज की गई है (props + o.property)

अद्यतन:

import scala.collection.IterableLike 
import scala.collection.generic.CanBuildFrom 

class RichCollection[A, Repr](xs: IterableLike[A, Repr]){ 
    def distinctBy[B, That](f: A => B)(implicit cbf: CanBuildFrom[Repr, A, That]) = { 
    val builder = cbf(xs.repr) 
    val i = xs.iterator 
    var set = Set[B]() 
    while (i.hasNext) { 
     val o = i.next 
     val b = f(o) 
     if (!set(b)) { 
     set += b 
     builder += o 
     } 
    } 
    builder.result 
    } 
} 

implicit def toRich[A, Repr](xs: IterableLike[A, Repr]) = new RichCollection(xs) 

उपयोग करने के लिए:

scala> list.distinctBy(_.property) 
res7: List[Obj] = List(Obj(1), Obj(2), Obj(3)) 

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

list.filterNot{ var set = Set[Property]() 
    obj => val b = set(obj.property); set += obj.property; b} 

हालांकि यह आंतरिक रूप से एक वर, मुझे लगता है कि यह समझना महत्वपूर्ण है और foldLeft-समाधान की तुलना में पढ़ने में आसान है का उपयोग करता है:

+0

भयानक होगा यदि आप त्वरित स्पष्टीकरण प्रदान कर सकते हैं। मुझे लगता है कि स्कैला पर्याप्त रूप से नया है कि हर कोई इसे तुरंत समझ नहीं पाएगा। –

+0

विशेष रूप से, क्या 'इस संदर्भ में क्या _2' करता है? –

+0

@Sudhir: _1 और _2 तरीकों कि एक टपल के पहले और दूसरे तत्व लौट आते हैं। – Landei

12

यहाँ एक छोटा सा डरपोक लेकिन तेजी से समाधान है कि आदेश को बरकरार रखता है है।

+5

मैं सहमत हूं। Var – IttayD

+0

के छिपाने के दायरे के साथ कूल चाल मैं स्पष्ट रूप से यहां कुछ खो रहा हूं। संपत्ति वास्तव में क्या है? – parsa

+0

@ पारसा 28: संपत्ति obj.property का प्रकार है – Landei

6

एक और समाधान

@tailrec 
def collectUnique(l: List[Object], s: Set[Property], u: List[Object]): List[Object] = l match { 
    case Nil => u.reverse 
    case (h :: t) => 
    if (s(h.property)) collectUnique(t, s, u) else collectUnique(t, s + h.prop, h :: u) 
} 
+1

कार्यात्मक: डी! – noncom

-3

मैं स्काला का कौन सा संस्करण उपयोग कर रहे हैं पता नहीं है, लेकिन निश्चित रूप से 2.8.2 है

list.distinct 

संपादित करें (फिक्सिंग नीचे वोट)

list.distinctBy 
+4

यह सवाल किसी विशेष मामले में काम नहीं करेगा, क्योंकि प्रश्न यह है कि: * "इस वर्ग में ** फ़ील्ड ** है जो अद्वितीय होना चाहिए: 'ऑब्जेक्ट.प्रोपर्टी'" * – KajMagnus

+0

इससे मेरी मदद की .. मैं इस सवाल के बारे में मत घूमें :) :) – neham

2

मुझे इसे समूह के साथ काम करने का एक तरीका मिला, जिसमें एक के साथ termediary कदम:

def distinctBy[T, P, From[X] <: TraversableLike[X, From[X]]](collection: From[T])(property: T => P): From[T] = { 
    val uniqueValues: Set[T] = collection.groupBy(property).map(_._2.head)(breakOut) 
    collection.filter(uniqueValues) 
} 

इस तरह यह प्रयोग करें:

scala> distinctBy(List(redVolvo, bluePrius, redLeon))(_.color) 
res0: List[Car] = List(redVolvo, bluePrius) 

IttayD का पहला समाधान की तरह, लेकिन यह अद्वितीय मानों की सेट के आधार पर मूल संग्रह फिल्टर। groupBy के लिए एक, map के लिए एक और filter के लिए एक: मेरी उम्मीदों सही हैं, तो यह तीन traversals करता है। यह मूल संग्रह के आदेश को बनाए रखता है, लेकिन यह आवश्यक नहीं है कि प्रत्येक संपत्ति के लिए पहला मूल्य लें। उदाहरण के लिए, यह इसके बजाय List(bluePrius, redLeon) वापस कर सकता था।

बेशक

, IttayD समाधान अभी भी तेजी के बाद से यह सिर्फ एक ही ट्रेवर्सल करता है।

मेरा समाधान भी नुकसान पहुंचाता है कि, यदि संग्रह में Car एस वास्तव में समान हैं, तो दोनों आउटपुट सूची में होंगे। इसे filter को हटाकर uniqueValues को सीधे From[T] के साथ वापस ले जाया जा सकता है। हालांकि, यह CanBuildFrom[Map[P, From[T]], T, From[T]] की तरह लगता है मौजूद नहीं है ... सुझाव का स्वागत है!

4
क्रम बनाए रखने के साथ

:

def distinctBy[L, E](list: List[L])(f: L => E): List[L] = 
    list.foldLeft((Vector.empty[L], Set.empty[E])) { 
    case ((acc, set), item) => 
     val key = f(item) 
     if (set.contains(key)) (acc, set) 
     else (acc :+ item, set + key) 
    }._1.toList 

distinctBy(list)(_.property) 
+1

आप अधिक सामान्य समाधान के लिए सेक [एल] का उपयोग कर सकते हैं। –

संबंधित मुद्दे