क्या डिस्टिंच () पद्धति अनुक्रम के मूल क्रम को बरकरार रखती है?


84

मैं सूची में अद्वितीय तत्वों के क्रम को बदलने के बिना, सूची से डुप्लिकेट को निकालना चाहता हूं।

जॉन स्कीट और अन्य ने निम्नलिखित का उपयोग करने का सुझाव दिया है:

list = list.Distinct().ToList();

संदर्भ:

क्या यह गारंटी है कि अद्वितीय तत्वों का क्रम पहले जैसा होगा? यदि हाँ, तो कृपया एक संदर्भ दें जो इस बात की पुष्टि करता है कि मैं प्रलेखन में इस पर कुछ नहीं पा सकता था।


5
@ColonelPanic - यहाँ आधिकारिक दस्तावेज msdn.microsoft.com/en-us/library/bb348436(v=vs.110).aspx राज्यों को स्पष्ट रूप से "अलग () विधि रिटर्न एक अव्यवस्थित अनुक्रम है कि कोई डुप्लिकेट मान हैं"।
एवोक

@Evk 'अनियोजित अनुक्रम' 'अनुक्रम के मूल क्रम' के समान नहीं है।
नितेश

3
मैं "बिना किसी विशेष क्रम में" का अर्थ "अनियंत्रित" मानता हूं, जिसका अर्थ "अनुक्रम के मूल क्रम में आवश्यक नहीं" भी है।
Evk

मुझे oracle12 Entity फ्रेमवर्क के साथ अलग से संबंधित एक समस्या थी। 6. मेरे मामले में मेरे लाइन क्लॉज में अरुचि होने से पहले मैंने ऑर्डर किया था और ऑर्डर चला गया था। select ()। OrderBy ()। Distinct ()। ToList () ने select करते समय काम नहीं किया ()। OrderBy ()। Distinct ()। ToList () ने काम किया।
कार्ल

2
@ कार्ल, ये भाव समान हैं। :)
पावगोरन

जवाबों:


77

इसकी गारंटी नहीं है, लेकिन यह सबसे स्पष्ट कार्यान्वयन है। स्ट्रीमिंग तरीके से लागू करना कठिन होगा (जैसे कि यह परिणाम जल्द से जल्द लौटा सकता है, ताकि इसे कम से कम पढ़ा जा सके) उन्हें क्रम में वापस किए बिना

आप डिस्टिंक्ट () के एडुलिनक कार्यान्वयन पर मेरे ब्लॉग पोस्ट को पढ़ना चाह सकते हैं ।

ध्यान दें कि भले ही यह LINQ ऑब्जेक्ट्स के लिए गारंटीकृत हो (जो व्यक्तिगत रूप से मुझे लगता है कि यह होना चाहिए), जो LINQ से SQL जैसे अन्य LINQ प्रदाताओं के लिए कुछ भी मतलब नहीं होगा।

LINQ से लेकर ऑब्जेक्ट तक प्रदान की जाने वाली गारंटियों का स्तर कभी-कभी IMO से थोड़ा असंगत होता है। कुछ अनुकूलन प्रलेखित हैं, अन्य नहीं। बिल्ली, प्रलेखन में से कुछ गलत है


मैं इसे स्वीकार कर रहा हूं क्योंकि 1) यह स्पष्ट रूप से मेरी चिंता का जवाब देता है कि इसकी गारंटी दी गई है या नहीं 2) लिंक की गई पोस्ट डिस्टिक्ट 3 के अनिर्धारित पहलुओं में गहराई से उजागर होती है) लिंक किए गए पोस्ट में एक नमूना कार्यान्वयन भी है जिसे डिस्टिक्ट को लागू करने के लिए संदर्भ के रूप में इस्तेमाल किया जा सकता है उस गारंटी के साथ सूचीबद्ध करता है।
नितेश

25

.NET फ्रेमवर्क 3.5 में, लिनक-टू-ऑब्जेक्ट्स कार्यान्वयन के सीआईएल को खारिज करते हुए Distinct()दिखाता है कि तत्वों का क्रम संरक्षित है - हालांकि यह प्रलेखित व्यवहार नहीं है।

