Найти все пересекающиеся данные, а не только уникальные значения - PullRequest
12 голосов
/ 02 февраля 2010

Я думал, что понял Intersect, но, оказывается, я ошибался.

 List<int> list1 = new List<int>() { 1, 2, 3, 2, 3};
 List<int> list2 = new List<int>() { 2, 3, 4, 3, 4};

 list1.Intersect(list2) =>      2,3

 //But what I want is:
 // =>  2,3,2,3,2,3,3

Я могу придумать, как:

 var intersected = list1.Intersect(list2);
 var list3 = new List<int>();
 list3.AddRange(list1.Where(I => intersected.Contains(I)));
 list3.AddRange(list2.Where(I => intersected.Contains(I)));

Есть ли в LINQ более простой способ добиться этого?

Мне нужно заявить, что мне все равно, в каком порядке выдаются результаты.

2,2,2,3,3,3,3 также будет в порядке.

Проблема в том, что я использую это в очень большой коллекции, поэтому мне нужна эффективность.

Мы говорим об объектах, а не целых. Цели были только для простого примера, но я понимаю, что это может иметь значение.

Ответы [ 4 ]

16 голосов
/ 02 февраля 2010

Посмотрим, сможем ли мы точно охарактеризовать то, что вы хотите. Поправь меня, если я ошибаюсь. Вы хотите: все элементы списка 1 по порядку, которые также появляются в списке 2, за которыми следуют все элементы списка 2 по порядку, которые также появляются в списке 1. Да?

Кажется простым.

return list1.Where(x=>list2.Contains(x))
     .Concat(list2.Where(y=>list1.Contains(y)))
     .ToList();

Обратите внимание, что не эффективно для больших списков. Если у списков есть тысяча пунктов каждый, то это делает пару миллионов сравнений. Если вы находитесь в такой ситуации, то вы хотите использовать более эффективную структуру данных для проверки членства:

list1set = new HashSet(list1);
list2set = new HashSet(list2);

return list1.Where(x=>list2set.Contains(x))
     .Concat(list2.Where(y=>list1set.Contains(y)))
     .ToList();

, который выполняет только пару тысяч сравнений, но потенциально использует больше памяти.

1 голос
/ 02 февраля 2010
var set = new HashSet(list1.Intersect(list2));
return list1.Concat(list2).Where(i=>set.Contains(i));
0 голосов
/ 04 апреля 2019

Может быть, это может помочь: https://gist.github.com/mladenb/b76bcbc4063f138289243fb06d099dda

Исходный Except / Intersect возвращает коллекцию уникальных элементов, даже если в их контракте так не указано (например, возвращаемое значение этих методов не HashSet / Set, а скорее IEnumerable), что, вероятно, является результатом плохого дизайнерского решения. Вместо этого мы можем использовать более интуитивную реализацию, которая возвращает столько же элементов из первого перечисления, сколько есть, а не только уникальный (используя Set.Contains).

Более того, была добавлена ​​функция отображения, чтобы помочь пересекать / исключать коллекции разных типов.

Если вам не нужно пересекать / кроме коллекций разных типов, просто проверьте исходный код Intersect / Except и измените часть, которая проходит через первое перечисление, чтобы использовать Set.Contains вместо Set.Add / Set .Remove.

0 голосов
/ 02 февраля 2010

Я не верю, что это возможно с помощью встроенных API. Но вы можете использовать следующее, чтобы получить результат, который вы ищете.

IEnumerable<T> Intersect2<T>(this IEnumerable<T> left, IEnumerable<T> right) {
  var map = left.ToDictionary(x => x, y => false);
  foreach ( var item in right ) {
    if (map.ContainsKey(item) ) {
      map[item] = true;
    }
  }
  foreach ( var cur in left.Concat(right) ) {
    if ( map.ContainsKey(cur) ) {
      yield return cur;
    }
  }
}
...