2009-10-30 5 views
23

से मेल नहीं खाता है, मैं एक संग्रह में सभी आइटम ढूंढना चाहता हूं जो किसी अन्य संग्रह से मेल नहीं खाते हैं। संग्रह एक ही प्रकार के नहीं हैं, यद्यपि; मैं समानता निर्दिष्ट करने के लिए एक लैम्ब्डा अभिव्यक्ति लिखना चाहता हूं।किसी संग्रह में आइटम्स को खोजने के लिए LINQ से ऑब्जेक्ट का उपयोग करना जो

मैं क्या कर रहा हूँ का एक LINQPad उदाहरण:

void Main() 
{ 
    var employees = new[] 
    { 
     new Employee { Id = 20, Name = "Bob" }, 
     new Employee { Id = 10, Name = "Bill" }, 
     new Employee { Id = 30, Name = "Frank" } 
    }; 

    var managers = new[] 
    { 
     new Manager { EmployeeId = 20 }, 
     new Manager { EmployeeId = 30 } 
    }; 

    var nonManagers = 
    from employee in employees 
    where !(managers.Any(x => x.EmployeeId == employee.Id)) 
    select employee; 

    nonManagers.Dump(); 

    // Based on cdonner's answer: 

    var nonManagers2 = 
    from employee in employees 
    join manager in managers 
     on employee.Id equals manager.EmployeeId 
    into tempManagers 
    from manager in tempManagers.DefaultIfEmpty() 
    where manager == null 
    select employee; 

    nonManagers2.Dump(); 

    // Based on Richard Hein's answer: 

    var nonManagers3 = 
    employees.Except(
     from employee in employees 
     join manager in managers 
      on employee.Id equals manager.EmployeeId 
     select employee); 

    nonManagers3.Dump(); 
} 

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

public class Manager 
{ 
    public int EmployeeId { get; set; } 
} 

ऊपर काम करता है, और वापस आ जाएगी कर्मचारी विधेयक (# 10)। हालांकि, यह सुरुचिपूर्ण प्रतीत नहीं होता है, और यह बड़े संग्रह के साथ अक्षम हो सकता है। एसक्यूएल में मैं शायद एक बाएं जॉइन करूँगा और उन वस्तुओं को ढूंढूंगा जहां दूसरी आईडी नल थी। LINQ में ऐसा करने के लिए सबसे अच्छा अभ्यास क्या है?

संपादित करें: इंडेक्स के बराबर आईडी पर निर्भर समाधानों को रोकने के लिए अपडेट किया गया।

संपादित करें: जोड़ा गया कोडरनर का समाधान - किसी के पास कुछ भी आसान है?

संपादित करें: रिचर्ड हेन के उत्तर, मेरे वर्तमान पसंदीदा पर एक संस्करण जोड़ा गया। कुछ उत्कृष्ट उत्तरों के लिए सभी को धन्यवाद!

उत्तर

30

यह लगभग कुछ अन्य उदाहरण लेकिन कम कोड के रूप में ही है => m.EmployeeId == e.Id)) या आपका मूल वाक्यविन्यास, हालांकि।

+0

असल में मुझे अन्य समाधानों से बेहतर पसंद है - मुझे इसका अर्थ स्पष्ट लगता है। मैं व्यक्तिगत वरीयता से क्वेरी सिंटैक्स में शामिल होने के लिए पुनः लिखता हूं (मेरे प्रश्न में संशोधित नमूना कोड देखें)। धन्यवाद! – TrueWill

+0

जब एक बड़ा संग्रह शामिल होता है, सिवाय इसके कि बहुत धीमा है। शामिल उत्तर सबसे अच्छा है। –

5
/// <summary> 
    /// This method returns items in a set that are not in 
    /// another set of a different type 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    /// <typeparam name="TOther"></typeparam> 
    /// <typeparam name="TKey"></typeparam> 
    /// <param name="items"></param> 
    /// <param name="other"></param> 
    /// <param name="getItemKey"></param> 
    /// <param name="getOtherKey"></param> 
    /// <returns></returns> 
    public static IEnumerable<T> Except<T, TOther, TKey>(
              this IEnumerable<T> items, 
              IEnumerable<TOther> other, 
              Func<T, TKey> getItemKey, 
              Func<TOther, TKey> getOtherKey) 
    { 
     return from item in items 
       join otherItem in other on getItemKey(item) 
       equals getOtherKey(otherItem) into tempItems 
       from temp in tempItems.DefaultIfEmpty() 
       where ReferenceEquals(null, temp) || temp.Equals(default(TOther)) 
       select item; 
    } 

