2016-09-27 1 views
0

Wie kann ich unter Code in LINQ konvertieren. Listen können manchmal 20k oder 30k Elemente enthalten. Ich suche nach etwas, das die Leistung verbessert und schneller läuft. Unten ist mein Code:Verbessern Sie die Leistung beim Abrufen bestimmter Artikel

 if(list1 != null) 
    { 
     foreach (var item in list1) 
     { 
     if(!list2.Any(x => x.Name == item.Name && x.Number == item.Number)) 
     { 
      list2.Add(item) 
     } 
     } 
    } 

Ich versuchte mit Parallel.ForEach, aber es wirft einen Fehler "Sammlung wurde geändert".

+0

Was bedeutet 'list2' mit beginnen zu aktualisieren? Ist es leer? –

+0

@JonSkeet - es könnte Werte haben oder es könnte leer sein. Ich habe beide Fälle in meiner Anforderung. Also muss die Lösung in der Lage sein, sowohl – user

+0

10 zu verarbeiten. Sind entweder 'Listen'-Variablen von einem Entity Framework-Aufruf? – krillgar

Antwort

0

Sie können list2 zu einem ConcurrentBag-Typ machen und dies tun. Ich bin nicht 100% sicher, dass es wie beabsichtigt funktioniert.

public class Item 
{ 
    public string Name { get; set; } 
    public int Number { get; set; } 
} 
public void test() 
{ 
    var list1 = new List<Item>(); // add items to list1 or maybe load from a database? 

    var list2 = new ConcurrentBag<Item>(); 

    Parallel.ForEach(list1.ToList(), (item, state, arg3) => 
    { 
     if (!list2.Any(x => x.Name == item.Name && x.Number == item.Number)) 
     { 
      list2.Add(item); 
     } 

    }); 

} 
+0

Leider dauert diese Methode etwa doppelt so lange wie die OP-Methode und scheint nicht das richtige Ergebnis zu erhalten (mit meinen Testdaten) - es eliminiert nicht alle Duplikate. –

1

Sie können die Methode LINQ Distinct verwenden. Es braucht ein IEqualityComparer eingerichtet, aber zum Glück des MSDN Beispiel hat genau das, was Sie schon geschrieben brauchen:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Diagnostics; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static Random rand = new Random(); 

     // see https://msdn.microsoft.com/en-us/library/bb338049(v=vs.110).aspx for Distinct() 
     public class Product 
     { 
      public string Name { get; set; } 
      public int Number { get; set; } 
     } 

     // Custom comparer for the Product class 
     class ProductComparer : IEqualityComparer<Product> 
     { 
      // Products are equal if their names and product numbers are equal. 
      public bool Equals(Product x, Product y) 
      { 

       //Check whether the compared objects reference the same data. 
       if (Object.ReferenceEquals(x, y)) return true; 

       //Check whether any of the compared objects is null. 
       if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) 
        return false; 

       //Check whether the products' properties are equal. 
       return x.Number == y.Number && x.Name == y.Name; 
      } 

      // If Equals() returns true for a pair of objects 
      // then GetHashCode() must return the same value for these objects. 

      public int GetHashCode(Product product) 
      { 
       //Check whether the object is null 
       if (Object.ReferenceEquals(product, null)) return 0; 

       //Get hash code for the Name field if it is not null. 
       int hashProductName = product.Name == null ? 0 : product.Name.GetHashCode(); 

       //Get hash code for the Code field. 
       int hashProductCode = product.Number.GetHashCode(); 

       //Calculate the hash code for the product. 
       return hashProductName^hashProductCode; 
      } 

     } 

     static string RandomLetter() 
     { 
      return (rand.Next((int)'A', (int)'Z' + 1)).ToString(); 
     } 

     static List<Product> CreateTestData() 
     { 
      int nItems = 20000; 
      List<Product> data = new List<Product>(nItems); 
      for (int i = 1; i <= nItems; i++) 
      { 
       data.Add(new Product { Name = RandomLetter() + RandomLetter(), Number = i % 10 }); 
      } 

      return data; 
     } 

     static void Main(string[] args) 
     { 
      var list1 = CreateTestData(); 
      Stopwatch sw = new Stopwatch(); 
      sw.Start(); 
      List<Product> noduplicates = list1.Distinct(new ProductComparer()).ToList(); 
      sw.Stop(); 
      Console.WriteLine($"x items: {list1.Count()} no duplicates: {noduplicates.Count()} Time: {sw.ElapsedMilliseconds} ms"); 

      List<Product> list2 = new List<Product>(); 
      if (list1 != null) 
      { 
       sw.Restart(); 
       foreach (var item in list1) 
       { 
        if (!list2.Any(x => x.Name == item.Name && x.Number == item.Number)) 
        { 
         list2.Add(item); 
        } 
       } 
       sw.Stop(); 
       Console.WriteLine($"x items: {list1.Count()} list2: {noduplicates.Count()} Time: {sw.ElapsedMilliseconds} ms"); 
      } 

      Console.ReadLine(); 

     } 
    } 
} 

