LINQ's Distinct () किसी विशेष प्रॉपर्टी पर


1094

मैं इसके बारे में जानने के लिए LINQ के साथ खेल रहा हूँ, लेकिन मैं यह पता नहीं लगा सकता कि Distinctजब मैं एक साधारण सूची (पूर्णांक की एक साधारण सूची करना बहुत आसान है, तो यह सवाल नहीं है) का उपयोग कैसे करें। यदि मैं वस्तु के एक या अधिक गुणों पर किसी वस्तु की सूची में डिस्टिंच का उपयोग करना चाहता हूं तो क्या होगा ?

उदाहरण: यदि कोई वस्तु है Person, संपत्ति के साथ Id। मैं सभी व्यक्ति को कैसे प्राप्त कर सकता हूं और वस्तु Distinctकी संपत्ति Idके साथ उन पर उपयोग कर सकता हूं ?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

मैं बस Person1और कैसे मिल सकता हूं Person3? क्या यह संभव है?

यदि यह LINQ के साथ संभव नहीं है, तो Person.NET 3.5 में इसके कुछ गुणों के आधार पर सूची बनाने का सबसे अच्छा तरीका क्या होगा ?

जवाबों:


1242

EDIT : यह अब MoreLINQ का हिस्सा है ।

आपको जो कुछ भी चाहिए वह "अलग-अलग" प्रभावी रूप से है। मुझे विश्वास नहीं है कि यह LINQ का हिस्सा है क्योंकि यह खड़ा है, हालांकि इसे लिखना काफी आसान है:

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

इसलिए केवल Idसंपत्ति का उपयोग करके अलग-अलग मानों को खोजने के लिए , आप उपयोग कर सकते हैं:

var query = people.DistinctBy(p => p.Id);

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

var query = people.DistinctBy(p => new { p.Id, p.Name });

अनटाइटेड, लेकिन यह काम करना चाहिए (और यह अब कम से कम संकलन करता है)।

यह कुंजी के लिए डिफ़ॉल्ट तुलनित्र को मानता है - यदि आप एक समानता तुलनित्र में पास करना चाहते हैं, तो बस इसे HashSetकंस्ट्रक्टर पर पास करें ।



1
@ ashes999: मुझे यकीन नहीं है कि आपका क्या मतलब है। कोड जवाब में मौजूद है और पुस्तकालय में - चाहे आप एक निर्भरता पर लेने के लिए खुश हैं पर निर्भर करता है।
जॉन स्कीट

10
@ ashes999: यदि आप इसे केवल एक ही स्थान पर कर रहे हैं, कभी, तो यकीन है, का उपयोग GroupByसरल है। यदि आपको एक से अधिक स्थानों पर इसकी आवश्यकता है, तो यह इरादा साफ करने के लिए बहुत अधिक क्लीनर (आईएमओ) है।
जॉन स्कीट

5
@MatthewWhited: यह देखते हुए कि IQueryable<T>यहाँ कोई उल्लेख नहीं है, मैं नहीं देखता कि यह कैसे प्रासंगिक है। मैं मानता हूं कि यह EF आदि के लिए उपयुक्त नहीं होगा, लेकिन LINQ से लेकर ऑब्जेक्ट्स तक मुझे लगता है कि यह इससे कहीं अधिक उपयुक्त है GroupBy। प्रश्न का संदर्भ हमेशा महत्वपूर्ण होता है।
जॉन स्कीट

7
यह परियोजना
गिथुब में

1858

क्या होगा यदि मैं एक या अधिक गुणों के आधार पर एक अलग सूची प्राप्त करना चाहता हूं ?

सरल! आप उन्हें समूह बनाना चाहते हैं और विजेता को समूह से बाहर करना चाहते हैं।

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

यदि आप कई गुणों पर समूहों को परिभाषित करना चाहते हैं, तो यहां बताया गया है:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();

1
@ErenErsonmez ज़रूर। मेरे पोस्ट किए गए कोड के साथ, यदि आस्थगित निष्पादन वांछित है, तो ToList कॉल को छोड़ दें।
एमी बी

5
बहुत अच्छा जवाब! Reallllly ने मुझे एक sql दृश्य से संचालित Linq-to-Entities में मदद की जहां मैं दृश्य को संशोधित नहीं कर सका। मुझे फर्स्ट () के बजाय फ़र्स्टऑडफॉल्ट () का उपयोग करने की ज़रूरत थी - सब अच्छा है।
एलेक्स कीस्मिथ

