Найти все пересекающиеся данные, а не только уникальные значения

Я думал, что понял 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 также будет отлично.

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

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

Ответ 1

Посмотрим, можем ли мы точно охарактеризовать то, что вы хотите. Поправьте меня, если я ошибаюсь. Вы хотите: все элементы списка 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();

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

Ответ 2

var set = new HashSet(list1.Intersect(list2));
return list1.Concat(list2).Where(i=>set.Contains(i));

Ответ 3

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

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

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

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

Ответ 4

Я не считаю, что это возможно со встроенными 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;
    }
  }
}