Найти все пересекающиеся данные, а не только уникальные значения
Я думал, что понял 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;
}
}
}