c# लैम्ब्डा के साथ अलग()?




c#-3.0 lambda (14)

ठीक है, तो मेरे पास एक संख्यात्मक है और इससे अलग मूल्य प्राप्त करना चाहते हैं।

System.Linq का उपयोग करना, निश्चित रूप से एक विस्तार विधि कहा जाता है जिसे Distinct कहा जाता है। साधारण मामले में, इसका उपयोग किसी पैरामीटर के साथ नहीं किया जा सकता है, जैसे:

var distinctValues = myStringList.Distinct();

अच्छा और अच्छा, लेकिन अगर मेरे पास ऐसी वस्तुओं का एक अनुमान है जिसके लिए मुझे समानता निर्दिष्ट करने की आवश्यकता है, तो केवल उपलब्ध अधिभार ही है:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

समानता तुलनाकर्ता तर्क IEqualityComparer<T> का एक उदाहरण होना चाहिए। मैं यह निश्चित रूप से कर सकता हूं, लेकिन यह कुछ हद तक verbose और, अच्छी तरह से, cludgy है।

मैं जो अपेक्षा करता था वह एक अधिभार है जो एक लैम्ब्डा लेता है, एक Func <टी, टी, बूल> कहें:

var distinctValues
    = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

किसी को पता है कि कुछ ऐसे एक्सटेंशन मौजूद हैं, या कुछ समकक्ष कामकाज? या क्या मैं कुछ न कुछ भूल रहा हूं?

वैकल्पिक रूप से, क्या एक IEqualityComparer इनलाइन निर्दिष्ट करने का कोई तरीका है (मुझे शर्मिंदा करें)?

अद्यतन करें

मुझे एंडर्स हेजल्सबर्ग ने इस विषय पर एक एमएसडीएन मंच में एक post लिए एक post दिया। वह कहता है:

जिस समस्या में आप भागने जा रहे हैं वह यह है कि जब दो ऑब्जेक्ट बराबर की तुलना करते हैं तो उनके पास गेटहैशकोड रिटर्न वैल्यू होना चाहिए (या फिर डिस्टिंट द्वारा आंतरिक रूप से उपयोग की गई हैश तालिका सही ढंग से काम नहीं करेगी)। हम IEqualityComparer का उपयोग करते हैं क्योंकि यह समान इंटरफ़ेस में समान और GetHashCode के संगत कार्यान्वयन पैकेज करता है।

मुझे लगता है कि समझ में आता है ..


मैंने जो कुछ किया है वह मेरे लिए अच्छा काम करता है।

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

एक और तरीका ले लो:

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

अनुक्रम वापसी विशिष्ट तत्वों की तुलना संपत्ति '_myCustomerProperty' से करते हैं।


ऐसा करने का एक मुश्किल तरीका Aggregate() एक्सटेंशन का उपयोग करता है, कुंजी-प्रॉपर्टी मानों के साथ संचयक के रूप में एक शब्दकोश का उपयोग कुंजी के रूप में होता है:

var customers = new List<Customer>();

var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

और समूह-स्टाइल समाधान ToLookup() का उपयोग कर रहा है:

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());

IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

माइक्रोसॉफ्ट सिस्टमइंटरैक्टिव पैकेज में डिस्टिंट का एक संस्करण है जो एक प्रमुख चयनकर्ता लैम्ब्डा लेता है। यह प्रभावी रूप से जॉन स्कीट के समाधान जैसा ही है, लेकिन लोगों के बारे में जानना और पुस्तकालय के बाकी हिस्सों की जांच करना सहायक हो सकता है।


यदि Distinct() अद्वितीय परिणाम नहीं देता है, तो इसे आजमाएं:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);

यह वही करेगा जो आप चाहते हैं लेकिन मुझे प्रदर्शन के बारे में पता नहीं है:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

कम से कम यह verbose नहीं है।


IEnumerable लैम्ब्डा एक्सटेंशन:

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

उपयोग:

class Employee
{
    public string Name { get; set; }
    public int EmployeeID { get; set; }
}

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 

