.NET проверяет, имеют ли два IEnumerable <T> те же элементы

Возможный дубликат:
Сравнение двух коллекций для равенства

Мне нужно проверить, имеют ли два IEnumerable<T> списки одинаковые элементы, не обязательно в том же порядке.

Я нацелен на .NET 3.5.

Вот тесты. Вопрос в том, как реализовать HasSameElements()?

var l1 = new[]{1,2,3};
var l2 = new[]{3,1,2};

bool rez1 = l1.HasSameElements(l2);//should be true

var l3 = new[]{1,2,3,2};
var l4 = new[]{3,1,2,2};
bool rez2 = l3.HasSameElements(l4);//should be true

var l5 = new[]{1,2,3,2};
var l6 = new[]{1,2,3};
bool rez3 = l5.HasSameElements(l6);//should be false

Дополнительные примечания:

  • В примерах я использую IEnumerable, но T может быть чем угодно. Должен ли T обязательно реализовать IComparable?

  • Enumerable.SequenceEquals() сам по себе не работает, он ожидает того же порядка для элементов.

  • Здесь шаблон для HasElements:

[только какой-то текст-заполнитель в качестве обходного пути для отформатирования кода Markdown]

public static class Extensions {
    public static bool HasElements(this IEnumerable<T> l1, IEnumerable<T> l2){
        throw new NotImplementedException();
    } 
}

Ответ 1

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

Здесь:

static class EnumerableExtensions {
    public static bool HasSameElementsAs<T>(
        this IEnumerable<T> first,
        IEnumerable<T> second
    ) {
        var firstMap = first
            .GroupBy(x => x)
            .ToDictionary(x => x.Key, x => x.Count());
        var secondMap = second
            .GroupBy(x => x)
            .ToDictionary(x => x.Key, x => x.Count());
        return 
            firstMap.Keys.All(x =>
                secondMap.Keys.Contains(x) && firstMap[x] == secondMap[x]
            ) &&
            secondMap.Keys.All(x =>
                firstMap.Keys.Contains(x) && secondMap[x] == firstMap[x]
            );
    }
}

Очевидно, повторный код может быть реорганизован в вспомогательные методы, но это просто путается с идеей здесь. Вы можете получить фантазию и принять IEqualityComparer для операции GroupBy. Кроме того, вы должны создать код, добавив null охранники, а что нет.

Ответ 2

Несмотря на то, что метод Cristi Except более эффективен, вы могли бы избежать:

source.Sort().SequenceEqual(target.Sort());

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

Ответ 3

Использование:

 return  l2.Count() == l1.Count() && l1.Intersect(l2).Count() == l2.Count();

Вы также можете передать пользовательский сопоставитель.

public static class Extensions
{
    public static bool HasElements(
        this IEnumerable<T> l1, 
        IEnumerable<T> l2,
        IEqualityComparer<T> comparaer)
    {
        l2.Count() == l1.Count() && 
        return l1.Intersect(l2, comparer).Count() == l2.Count();
    } 
}

Ответ 4

Я бы сделал что-то вроде этого

 public static bool HasSameElements<T>(this IEnumerable<T> source, IEnumerable<T> target)
 {
     return (source.Count() == target.Count() && source.All(a => target.Contains(a)));
 }

Ответ 5

Поскольку входы могут содержать дубликаты, вы не можете использовать Except. Один алгоритм:

if the lists contain a different number of elements return false

make a copy of listA
foreach element in listB 
  if the element exists in copyA remove the first match from copyA
  otherwise return false

Для реализации вы можете посмотреть на логику метода ShouldBeEqualTo() в FluentAssert.