LINQ пересекаются, несколько списков, некоторые пустые

Я пытаюсь найти пересечение с LINQ.

Пример:

List<int> int1 = new List<int>() { 1,2 };
List<int> int2 = new List<int>();
List<int> int3 = new List<int>() { 1 };
List<int> int4 = new List<int>() { 1, 2 };
List<int> int5 = new List<int>() { 1 };

Хотите вернуть: 1, поскольку он существует во всех списках. Если я запустил:

var intResult= int1
            .Intersect(int2)
            .Intersect(int3)
            .Intersect(int4)
            .Intersect(int5).ToList();

Он ничего не возвращает, поскольку 1 явно не входит в список int2. Как я могу заставить это работать независимо от того, пуст ли один список или нет?

Используйте приведенный выше пример или:

List<int> int1 = new List<int>() { 1,2 };
List<int> int2 = new List<int>();
List<int> int3 = new List<int>();
List<int> int4 = new List<int>();
List<int> int5 = new List<int>();

Как мне вернуть 1 и 2 в этом случае. Я не знаю заранее, если списки заполнены...

Ответ 1

Если вам это нужно за один шаг, самым простым решением является отфильтровать пустые списки:

public static IEnumerable<T> IntersectNonEmpty<T>(this IEnumerable<IEnumerable<T>> lists)
{
    var nonEmptyLists = lists.Where(l => l.Any());
    return nonEmptyLists.Aggregate((l1, l2) => l1.Intersect(l2));
}

Затем вы можете использовать его в коллекции списков или других IEnumerable s:

IEnumerable<int>[] lists = new[] { l1, l2, l3, l4, l5 };
var intersect = lists.IntersectNonEmpty();

Вы можете выбрать обычный статический метод:

public static IEnumerable<T> IntersectNonEmpty<T>(params IEnumerable<T>[] lists)
{
    return lists.IntersectNonEmpty();
}

var intersect = ListsExtensionMethods.IntersectNonEmpty(l1, l2, l3, l4, l5);

Ответ 2

Вы можете написать метод расширения для определения этого поведения. Что-то вроде

static class MyExtensions
{
    public static IEnumerable<T> IntersectAllIfEmpty<T>(this IEnumerable<T> list, IEnumerable<T> other)
    {
        if (other.Any())
            return list.Intersect(other);
        else
            return list;
    }
}

Итак, код ниже будет печатать 1.

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

foreach (int i in list1.IntersectAllIfEmpty(list2).IntersectAllIfEmpty(list3))
    Console.WriteLine(i);

Обновление:

Анон поднимает хороший момент в комментариях к вопросу. Вышеприведенная функция приведет к пустующему набору, если list сам пуст, что должно быть желательно. Это означает, что если первый список в цепочке методов или результирующий набор любого пересечения пуст, конечный результат будет пустым.

Чтобы разрешить пустой первый список, но не для пустых наборов результатов, вы можете использовать другой подход. Это метод, который не является методом расширения, а скорее принимает массив params из IEnumerables и сначала отфильтровывает пустые множества, а затем пытается пересечь остальные.

public static IEnumerable<T> IntersectAllIfEmpty<T>(params IEnumerable<T>[] lists)
{
    IEnumerable<T> results = null;

    lists = lists.Where(l => l.Any()).ToArray();

    if (lists.Length > 0)
    {
        results = lists[0];

        for (int i = 1; i < lists.Length; i++)
            results = results.Intersect(lists[i]);
    }
    else
    {
        results = new T[0];
    }

    return results;
}

Вы бы использовали его как это

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

foreach (int i in IntersectAllIfEmpty(list0, list1, list2, list3))
{
    Console.WriteLine(i);
}