मैंने रिफ्लेक्टर के साथ थोड़ी जांच की। System.Core.dll, संस्करण = 3.5.0.0 को अलग करने के बाद आप देख सकते हैं कि Distinct () एक एक्सटेंशन विधि है, जो इस तरह दिखता है:

public static class Emunmerable
{
    public static IEnumerable<TSource> Distinct<TSource>(this IEnumerable<TSource> source)
    {
        if (source == null)
            throw new ArgumentNullException("source");

        return DistinctIterator<TSource>(source, null);
    }
}

तो, यहां दिलचस्प है डिस्टिक्टिएटर, जो IEnumerable और IEnumerator को लागू करता है। इस IEnumerator के कार्यान्वयन को सरल (गोटो और लैब्स को हटा दिया गया है):

private sealed class DistinctIterator<TSource> : IEnumerable<TSource>, IEnumerable, IEnumerator<TSource>, IEnumerator, IDisposable
{
    private bool _enumeratingStarted;
    private IEnumerator<TSource> _sourceListEnumerator;
    public IEnumerable<TSource> _source;
    private HashSet<TSource> _hashSet;    
    private TSource _current;

    private bool MoveNext()
    {
        if (!_enumeratingStarted)
        {
            _sourceListEnumerator = _source.GetEnumerator();
            _hashSet = new HashSet<TSource>();
            _enumeratingStarted = true;
        }

        while(_sourceListEnumerator.MoveNext())
        {
            TSource element = _sourceListEnumerator.Current;

             if (!_hashSet.Add(element))
                 continue;

             _current = element;
             return true;
        }

        return false;
    }

    void IEnumerator.Reset()
    {
        throw new NotSupportedException();
    }

    TSource IEnumerator<TSource>.Current
    {
        get { return _current; }
    }

    object IEnumerator.Current
    {        
        get { return _current; }
    }
}

जैसा कि आप देख सकते हैं - enumerating स्रोत enumerable द्वारा प्रदान क्रम में जाता है (सूची, जिस पर हम कॉल कर रहे हैं Distinct)। Hashsetइसका उपयोग केवल यह निर्धारित करने के लिए किया जाता है कि क्या हम पहले से ही ऐसे तत्व को लौटाते हैं या नहीं। यदि नहीं, तो हम इसे लौटा रहे हैं, अन्यथा - स्रोत पर गणना जारी रखें।

इसलिए, यह गारंटी है, कि Distinct()तत्वों को ठीक उसी क्रम में लौटाया जाएगा , जो संग्रह द्वारा प्रदान किया जाता है, जिसमें डिस्टिंक्ट लागू किया गया था।


8
क्या यह एक अच्छी तरह से प्रलेखित व्यवहार है?
abatishchev

4
लिंक किए गए उत्तर में प्रलेखन का संदर्भ है जो कहता है: "परिणाम अनुक्रम अनियंत्रित है।"
मिलीग्राम

5
@lazyberezovsky: सवाल गारंटी के बारे में पूछता है , न कि सामान्य कार्यान्वयन का । (जैसा कि मैंने पहले ही कहा, मुझे आश्चर्य होगा कि कार्यान्वयन कभी भी प्लेटफार्मों / संस्करणों में बदल जाता है, लेकिन इसकी गारंटी नहीं है।)
ल्यूक

5
@lazyberezovsky: मैं C \ C ++ से हूं, जहां बहुत सारी चीजें अपरिभाषित हैं और यदि कुछ गारंटी दी जाती है, तो यह पूछना बहुत आम है। इसके अलावा, मैं एक सिल्वरलाइट एप्लिकेशन में डिस्टिंक्ट () का उपयोग कर रहा हूं, जो मैक और विंडोज दोनों पर है, इसलिए हम 'सामान्य कार्यान्वयन' पर समझौता नहीं कर सकते, इसकी गारंटी होनी चाहिए।
नितेश

43
@lazyberezovsky: जब लोग गारंटी के बारे में बात करते हैं, तो उनका मतलब सामान्य रूप से प्रलेखित व्यवहार होता है, जिस पर भरोसा करना उचित होता है। उदाहरण के लिए, GroupBy के लिए डॉक्स व्यवहार निर्दिष्ट करते हैं, लेकिन डिस्टिक्ट के लिए डॉक्स नहीं करते हैं
जॉन स्कीट