इसके लिए कोई एक्सटेंशन विधि ओवरलोड नहीं है। मैंने खुद को अतीत में निराशाजनक पाया है और इस तरह मैं आमतौर पर इस समस्या से निपटने के लिए एक सहायक वर्ग लिखता हूं। लक्ष्य एक Func<T,T,bool> को IEqualityComparer<T,T> परिवर्तित करना है।

उदाहरण

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

यह आपको निम्नलिखित लिखने की अनुमति देता है

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));

यह मुझे लगता है जैसे आप MoreLINQ से DistinctBy चाहते हैं। फिर आप लिख सकते हैं:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

यहां DistinctBy का एक कट-डाउन संस्करण है (कोई शून्यता जांच नहीं है और अपना स्वयं का कुंजी तुलनाकर्ता निर्दिष्ट करने का कोई विकल्प नहीं है):

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

आप इनलाइन कॉम्पैयर का उपयोग कर सकते हैं

public class InlineComparer<T> : IEqualityComparer<T>
{
    //private readonly Func<T, T, bool> equalsMethod;
    //private readonly Func<T, int> getHashCodeMethod;
    public Func<T, T, bool> EqualsMethod { get; private set; }
    public Func<T, int> GetHashCodeMethod { get; private set; }

    public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = hashCode;
    }

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

    public int GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null) return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

उपयोग नमूना :

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
  var peticionesEV = listaLogs.Distinct(comparer).ToList();
  Assert.IsNotNull(peticionesEV);
  Assert.AreNotEqual(0, peticionesEV.Count);

स्रोत: https://.com/a/5969691/206730
संघ के लिए IEqualityComparer का उपयोग करना
क्या मैं अपना स्पष्ट प्रकार तुलनित्र इनलाइन निर्दिष्ट कर सकता हूं?


मैंने देखा है कि सभी समाधान पहले से ही तुलनात्मक क्षेत्र का चयन करने पर भरोसा करते हैं। अगर किसी को अलग तरीके से तुलना करने की ज़रूरत है, हालांकि, यहां यह समाधान आम तौर पर काम करने लगता है, जैसे कुछ:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

शॉर्टैंड समाधान

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

यहां बताया गया है कि आप इसे कैसे कर सकते हैं:

public static class Extensions
{
    public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
                                                    Func<T, V> f, 
                                                    Func<IGrouping<V,T>,T> h=null)
    {
        if (h==null) h=(x => x.First());
        return query.GroupBy(f).Select(h);
    }
}

यह विधि आपको एक पैरामीटर निर्दिष्ट करके इसका उपयोग करने की अनुमति देती है जैसे .MyDistinct(d => d.Name) , लेकिन यह आपको दूसरे पैरामीटर के रूप में एक शर्त स्थिति निर्दिष्ट करने की अनुमति देता है:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

एनबी यह आपको उदाहरण के लिए अन्य कार्यों को निर्दिष्ट करने की अनुमति देगा। .LastOrDefault(...) भी।

यदि आप केवल हालत को बेनकाब करना चाहते हैं, तो आप इसे कार्यान्वित करके इसे और भी सरल बना सकते हैं:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
                                                Func<T, V> f,
                                                Func<T,bool> h=null
                                                )
{
    if (h == null) h = (y => true);
    return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

इस मामले में, क्वेरी सिर्फ इस तरह दिखेगी:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

एनबी यहाँ, अभिव्यक्ति सरल है, लेकिन ध्यान दें। .MyDistinct2 का उपयोग करता है। .FirstOrDefault(...) निस्संदेह।

नोट: उपरोक्त उदाहरण निम्नलिखित डेमो वर्ग का उपयोग कर रहे हैं

class MyObject
{
    public string Name;
    public string Code;
}

private MyObject[] _myObject = {
    new MyObject() { Name = "Test1", Code = "T"},
    new MyObject() { Name = "Test2", Code = "Q"},
    new MyObject() { Name = "Test2", Code = "T"},
    new MyObject() { Name = "Test5", Code = "Q"}
};




extension-methods