8
मैंने इसे आजमाया और इसे बदलकर चयन करना चाहिए (g => g.FirstOrDefault ())

26
@ChocapicSz नहींं। स्रोत के एक से अधिक आइटम होने पर दोनों Single()और SingleOrDefault()प्रत्येक फेंकते हैं। इस ऑपरेशन में, हम इस संभावना की अपेक्षा करते हैं कि प्रत्येक समूह में एक आइटम अधिक हो सकता है। उस मामले के लिए, First()अधिक पसंद किया जाता है FirstOrDefault()क्योंकि प्रत्येक समूह में कम से कम एक सदस्य होना चाहिए .... जब तक आप EntityFramework का उपयोग नहीं कर रहे हैं, जो यह पता नहीं लगा सकता है कि प्रत्येक समूह में कम से कम एक सदस्य और मांगें हैं FirstOrDefault()
एमी बी

2
वर्तमान में EF Core में समर्थित नहीं होने लगता है, यहां तक ​​कि FirstOrDefault() github.com/dotnet/efcore/issues/12088 का उपयोग करके मैं 3.1 पर हूं, और मुझे "त्रुटियों का अनुवाद करने में असमर्थ" मिलता है।
कोलिन एम। बैरेट

78

उपयोग:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

whereआप प्रविष्टियों को फ़िल्टर मदद करता है (और अधिक जटिल हो सकता है) और groupbyऔर selectविशिष्ट कार्य करते हैं।


1
बिल्कुल सही, और Linq का विस्तार किए बिना या किसी अन्य निर्भरता का उपयोग किए बिना काम करता है।
डेविडशेकर

77

आप चाहें तो क्वेरी सिंटैक्स का उपयोग कर सकते हैं यदि आप चाहते हैं कि यह सभी LINQ की तरह दिखे:

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();

4
हम्म मेरे विचार क्वेरी सिंटैक्स और धाराप्रवाह एपीआई सिंटैक्स दोनों एक दूसरे की तरह ही LINQ हैं और इसकी सिर्फ प्राथमिकता है जिस पर लोग उपयोग करते हैं। मैं स्वयं धाराप्रवाह एपीआई पसंद करता हूं, इसलिए मैं इस पर अधिक लिंक-जैसे विचार करूंगा, लेकिन फिर मुझे लगता है कि यह व्यक्तिपरक है
मैक्स कैरोल

LINQ- लाइक का वरीयता से कोई लेना-देना नहीं है, "LINQ-like" होने के साथ सी क्वेरी # में एम्बेड की जा रही एक अलग क्वेरी की तरह दिखना है, मैं धाराप्रवाह इंटरफ़ेस पसंद करता हूं, जावा स्ट्रीम से आता है, लेकिन यह LINQ-Like नहीं है।
रयान द लीच

अति उत्कृष्ट!! आप मेरे हीरो हैं!
फ़रज़िन कांजी

63

मुझे लगता है कि यह पर्याप्त है:

list.Select(s => s.MyField).Distinct();

43
क्या होगा अगर उसे अपनी पूरी वस्तु वापस चाहिए, न कि केवल उस विशेष क्षेत्र को?
फेस्टिम काहानी

1
एक ही संपत्ति के मूल्य वाले कई वस्तुओं का वास्तव में क्या वस्तु है?
donRumatta

40

अपने क्षेत्रों द्वारा पहले समूह का समाधान करें फिर फ़र्स्टडफ़ॉल्ट आइटम चुनें।

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();

26

आप इसे मानक के साथ कर सकते हैं Linq.ToLookup()। यह प्रत्येक अद्वितीय कुंजी के लिए मूल्यों का एक संग्रह तैयार करेगा। बस संग्रह में पहले आइटम का चयन करें

Persons.ToLookup(p => p.Id).Select(coll => coll.First());

17

निम्नलिखित कोड कार्यात्मक रूप से जॉन स्कीट के उत्तर के बराबर है ।

.NET 4.5 पर परीक्षण किया, LINQ के किसी भी पुराने संस्करण पर काम करना चाहिए।

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

Google कोड पर गुप्त रूप से जॉन स्कीट के नवीनतम संस्करण DistinctBy.cs को देखें


3
इससे मुझे "अनुक्रम में कोई मूल्य नहीं मिला", लेकिन स्कीट के उत्तर ने सही परिणाम उत्पन्न किया।
क्या कूल होगा

10