14

प्रलेखन के अनुसार अनुक्रम अनियंत्रित है।


3
इसे खोजने के लिए अतिरिक्त जानकारी: लिंक में, "रिमार्क्स" अनुभाग देखें। "परिणाम अनुक्रम अनियंत्रित है।"
कर्टिस येलोप

6

हाँ , Enumerable.Distinct आदेश को संरक्षित करता है। आलसी होने की विधि को मानते हुए "अलग-अलग मूल्य मिलते हैं जैसे ही उन्हें देखा जाता है", यह स्वचालित रूप से अनुसरण करता है। इसके बारे में सोचो।

नेट संदर्भ स्रोत पुष्टि करता है। यह एक समानता, प्रत्येक तुल्यता वर्ग में पहला तत्व देता है।

foreach (TSource element in source)
    if (set.Add(element)) yield return element;

नेट कोर कार्यान्वयन समान है।

निराशा की बात है, Enumerable.Distinct के लिए प्रलेखन इस बिंदु पर भ्रमित है:

परिणाम अनुक्रम अनियंत्रित है।

मैं केवल कल्पना कर सकता हूं कि उनका मतलब है "परिणाम अनुक्रम सॉर्ट नहीं किया गया है।" आप प्रत्येक तत्व की तुलना पहले से करते हुए डिस्टिक्ट को लागू कर सकते हैं , लेकिन यह ऊपर वर्णित के रूप में आलसी नहीं होगा।


7
स्रोत विनिर्देश नहीं है। आपको जो मिला है वह एक संयोग है और अगले अपडेट के बाद अमान्य हो सकता है।
हेनक होल्टरमैन

@ हेंकोल्टरमैन सामान्य तौर पर, मैं सहमत हूँ, कार्यान्वयन बदल सकते हैं। उदाहरण के लिए, .NET 4.5 ने Array.Sort के पीछे छँटाई एल्गोरिथ्म को बदल दिया । हालांकि इस विशेष मामले में, Enumerable.Distinct का कोई भी समझदार कार्यान्वयन निश्चित रूप से आलसी होगा ("पैदावार अलग-अलग मूल्य जल्द से जल्द दिखाई देते हैं"), और ऑर्डर-प्रोटेक्टिंग प्रॉपर्टी इस प्रकार है। आलसी मूल्यांकन वस्तुओं को LINQ का एक मुख्य सिद्धांत है; बचाव के लिए यह अकल्पनीय होगा।
कर्नल पैनिक

1
मैंने .net 4.6 का उपयोग करते हुए कार्यान्वयन देखा है, जहाँ कॉलिंग dbQuery.OrderBy(...).Distinct().ToList()आदेश द्वारा निर्दिष्ट क्रम में एक सूची नहीं लौटाती है - डिस्टिक्ट को हटाने (जो निरर्थक हुआ) ने मेरे मामले में बग को ठीक कर दिया है
रोलैंड शॉ

1

डिफ़ॉल्ट रूप से उपयोग करते समय डिस्टिंचल लाइन ऑपरेटर ऑपरेटर विधि का उपयोग करता है लेकिन आप IEqualityComparer<T>दो ऑब्जेक्ट्स कस्टम तर्क लागू करने GetHashCodeऔर Equalsविधि के बराबर होने पर निर्दिष्ट करने के लिए अपनी खुद की वस्तु का उपयोग कर सकते हैं । उसे याद रखो:

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

आपके पास MyTypeऔर एक MyTypeEqualityComparerकक्षा के बाद कोड का पालन करना सुनिश्चित न करें कि अनुक्रम अपने आदेश को बनाए रखता है:

var cmp = new MyTypeEqualityComparer();
var lst = new List<MyType>();
// add some to lst
var q = lst.Distinct(cmp);

विज्ञान पुस्तकालय का अनुसरण करने के लिए, मैंने एक विशिष्ट पद्धति का उपयोग करते हुए वेक्टर 3 डी सेट को बनाए रखने के लिए एक विस्तार विधि लागू की DistinctKeepOrder:

प्रासंगिक कोड इस प्रकार है:

