c# LINQ की विशिष्ट() एक विशेष संपत्ति पर




.net-3.5 distinct (14)

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

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

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

मैं सिर्फ व्यक्ति 1 और व्यक्ति 3 कैसे प्राप्त कर सकता हूं? क्या यह संभव है?

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


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


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

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

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

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

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

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

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


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

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 क्लास का पुन: उपयोग अन्य तरीकों के लिए है जो एक LambdaEqualityComparer स्वीकार करते हैं। (ओह, और मैं yield सामग्री को मूल LINQ कार्यान्वयन में छोड़ देता हूं ...)


यदि आप अपनी परियोजना में MoreLinq लाइब्रेरी को केवल DistinctBy कार्यक्षमता प्राप्त करने के लिए नहीं जोड़ना चाहते हैं तो आप एक समान अंत परिणाम प्राप्त कर सकते हैं जो लिंक की 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() के डिफ़ॉल्ट कार्यान्वयन को ओवरराइड करना है और फिर केवल आउट-ऑफ- किसी भी डुप्लिकेट को फ़िल्टर करने के लिए लिंक के Distinct() बॉक्स Distinct() विधि।


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

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

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

संपादित करें : यह अब 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 कन्स्ट्रक्टर पर पास करें।


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

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

मैंने एक लेख लिखा है जो बताता है कि विशिष्ट कार्य को कैसे विस्तारित किया जाए ताकि आप निम्नानुसार कार्य कर सकें:

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 का विस्तार - विशिष्ट कार्य में एक संपत्ति निर्दिष्ट करना


उपयोग:

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


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


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

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

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


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

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

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();




distinct