2013-09-18 4 views
2

Использование LINQ, является ли более быстрый альтернатива Where() метод с List<T>.Contains() внутри предиката, что дает точно же результаты?Есть LINQ альтернативный оператор, где оператор() с List.Contains() метод внутри

Вот пример:

List<int> a = ... 
List<int> b = ... 

var result = a.Where(x => b.Contains(x)); //very slow 

Один из вариантов я нашел заключается в использовании метода Intersect():

var result = a.Intersect(b); 

В result переменной, сохраняется a порядок величины. Однако он не дает точно таких же результатов, если значения в a содержат дубликаты, потому что оператор Intersect() возвращает только отдельные значения.

Другой способ:

var result = a.Join(b, x => x, y => y, (x, y) => x); 

результаты Опять не то же самое, если b содержит дубликаты.

Есть ли еще возможность?

То, что я хочу, чтобы избежать:

  • , чтобы создать свой собственный метод расширения LINQ
  • создать отдельный HashSet в первом списке и использовать Contains() на внутренней Where().
+2

'a.Where (х => b.Contains (а))', вероятно, следует 'a.Where (х => b.Contains (х)) ' – sloth

+0

К сожалению, это всегда будет медленным в' List ', потому что' Contains' является операцией O (n). Вы можете попробовать «HashSet », но следует предупредить, что для того, чтобы быть быстрым, вам нужно переопределить «GetHashCode» и «Equals» для типа 'T' (при условии, что' T' является некадровым типом) – Mgetz

+0

@Mgetz, это это то, что я предложил в последней части, используя «Словарь». Но вы правы, достаточно использовать «HashSet». – tigrou

ответ

3

Семантически, то, что вы хотите, это левое внутреннее соединение. Оператор LINQ Join выполняет внутреннее соединение , которое близко, но не совсем то же самое. К счастью, вы можете использовать GroupJoin для выполнения левого соединения.

var query = from n in a 
      join k in b 
      on n equals k into matches 
      where matches.Any() 
      select n; 

Другой вариант поставить элементы в вашей второй последовательности в HashSet, которая может быть гораздо более эффективно, чем искали List. (Это похоже на то, что Join/GroupJoin будет делать внутри.)

var set = new HashSet<int>(b); 
var query = a.Where(n => set.Contains(n)); 

Другой вариант заключается в использовании Join, как вы это делали, а просто удалить все дубликаты из b первых, потому что если нет дубликатов, то это делает что вы хотите:

var result = a.Join(b.Distinct(), x => x, y => y, (x, y) => x); 
+0

+1 Если в обеих коллекциях есть дубликаты, вам нужно «объединение двойной группы». Либо реализуйте это самостоятельно, либо присоедините результаты двух обычных GroupBy. – usr

0

Для более быстрого и повторного использования я бы использовал традиционное «для».

Edited
Я написал код тестирования с учетом:

  • списки с 1000 случайных целых чисел.
  • 200 тестов на каждый метод.
  • 1, 2, 4 и 8 использует результат, чтобы показать необходимость преобразования IEnumerable<int> результата LINQ в лучшую структуру данных, например List<int>, если вы используете результат много раз.

Результат таков:

1 uses per result 
Tigrou-Where  : count= 93, 3.167,0ms 
Tigrou-Intersect : count= 89, 116,0ms 
Tigrou-Join   : count= 96, 179,0ms 
Servy-GroupJoin  : count= 93, 262,0ms 
Servy-HashSet  : count= 93,  71,0ms 
Servy-JoinDisctinct : count= 93, 212,0ms 
JoseH-TheOldFor  : count= 93,  72,0ms 

2 uses per result 
Tigrou-Where  : count= 93, 6.007,0ms 
Tigrou-Intersect : count= 89, 182,0ms 
Tigrou-Join   : count= 96, 293,0ms 
Servy-GroupJoin  : count= 93, 455,0ms 
Servy-HashSet  : count= 93,  99,0ms 
Servy-JoinDisctinct : count= 93, 407,0ms 
JoseH-TheOldFor  : count= 93,  73,0ms 

4 uses per result 
Tigrou-Where  : count= 93, 11.866,0ms 
Tigrou-Intersect : count= 89, 353,0ms 
Tigrou-Join   : count= 96, 565,0ms 
Servy-GroupJoin  : count= 93, 899,0ms 
Servy-HashSet  : count= 93, 165,0ms 
Servy-JoinDisctinct : count= 93, 786,0ms 
JoseH-TheOldFor  : count= 93,  73,0ms 

8 uses per result 
Tigrou-Where  : count= 93, 23.831,0ms 
Tigrou-Intersect : count= 89, 724,0ms 
Tigrou-Join   : count= 96, 1.151,0ms 
Servy-GroupJoin  : count= 93, 1.807,0ms 
Servy-HashSet  : count= 93, 299,0ms 
Servy-JoinDisctinct : count= 93, 1.570,0ms 
JoseH-TheOldFor  : count= 93,  81,0ms 