/// <summary>
/// support class for DistinctKeepOrder extension
/// </summary>
public class Vector3DWithOrder
{
    public int Order { get; private set; }
    public Vector3D Vector { get; private set; }
    public Vector3DWithOrder(Vector3D v, int order)
    {
        Vector = v;
        Order = order;
    }
}

public class Vector3DWithOrderEqualityComparer : IEqualityComparer<Vector3DWithOrder>
{
    Vector3DEqualityComparer cmp;

    public Vector3DWithOrderEqualityComparer(Vector3DEqualityComparer _cmp)
    {
        cmp = _cmp;
    }

    public bool Equals(Vector3DWithOrder x, Vector3DWithOrder y)
    {
        return cmp.Equals(x.Vector, y.Vector);
    }

    public int GetHashCode(Vector3DWithOrder obj)
    {
        return cmp.GetHashCode(obj.Vector);
    }
}

संक्षेप Vector3DWithOrderमें प्रकार और एक आदेश पूर्णांक Vector3DWithOrderEqualityComparerencapsulate , जबकि मूल प्रकार तुलनित्र encapsulate।

और यह व्यवस्था बनाए रखने के लिए सहायक विधि है

/// <summary>
/// retrieve distinct of given vector set ensuring to maintain given order
/// </summary>        
public static IEnumerable<Vector3D> DistinctKeepOrder(this IEnumerable<Vector3D> vectors, Vector3DEqualityComparer cmp)
{
    var ocmp = new Vector3DWithOrderEqualityComparer(cmp);

    return vectors
        .Select((w, i) => new Vector3DWithOrder(w, i))
        .Distinct(ocmp)
        .OrderBy(w => w.Order)
        .Select(w => w.Vector);
}

नोट : आगे के शोध एक अधिक सामान्य (इंटरफेस का उपयोग) और अनुकूलित तरीके (ऑब्जेक्ट को बिना एनकैप्सुलेट किए बिना) खोजने की अनुमति दे सकते हैं।


1

यह अत्यधिक आपके linq- प्रदाता पर निर्भर करता है। Linq2Objects पर आप आंतरिक स्रोत-कोड पर बने रह सकते हैं Distinct, जिससे यह माना जाता है कि मूल ऑर्डर संरक्षित है।

हालाँकि, अन्य प्रदाताओं के लिए जो कुछ प्रकार के एसक्यूएल के लिए उदाहरण के लिए हल करते हैं, जो कि ORDER BYआमतौर पर मामला नहीं है, क्योंकि आमतौर पर किसी भी एकत्रीकरण (जैसे Distinct) के बाद आता है । तो अगर आपका कोड यह है:

myArray.OrderBy(x => anothercol).GroupBy(x => y.mycol);

यह SQL में निम्नलिखित के समान कुछ के लिए अनुवादित है:

SELECT * FROM mytable GROUP BY mycol ORDER BY anothercol;

यह स्पष्ट रूप से आपके डेटा को पहले समूहित करता है और बाद में इसे क्रमबद्ध करता है। अब आप DBMS पर ही अटक गए हैं कि इसे कैसे निष्पादित किया जाए। कुछ DBMS पर यह भी अनुमति नहीं है। निम्नलिखित आंकड़ों की कल्पना करें:

mycol anothercol
1     2
1     1
1     3
2     1
2     3

निष्पादित करते समय myArr.OrderBy(x => x.anothercol).GroupBy(x => x.mycol)हम निम्नलिखित परिणाम मानते हैं:

mycol anothercol
1     1
2     1

लेकिन DBMS एक और कोलम-कॉलम को एकत्रित कर सकता है, जिससे कि पहली पंक्ति के मूल्य का उपयोग किया जाता है, जिसके परिणामस्वरूप निम्नलिखित सॉफ्टवेयर हैं:

mycol anothercol
1    2
2    1

आदेश देने के बाद यह परिणाम होगा:

mycol anothercol
2    1
1    2

यह निम्नलिखित के समान है:

SELECT mycol, First(anothercol) from mytable group by mycol order by anothercol;

जो आपके द्वारा अपेक्षित की तुलना में पूरी तरह से रिवर्स ऑर्डर है।

आप देखते हैं कि निष्पादन-योजना अंतर्निहित प्रदाता के आधार पर भिन्न हो सकती है। यही कारण है कि डॉक्स में इस बारे में कोई गारंटी नहीं है।

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