मैंने एक लेख लिखा है जो बताता है कि डिस्टिक्ट फ़ंक्शन को कैसे बढ़ाया जाए ताकि आप इस प्रकार कर सकें:

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

यहां लेख है: LINQ का विस्तार - विशिष्ट फ़ंक्शन में एक संपत्ति निर्दिष्ट करना


3
आपके लेख में एक त्रुटि है, डिस्टिंक्ट के बाद एक <T> होना चाहिए: सार्वजनिक स्थैतिक IEnumerable <T> डिस्टेक्ट (यह ... यह भी नहीं लगता है कि यह काम करेगा (अच्छी तरह से) एक से अधिक संपत्ति पर यानी पहले का एक संयोजन और अंतिम नाम।
पंक्ति 1

2
+1, एक मामूली त्रुटि डाउनवोट के लिए पर्याप्त कारण नहीं है, कि बस इतना मूर्खतापूर्ण, अक्सर एक टाइपो कहा जाता है। और मुझे अभी तक एक सामान्य कार्य देखना है जो किसी भी संख्या में संपत्ति के लिए काम करेगा! मुझे उम्मीद है कि डाउनवॉटर ने इस धागे में भी हर दूसरे जवाब को नकार दिया है। लेकिन हे यह दूसरी प्रकार की वस्तु क्या है ?? मुझे ऐतराज है !
नवाफल

4
आपका लिंक टूट गया है
टॉम लिंट

7

व्यक्तिगत रूप से मैं निम्न वर्ग का उपयोग करता हूं:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

फिर, एक विस्तार विधि:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

अंत में, इच्छित उपयोग:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

इस दृष्टिकोण का उपयोग करके मैंने जो लाभ पाया, वह LambdaEqualityComparerअन्य तरीकों के लिए वर्ग का पुन: उपयोग है जो स्वीकार करते हैं IEqualityComparer। (ओह, और मैं yieldसामान को मूल LINQ कार्यान्वयन के लिए छोड़ देता हूं ...)


5

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

आप इसका उपयोग कैसे करते हैं:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);

5

जब हमने अपनी परियोजना में इस तरह के कार्य का सामना किया, तो हमने तुलना करने के लिए एक छोटे एपीआई को परिभाषित किया।

तो, उपयोग मामला इस तरह था:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

और एपीआई खुद इस तरह दिखता है:

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

हमारी साइट पर अधिक विवरण हैं: LINQ में IEqualityComparer


5

आप किसी ऑब्जेक्ट प्रॉपर्टी द्वारा डिस्टिक्ट रिकॉर्ड प्राप्त करने के लिए DistinctBy () का उपयोग कर सकते हैं। उपयोग करने से पहले बस निम्नलिखित कथन जोड़ें:

Microsoft.Ajax.Utilities का उपयोग करना;

और फिर इसे निम्नलिखित की तरह उपयोग करें:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

जहां 'सूचकांक' वह संपत्ति है जिस पर मैं चाहता हूं कि डेटा अलग हो।


4

आप ऐसा कर सकते हैं (यद्यपि बिजली जल्दी नहीं)

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

यही है, "उन सभी लोगों का चयन करें जहां एक ही आईडी के साथ सूची में कोई दूसरा व्यक्ति नहीं है।"

ध्यान रखें, आपके उदाहरण में, वह सिर्फ 3 व्यक्ति का चयन करेगा। मुझे यकीन नहीं है कि आप कैसे बता सकते हैं कि आप कौन चाहते हैं, पिछले दो में से।


4

यदि आप केवल DistinctByकार्यक्षमता प्राप्त करने के लिए MoreLinq लाइब्रेरी को अपनी परियोजना में जोड़ना नहीं चाहते हैं, तो आप Linq की Distinctविधि के ओवरलोड का उपयोग करके एक ही अंतिम परिणाम प्राप्त कर सकते हैं जो एक IEqualityComparerतर्क में लेता है ।

आप एक सामान्य कस्टम समानता तुलनित्र वर्ग बनाकर शुरू करते हैं जो एक सामान्य वर्ग के दो उदाहरणों की कस्टम तुलना करने के लिए लैम्ब्डा सिंटैक्स का उपयोग करता है:

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

    public bool Equals(T x, T y)
    {
        return _comparison(x, y);
    }

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

फिर अपने मुख्य कोड में आप इसे इस तरह उपयोग करते हैं:

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

देखा! :)

उपरोक्त मानता है:

  • संपत्ति Person.Idप्रकार की हैint
  • peopleसंग्रह किसी भी अशक्त तत्वों शामिल नहीं है

