2016-09-27 10 views
0

以下のコードをLINQに変換する方法を教えてください。リストには20kまたは30kの項目が含まれることがあります。だから私はパフォーマンスを向上させ、より速く動くものを探しています。以下は私のコードです:別個のアイテムを取得するパフォーマンスを向上させる

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

私はParallel.ForEachを使ってみましたが、 "コレクションが変更されました"というエラーがスローされます。

+0

何 'から始めlist2'ないとのセットを更新する必要がある場合は?それは空ですか? –

+0

@JonSkeet - 値があるか、空である可能性があります。私は両方の場合が私の要件にあります。したがって、ソリューションは両方を処理できる必要があります – user

+0

'list'変数はEntity Framework呼び出しの結果ですか? – krillgar

答えて

0

list2をConcurrentBagタイプにすることができます。このようにします。私は100%それが意図したように動作することを確認していない。

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

残念ながら、この方法はOPの方法の約2倍の時間を要し、正しい結果を得られないようです(私のテストデータで) - すべての重複を排除するわけではありません。 –

1

LINQ Distinctメソッドを使用できます。それはされたIEqualityComparerを設定必要がありますが、幸いにもMSDNの例では、すでに書かれて必要なものだけ持っている:

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

     } 
    } 
} 

出力例:あなたはすでにいくつかのデータを持っていた場合

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

は、あなたがUnionメソッドを使用することができますその代わりに、再び比較関数を使用します。

N.B.私のRandomLetter()機能は私が意図したことをしません。しかしそれで十分です。

+0

ありがとうございます。 – user

1

20 - 30kの項目はあまりありません。あなたが必要とするのは、潜在的に遅い線形検索を置き換えることだけです。高速ルックアップデータ構造を使用すると、潜在的に遅い線形検索を置き換えることができます。

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

最も簡単なのはNameNumberのプロパティを含む匿名タイプのHashSetを作成することです。そのためには、次のような便利なカスタム拡張メソッドを使用することができます

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

を、問題のコードは次のようになります:

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

これはLINQではありませんが、そうでありませんLINQはクエリのためのものであり、コードは変更のためのものである必要があります。

+0

IEqualityComparerを設定してDistinctを使用するのに比べて、短くて、HashSetの設定にDistinctを使用するよりも時間がかかります(Distinctを使用するよりもはるかに遅くはありません)。 *しかし*、私のテストデータは適切な比較のために十分ではないかもしれません。 –

+0

サンプルをありがとう、私を助けるために時間をとってくれてありがとう! – user

+0

あなたとAndrewの答えをアップしましたが、OPは 'new to c#'なので、簡単な説明のためにこれを好きです。 – Neolisk

0

あなたの驚きによってグループ化し、グループから最初のレコードを選択し、リストを作成します。

最速あなたlist2は既存している場合は、(次のバージョンよりも4倍遅い)既存の値

を持っている場合は、既存の値

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

シンプルを提供する必要がない場合あなたはこのようなことをすることができます。

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

最速の既存の値

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); 
関連する問題