Beispielausgabe:

x items: 20000 no duplicates: 6393 Time: 12 ms 
x items: 20000 list2: 6393 Time: 4225 ms 

Wenn Sie bereits einige Daten haben, können Sie die Union-Methode verwenden, stattdessen, wieder mit dem Vergleich.

N.B. Meine RandomLetter() Funktion tut nicht, was ich beabsichtigte. Aber es genügt.

+0

Vielen Dank. – user

1

20 - 30k Artikel sind nicht so sehr. Alles, was Sie brauchen, ist die langsame Suche

list2.Any(x => x.Name == item.Name && x.Number == item.Number) 

mit schneller Lookup-Datenstruktur zu ersetzen.

Am einfachsten ist es, einen HashSet mit anonymen Typ zu erstellen, der die Eigenschaften Name und Number enthält. Um das zu tun, können Sie die folgende praktische benutzerdefinierte Erweiterung-Methode verwenden:

if (list1 != null) 
{ 
    var keys = list2.Select(item => new { item.Name, item.Number }).ToHashSet(); 
    foreach (var item in list1) 
    { 
     var key = new { item.Name, item.Number }; 
     if (!keys.Contains(key)) 
     { 
      list2.Add(item); 
      keys.Add(key); 
     } 
    } 
} 

Dies ist nicht LINQ, aber es funktioniert nicht:

public static class Extensions 
{ 
    public static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T> comparer = null) 
    { 
     return new HashSet<T>(source, comparer); 
    } 
} 

und den Code in Frage wie diese wären muss sein, da LINQ zum Abfragen dient, während der Code zur Änderung dient.

+0

Das ist schön und kurz, verglichen mit der Einrichtung eines IEqualityComparer, um Distinct zu verwenden, und nimmt nur die zusätzliche Zeit in Anspruch, um das HashSet im Vergleich zur Verwendung von Distinct einzurichten (d. H. Es ist nicht viel langsamer als Distinct). * Jedoch *, meine Testdaten sind möglicherweise nicht gut genug für einen richtigen Vergleich. –

+0

Vielen Dank für die Probe und danke, dass Sie sich die Zeit genommen haben, mir zu helfen !! – user

+0

Upvoted Ihre und Andrews Antworten, aber bevorzugen dies für eine einfache Erklärung, da OP ist "neu zu C#". – Neolisk

0

Gruppieren Sie nach Ihrer Bestimmung und wählen Sie den ersten Datensatz aus der Gruppe und erstellen Sie Ihre Liste.

Schnellste, wenn Sie nicht vorhandene Werte

var list2 = list1.GroupBy(i => new { i.Name, i.Number }) 
       .Select(g=>g.First()) 
       .ToList(); 

Einfach zur Verfügung stellen müssen, wenn Sie vorhandene Werte (4-mal langsamer als die nächste Version) haben

wenn Sie list2 vorbestehenden hat Werte können Sie so etwas tun ...

var keys = list2.ToList(); 
var toadd = list1.GroupBy(i => new { i.Name, i.Number }) 
       .Where(g => !keys.Any(i => i.Name == g.Key.Name 
             && i.Number == g.Key.Number)) 
       .Select(g=>g.First()); 
list2.AddRange(toadd); 

Schnellste, wenn Sie benötigen einen Satz mit vorhandenen Werten

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items) 
{ 
    return new HashSet<T>(items); 
} 

var keys = list2.Select(i => new { i.Name, i.Number }).ToHashSet(); 
var toadd = list1.GroupBy(i => new { i.Name, i.Number }) 
       .Where(g => !keys.Contains(g.Key)) 
       .Select(g => g.First()); 
list2.AddRange(toadd); 
Verwandte Themen