मुझे याद नहीं है कि मुझे यह तरीका कहां मिला।

+0

+1:

तो आपकी समस्या आपके Employee सुपर क्लास पर IEquatable इंटरफेस को लागू करने के रूप में सरल है (GetHashCode के लिए बस EmployeeID वापसी) और फिर इस कोड का उपयोग। मैंने इसे थोड़ा संशोधित किया और इसे मेरे प्रश्न में शामिल किया। मैं देखना चाहता हूं कि दूसरों के साथ क्या आना है। धन्यवाद! – TrueWill

2

एक्सेप्ट() LINQ फ़ंक्शन पर एक नज़र डालें। यह वही करता है जो आपको चाहिए।

employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e)); 

यह किसी भी सरल employees.Where से (ई => managers.Any (एम नहीं है:

+0

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

3
var nonmanagers = employees.Select(e => e.Id) 
    .Except(managers.Select(m => m.EmployeeId)) 
    .Select(id => employees.Single(e => e.Id == id)); 
+1

कोई गारंटी नहीं है कि कर्मचारी आईडी सरणी में कर्मचारी इंडेक्स से मेल खाएगा ... –

+0

अच्छा विचार - मैंने आईडी का चयन करने के बारे में नहीं सोचा था ताकि डिफ़ॉल्ट समानता तुलनाकर्ता के अलावा पूर्णांक की तुलना की जाए। हालांकि श्री लेवेस्क सही है, और मैंने इसे प्रतिबिंबित करने के लिए उदाहरण अपडेट किया है। क्या आप एक उदाहरण प्रदान कर सकते हैं जो कर्मचारियों को सही ढंग से लौटाता है? – TrueWill

+0

आह आप सही हैं। उत्तर अपडेट किया गया है। –

5

 
     var nonManagers = (from e1 in employees 
          select e1).Except(
            from m in managers 
            from e2 in employees 
            where m.EmployeeId == e2.Id 
            select e2); 

+0

+1। सुरुचिपूर्ण और सही ढंग से काम करता है। – TrueWill

+1

धन्यवाद। मूल रूप से इसे यहां मिला: http://rsanidad.wordpress.com/2007/10/16/linq-except-and-intersect/ –

3

यह थोड़ा देर हो चुकी है (मुझे पता है)।

मैं एक ही समस्या को देख रहा था, और उस दिशा इंक में विभिन्न प्रदर्शन संकेतों के कारण हैशसेट पर विचार कर रहा था। @ स्कीट के Intersection of multiple lists with IEnumerable.Intersect() - और मेरे कार्यालय के आसपास पूछा और आम सहमति थी एक HashSet तेजी से और अधिक से पढ़े जा सकेंगे कि:

HashSet<int> managerIds = new HashSet<int>(managers.Select(x => x.EmployeeId)); 
nonManagers4 = employees.Where(x => !managerIds.Contains(x.Id)).ToList(); 

तो मैं एक भी तेजी से समाधान देशी सरणियों का उपयोग कर एक सा मुखौटा-ish प्रकार समाधान बनाने के लिए प्रस्ताव दिया गया था (मूल सरणी प्रश्नों पर सिंटैक्स मुझे चरम प्रदर्शन कारणों को छोड़कर उन्हें उपयोग कर देगा)।

ताकि आप तुलना कर सकते हैं, जिसे अब छह विकल्प हैं इस जवाब एक भयंकर लंबे समय मैं समय के साथ अपने LINQPad प्रोग्राम और डेटा का विस्तार किया है के बाद एक छोटे से बल देने के लिए:

void Main() 
{ 
    var employees = new[] 
    { 
     new Employee { Id = 20, Name = "Bob" }, 
     new Employee { Id = 10, Name = "Kirk NM" }, 
     new Employee { Id = 48, Name = "Rick NM" }, 
     new Employee { Id = 42, Name = "Dick" }, 
     new Employee { Id = 43, Name = "Harry" }, 
     new Employee { Id = 44, Name = "Joe" }, 
     new Employee { Id = 45, Name = "Steve NM" }, 
     new Employee { Id = 46, Name = "Jim NM" }, 
     new Employee { Id = 30, Name = "Frank"}, 
     new Employee { Id = 47, Name = "Dave NM" }, 
     new Employee { Id = 49, Name = "Alex NM" }, 
     new Employee { Id = 50, Name = "Phil NM" }, 
     new Employee { Id = 51, Name = "Ed NM" }, 
     new Employee { Id = 52, Name = "Ollie NM" }, 
     new Employee { Id = 41, Name = "Bill" }, 
     new Employee { Id = 53, Name = "John NM" }, 
     new Employee { Id = 54, Name = "Simon NM" } 
    }; 

    var managers = new[] 
    { 
     new Manager { EmployeeId = 20 }, 
     new Manager { EmployeeId = 30 }, 
     new Manager { EmployeeId = 41 }, 
     new Manager { EmployeeId = 42 }, 
     new Manager { EmployeeId = 43 }, 
     new Manager { EmployeeId = 44 } 
    }; 

    System.Diagnostics.Stopwatch watch1 = new System.Diagnostics.Stopwatch(); 

    int max = 1000000; 

    watch1.Start(); 
    List<Employee> nonManagers1 = new List<Employee>(); 
    foreach (var item in Enumerable.Range(1,max)) 
    { 
     nonManagers1 = (from employee in employees where !(managers.Any(x => x.EmployeeId == employee.Id)) select employee).ToList(); 

    } 
    nonManagers1.Dump(); 
    watch1.Stop(); 
    Console.WriteLine("Any: " + watch1.ElapsedMilliseconds); 

    watch1.Restart();  
    List<Employee> nonManagers2 = new List<Employee>(); 
    foreach (var item in Enumerable.Range(1,max)) 
    { 
     nonManagers2 = 
     (from employee in employees 
     join manager in managers 
      on employee.Id equals manager.EmployeeId 
     into tempManagers 
     from manager in tempManagers.DefaultIfEmpty() 
     where manager == null 
     select employee).ToList(); 
    } 
    nonManagers2.Dump(); 
    watch1.Stop(); 
    Console.WriteLine("temp table: " + watch1.ElapsedMilliseconds); 

    watch1.Restart();  
    List<Employee> nonManagers3 = new List<Employee>(); 
    foreach (var item in Enumerable.Range(1,max)) 
    { 
     nonManagers3 = employees.Except(employees.Join(managers, e => e.Id, m => m.EmployeeId, (e, m) => e)).ToList(); 
    } 
    nonManagers3.Dump(); 
    watch1.Stop(); 
    Console.WriteLine("Except: " + watch1.ElapsedMilliseconds); 

    watch1.Restart();  
    List<Employee> nonManagers4 = new List<Employee>(); 
    foreach (var item in Enumerable.Range(1,max)) 
    { 
     HashSet<int> managerIds = new HashSet<int>(managers.Select(x => x.EmployeeId)); 
     nonManagers4 = employees.Where(x => !managerIds.Contains(x.Id)).ToList(); 

    } 
    nonManagers4.Dump(); 
    watch1.Stop(); 
    Console.WriteLine("HashSet: " + watch1.ElapsedMilliseconds); 

     watch1.Restart(); 
     List<Employee> nonManagers5 = new List<Employee>(); 
     foreach (var item in Enumerable.Range(1, max)) 
     { 
        bool[] test = new bool[managers.Max(x => x.EmployeeId) + 1]; 
        foreach (var manager in managers) 
        { 
         test[manager.EmployeeId] = true; 
        } 

        nonManagers5 = employees.Where(x => x.Id > test.Length - 1 || !test[x.Id]).ToList(); 


     } 
     nonManagers5.Dump(); 
     watch1.Stop(); 
     Console.WriteLine("Native array call: " + watch1.ElapsedMilliseconds); 

     watch1.Restart(); 
     List<Employee> nonManagers6 = new List<Employee>(); 
     foreach (var item in Enumerable.Range(1, max)) 
     { 
        bool[] test = new bool[managers.Max(x => x.EmployeeId) + 1]; 
        foreach (var manager in managers) 
        { 
         test[manager.EmployeeId] = true; 
        } 

        nonManagers6 = employees.Where(x => x.Id > test.Length - 1 || !test[x.Id]).ToList(); 
     } 
     nonManagers6.Dump(); 
     watch1.Stop(); 
     Console.WriteLine("Native array call 2: " + watch1.ElapsedMilliseconds); 
} 

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

public class Manager 
{ 
    public int EmployeeId { get; set; } 
} 
+0

अच्छा डेटा! धन्यवाद! – TrueWill

+0

यदि आपके कर्मचारियों और प्रबंधकों की आईडी बहुत अधिक हैं, तो कहें, 100,000 में, आपके स्पैस सरणी समाधान रॉयली बारफ पर जा रहे हैं। ऐसा कुछ भी नहीं है जो कहता है कि आईडी उच्च नहीं हो सकती - वे चींटियां हैं, और मुझे लगता है कि कोड लिखना बेहतर है जिसमें अजीब किनारे के मामले नहीं हैं। – ErikE

+0

@ErikE मुझे यकीन नहीं है कि आप क्या चल रहे हैं। ओपी ने प्रश्न के हिस्से के रूप में डेटा प्रदान किया और मैंने उस डेटा को संसाधित करने के 6 वैकल्पिक तरीकों का समय दिया है। यदि डेटा अलग था तो एक अलग विकल्प अधिक अनुकूलित हो सकता है। क्या कोई ऐसा उत्तर है जो हर कल्पनीय डेटा सेट के साथ सबसे अच्छा काम करेगा? यदि वहां है तो मैं वास्तव में इसकी सराहना करता हूं अगर आपने इसे बाहर रखा ताकि मैं भविष्य में इसका उपयोग कर सकूं। – amelvin

1

इसके बेहतर अगर तुम में शामिल होने के लिए छोड़ दिया आइटम और फिल्टर शून्य स्थिति

var finalcertificates = (from globCert in resultCertificate 
             join toExcludeCert in certificatesToExclude 
              on globCert.CertificateId equals toExcludeCert.CertificateId into certs 
             from toExcludeCert in certs.DefaultIfEmpty() 
             where toExcludeCert == null 
             select globCert).Union(currentCertificate).Distinct().OrderBy(cert => cert.CertificateName); 
0

प्रबंधक भी कर्मचारी हैं! तो Manager वर्ग को Employee कक्षा से उप-वर्ग होना चाहिए (या, यदि आपको यह पसंद नहीं है, तो उन्हें दोनों को मूल वर्ग से उप-वर्ग होना चाहिए, या NonManager कक्षा बनाना चाहिए)।अच्छा -

var nonManagerEmployees = employeeList.Except(managerList); 
+0

अच्छे अंक; हालांकि यह सिर्फ एक स्वच्छता उदाहरण था। गैर-मैचों को ढूंढने की सामान्य समस्या हल करने के लिए एक अच्छा है। – TrueWill

+0

हालांकि, यह कई सामान्य समस्याओं का एक अच्छा समाधान हो सकता है! यदि दो अलग-अलग वस्तुओं को किसी तरह से विलय किया जा सकता है, तो यह संभव है कि वे एक रिश्ते साझा करें जिसे सुपरक्लास/सबक्लास के माध्यम से व्यक्त किया जा सके। इस मामले में, एक प्रबंधक के पास कर्मचारी के साथ "एक-एक" संबंध होता है, इसलिए यह विरासत का उपयोग करने के लिए सही अर्थ बनाता है। "हैस-ए" रिश्तों को मेरे सुझाए गए समाधान के लिए अतिसंवेदनशील होने की संभावना कम है (लेकिन यह जरूरी नहीं है, क्योंकि जीवनशैली और भूमिकाएं आदर्श रूप से मॉडल के लिए मुश्किल हो सकती हैं और डेवलपर्स कभी-कभी "एक-" संबंधों को याद कर सकते हैं)। – ErikE