यदि संग्रह में नल हो सकते हैं, तो बस लंबोदर को नल की जांच के लिए फिर से लिखें, जैसे:

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

संपादित करें

यह दृष्टिकोण व्लादिमीर नेस्टरोवस्की के उत्तर के समान है लेकिन सरल है।

यह भी जोएल के जवाब में एक के समान है, लेकिन कई गुणों वाले जटिल तुलना तर्क के लिए अनुमति देता है।

हालांकि, अगर आपके वस्तुओं ही कभी से अलग कर सकते हैं Id, फिर अन्य उपयोगकर्ता सही जवाब के डिफ़ॉल्ट कार्यान्वयन ओवरराइड है कि तुम सब करने की जरूरत है दे दी है GetHashCode()और Equals()अपने में Personवर्ग और फिर बस से बाहर के बॉक्स का उपयोग Distinct()फिल्टर करने के लिए Linq की विधि किसी भी डुप्लिकेट।


मैं तानाशाही में केवल अद्वितीय आइटम प्राप्त करना चाहता हूं, क्या आप मदद कर सकते हैं, मैं इस कोड का उपयोग कर रहा हूं यदि TempDT IsNot कुछ नहीं है तो m_ConcurrentScriptScript = TempDT.AsEnumerable.ToDictionary (फ़ंक्शन (x) x.SafeField (fldClusterId, NULL_ID_VALUE), फ़ंक्शन। y.SafeField (fldParamValue11, NULL_ID_VALUE))
RSB

2

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


1
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();

क्या आपको Select() new Personइसके बजाय मतलब था new Player? यह तथ्य कि आप जो आदेश दे रहे हैं, IDवह किसी भी तरह Distinct()से विशिष्टता का निर्धारण करने में उस संपत्ति का उपयोग करने के लिए सूचित नहीं करता है , हालांकि यह काम नहीं करेगा।
बैकोन

1

अवहेलना बराबर (वस्तु obj) और GetHashCode () विधि:

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

और फिर कॉल करें:

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

हालाँकि GetHashCode () को अधिक उन्नत (नाम भी गिनने के लिए) होना चाहिए, यह उत्तर संभवतः मेरी राय से सर्वोत्तम है। वास्तव में, लक्ष्य तर्क को संग्रहीत करने के लिए, GetHashCode (), समतुल्य () पर्याप्त नहीं है, लेकिन यदि हमें प्रदर्शन की आवश्यकता है, तो हमें इसे ओवरराइड करना होगा। सभी तुलना अल्ग, पहले हैश की जाँच करें, और यदि वे समान हैं तो बराबर () को कॉल करें।
ओलेग स्क्रीपनीक

इसके अलावा, वहाँ बराबर में () पहली पंक्ति "अगर ((obj व्यक्ति है)) वापस झूठी" होनी चाहिए। लेकिन सबसे अच्छा अभ्यास एक प्रकार के लिए डाली गई अलग वस्तु का उपयोग करना है, जैसे "var o = obj for Person; if (o == null) झूठे लौटना;" फिर बिना कास्टिंग के ओ के साथ समानता की जांच करें
ओलेग स्क्रीपनीक

1
इस तरह से बराबरी करना एक अच्छा विचार नहीं है क्योंकि यह अन्य प्रोग्रामर के लिए अनपेक्षित परिणाम हो सकता है जो व्यक्ति की समानता को एक से अधिक संपत्ति पर निर्धारित करने की उम्मीद करता है।
B2K

0

आप व्यक्ति पर समानताओं को ओवरराइड करने में सक्षम होना चाहिए वास्तव में पर्सनलाइड पर बराबर करते हैं। यह आपके द्वारा किए गए व्यवहार के परिणामस्वरूप होना चाहिए।


-5

कृपया नीचे दिए गए कोड के साथ कोशिश करें।

var Item = GetAll().GroupBy(x => x .Id).ToList();

3
एक संक्षिप्त उत्तर का स्वागत है, हालांकि यह बाद वाले उपयोगकर्ताओं को बहुत अधिक मूल्य प्रदान नहीं करेगा जो समझने की कोशिश कर रहे हैं कि समस्या के पीछे क्या हो रहा है। समस्या के कारण और समस्या को हल करने के लिए वास्तविक मुद्दा क्या है, यह समझाने के लिए कृपया कुछ समय दें। आप ~ धन्यवाद
Hearen
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.