И код:

class Program 
{ 
    static void Main(string[] args) 
    { 
     Random random = new Random(Environment.TickCount); 
     var cases = 1000; 
     List<int> a = new List<int>(cases); 
     List<int> b = new List<int>(cases); 
     for (int c = 0; c < cases; c++) 
     { 
      a.Add(random.Next(9999)); 
      b.Add(random.Next(9999)); 
     } 

     var times = 100; 
     var usesCount = 1; 

     Console.WriteLine("{0} times", times); 
     for (int u = 0; u < 4; u++) 
     { 
      Console.WriteLine(); 
      Console.WriteLine("{0} uses per result", usesCount); 
      TestMethod(a, b, "Tigrou-Where", Where, times, usesCount); 
      TestMethod(a, b, "Tigrou-Intersect", Intersect, times, usesCount); 
      TestMethod(a, b, "Tigrou-Join", Join, times, usesCount); 
      TestMethod(a, b, "Servy-GroupJoin", GroupJoin, times, usesCount); 
      TestMethod(a, b, "Servy-HashSet", HashSet, times, usesCount); 
      TestMethod(a, b, "Servy-JoinDisctinct", JoinDistinct, times, usesCount); 
      TestMethod(a, b, "JoseH-TheOldFor", TheOldFor, times, usesCount); 
      usesCount *= 2; 
     } 

     Console.ReadLine(); 
    } 

    private static void TestMethod(List<int> a, List<int> b, string name, Func<List<int>, List<int>, IEnumerable<int>> method, int times, int usesCount) 
    { 
     var stopwatch = new Stopwatch(); 
     stopwatch.Start(); 
     int count = 0; 
     for (int t = 0; t < times; t++) 
     { 
      // Process 
      var result = method(a, b); 
      // Count 
      for (int u = 0; u < usesCount; u++) 
      { 
       count = 0; 
       foreach (var item in result) 
       { 
        count++; 
       } 
      } 
     } 
     stopwatch.Stop(); 
     Console.WriteLine("{0,-20}: count={1,4}, {2,8:N1}ms", 
      name, count, stopwatch.ElapsedMilliseconds); 
    } 

    private static IEnumerable<int> Where(List<int> a, List<int> b) 
    { 
     return a.Where(x => b.Contains(x)); 
    } 

    private static IEnumerable<int> Intersect(List<int> a, List<int> b) 
    { 
     return a.Intersect(b); 
    } 

    private static IEnumerable<int> Join(List<int> a, List<int> b) 
    { 
     return a.Join(b, x => x, y => y, (x, y) => x); 
    } 

    private static IEnumerable<int> GroupJoin(List<int> a, List<int> b) 
    { 
     return from n in a 
       join k in b 
       on n equals k into matches 
       where matches.Any() 
       select n; 
    } 

    private static IEnumerable<int> HashSet(List<int> a, List<int> b) 
    { 
     var set = new HashSet<int>(b); 
     return a.Where(n => set.Contains(n)); 
    } 

    private static IEnumerable<int> JoinDistinct(List<int> a, List<int> b) 
    { 
     return a.Join(b.Distinct(), x => x, y => y, (x, y) => x); 
    } 

    private static IEnumerable<int> TheOldFor(List<int> a, List<int> b) 
    { 
     var result = new List<int>(); 
     int countA = a.Count; 
     var setB = new HashSet<int>(b); 
     for (int loopA = 0; loopA < countA; loopA++) 
     { 
      var itemA = a[loopA]; 
      if (setB.Contains(itemA)) 
       result.Add(itemA); 
     } 
     return result; 
    } 
} 

Изменение строки в коде, чтобы преобразовать результат в List<int> перед использованием его бросили их на 8 использований:

8 uses per result 
Tigrou-Where  : count= 97, 2.974,0ms 
Tigrou-Intersect : count= 91,  91,0ms 
Tigrou-Join   : count= 105, 150,0ms 
Servy-GroupJoin  : count= 97, 224,0ms 
Servy-HashSet  : count= 97,  74,0ms 
Servy-JoinDisctinct : count= 97, 223,0ms 
JoseH-TheOldFor  : count= 97,  75,0ms 

Итак, я думаю, что победитель : Метод Servy-HashSet с небольшим количеством варианта:

var set = new HashSet<int>(b); 
var result = a.Where(n => set.Contains(n)).ToList(); 
+0

И как, используя цикл 'for', вы планируете это сделать? Если вы планируете иметь два вложенных цикла, то ваша производительность идет по порядку его первого решения, а именно - плохого, потому что это алгоритм изначально плохой. – Servy

+0

У вас есть доказательства, что это быстрее? – RvdK

+2

LINQ в основном с постоянным коэффициентом медленнее, чем петлевые алгоритмы. Замедление может быть значительным, но оно не меняет асимптотической сложности. – usr

Смежные вопросы