Как сделать пересечение целочисленных списков при сохранении дубликатов?

Я работаю над заданием "Наибольший общий фактор" и "Наименьшее общее множественное", и мне нужно перечислить общие факторы. Пересечение () не будет работать, потому что это удаляет дубликаты. Contains() не будет работать, потому что, если он видит int во втором списке, он возвращает все соответствующие целые числа из первого списка. Есть ли способ сделать пересечение, которое не является отличительным?

редактировать: извините за то, что не предоставил пример, вот что я имел в виду:

если у меня есть наборы:

{1, 2, 2, 2, 3, 3, 4, 5}
{1, 1, 2, 2, 3, 3, 3, 4, 4}

Я хотел бы вывод

{1, 2, 2, 3, 3, 4}

6 ответов

Решение
ILookup<int, int> lookup1 = list1.ToLookup(i => i);
ILookup<int, int> lookup2 = list2.ToLookup(i => i);

int[] result =
(
  from group1 in lookup1
  let group2 = lookup2[group1.Key]
  where group2.Any()
  let smallerGroup = group1.Count() < group2.Count() ? group1 : group2
  from i in smallerGroup
  select i
).ToArray();

Выражение where технически необязательно, я чувствую, что оно проясняет намерение.


Если вы хотите более краткий код:

ILookup<int, int> lookup2 = list2.ToLookup(i => i);

int[] result =
(
  from group1 in list1.GroupBy(i => i)
  let group2 = lookup2[group1.Key]
  from i in (group1.Count() < group2.Count() ? group1 : group2)
  select i
).ToArray();

Я написал это расширение, чтобы решить проблему:

public static IEnumerable<T> Supersect<T>(this IEnumerable<T> a, List<T> b)
              => a.Where(t => b.Remove(t));

пример:

var a = new List<int> { 1, 2, 2, 2, 3, 3, 4, 5 };
var b = new List<int> { 1, 1, 2, 2, 3, 3, 3, 4, 4};

var result = a.Supersect(b);

результат:

{ 1, 2, 2, 3, 3, 4 }

Вы можете использовать это обобщенное расширение, которое я написал для другого ответа, это по сути один оператор Linq. Обратите внимание, что он использует Zip чтобы избежать ненужного полного перечисления совпадающих групп.

public static IEnumerable<T> Commom<T>(
        this IEnumerable<T> source,
        IEnumerable<T> sequence,
        IEqualityComparer<T> comparer = null)
{
    if (sequence == null)
    {
        return Enumerable.Empty<T>();
    }

    if (comparer == null)
    {
        comparer = EqualityComparer<T>.Default;
    }

    return source.GroupBy(t => t, comparer)
        .Join(
            sequence.GroupBy(t => t, comparer),
            g => g.Key,
            g => g.Key,
            (lg, rg) => lg.Zip(rg, (l, r) => l),
            comparer)
        .SelectMany(g => g);
}

это позволяет,

new[] {1, 2, 2, 2, 3, 3, 4, 5}.Common(
    new[] {1, 1, 2, 2, 3, 3, 3, 4, 4}).ToArray()

поддерживая порядок исходной последовательности, по желанию.

Вы ищете что-то подобное? Должно быть в значительной степени O (n + m), где n - количество элементов в first и м это количество предметов в second,

public static IEnumerable<T> Overlap<T>(this IEnumerable<T> first,
    IEnumerable<T> second, IEqualityComparer<T> comparer = null)
{
    // argument checking, optimisations etc removed for brevity

    var dict = new Dictionary<T, int>(comparer);

    foreach (T item in second)
    {
        int hits;
        dict.TryGetValue(item, out hits);
        dict[item] = hits + 1;
    }

    foreach (T item in first)
    {
        int hits;
        dict.TryGetValue(item, out hits);
        if (hits > 0)
        {
            yield return item;
            dict[item] = hits - 1;
        }
    }
}

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

IEnumerable<Foo> seqA = ...
IEnumerable<Foo> seqB = ...

var result = from aGroup in seqA.GroupBy(x => x)
             join bGroup in seqB.GroupBy(x => x) 
                         on aGroup.Key equals bGroup.Key
             let smallerGroup = aGroup.Count() < bGroup.Count() 
                                ? aGroup : bGroup
             from item in smallerGroup
             select item;
  • Найдите пересечение двух списков.
  • Сгруппируйте списки по пересекающимся элементам
  • Присоединитесь к группам и выберите Min(Count) для каждого элемента
  • Свести в новый список.

Увидеть ниже:

var intersect = list1.Intersect(list2).ToList();
var groups1 = list1.Where(e => intersect.Contains(e)).GroupBy(e => e);
var groups2 = list2.Where(e => intersect.Contains(e)).GroupBy(e => e);

var allGroups = groups1.Concat(groups2);

return allGroups.GroupBy(e => e.Key)
    .SelectMany(group => group
        .First(g => g.Count() == group.Min(g1 => g1.Count())))
    .ToList();
Другие вопросы по тегам