Каков наилучший способ проверить два списка списков на равенство в C#

есть много способов сделать это, но я чувствую, что я пропустил функцию или что-то.

очевидно List == List использовать Object.Equals() и return false.

Если каждый элемент списка равен и присутствует в одном и том же месте в противоположном списке, я бы считал их равными. Я использую типы значений, но правильно реализованный объект данных должен работать таким же образом (i.e я не ищу мелкий скопированный список, только то, что стоимостью каждого объекта внутри то же самое).

Я пробовал поиск, и есть похожие вопросы, но мой вопрос-это равенство каждого элемента в точном порядке.

6 ответов


Enumerable.SequenceEqual<TSource>

MSDN


внедрение зло

if (List1.Count == List2.Count)
{
   for(int i = 0; i < List1.Count; i++)
   {
      if(List1[i] != List2[i])
      {
         return false;
      }
   }
   return true;
}
return false;

Я собрал такой вариант:

private bool AreEqual<T>(List<T> x, List<T> y)
{
    // same list or both are null
    if (x == y)
    {
        return true;
    }

    // one is null (but not the other)
    if (x== null || y == null)
    {
        return false;
    }

    // count differs; they are not equal
    if (x.Count != y.Count)
    {
        return false;
    }

    for (int i = 0; i < x.Count; i++)
    {
        if (!x[i].Equals(y[i]))
        {
            return false;
        }
    }
    return true;
}

ботаник во мне также выполз, поэтому я сделал тест производительности против SequenceEquals, и у этого есть небольшое преимущество.

теперь вопрос: стоит ли этот крошечный, почти измеримый прирост производительности добавлять код в базу кода и поддерживать его? Я очень сомневаюсь в этом; o)


я постучал быстрый метод расширения:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static bool Matches<T>(this List<T> list1, List<T> list2)
        {
            if (list1.Count != list2.Count) return false;
            for (var i = 0; i < list1.Count; i++)
            {
                if (list1[i] != list2[i]) return false;
            }
            return true;
        }
    }   
}

можно написать общего назначения IEqualityComparer<T> для последовательностей. Простой:

public class SequenceEqualityComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        return x.SequenceEqual(y);
    }

    public int GetHashCode(IEnumerable<T> obj)
    {
        return unchecked(obj.Aggregate(397, (x, y) => x * 31 + y.GetHashCode()));
    }
}

более плотная версия: что должно быть лучше.

public class SequenceEqualityComparer<T> : EqualityComparer<IEnumerable<T>>, 
                                           IEquatable<SequenceEqualityComparer<T>>
{
    readonly IEqualityComparer<T> comparer;

    public SequenceEqualityComparer(IEqualityComparer<T> comparer = null)
    {
        this.comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public override bool Equals(IEnumerable<T> x, IEnumerable<T> y)
    {
        // safer to use ReferenceEquals as == could be overridden
        if (ReferenceEquals(x, y))
            return true;

        if (x == null || y == null)
            return false;

        var xICollection = x as ICollection<T>;
        if (xICollection != null)
        {
            var yICollection = y as ICollection<T>;
            if (yICollection != null)
            {
                if (xICollection.Count != yICollection.Count)
                    return false;

                var xIList = x as IList<T>;
                if (xIList != null)
                {
                    var yIList = y as IList<T>;
                    if (yIList != null)
                    {
                        // optimization - loops from bottom
                        for (int i = xIList.Count - 1; i >= 0; i--)
                            if (!comparer.Equals(xIList[i], yIList[i]))
                                return false;

                        return true;
                    }
                }
            }
        }

        return x.SequenceEqual(y, comparer);
    }

    public override int GetHashCode(IEnumerable<T> sequence)
    {
        unchecked
        {
            int hash = 397;
            foreach (var item in sequence)
                hash = hash * 31 + comparer.GetHashCode(item);

            return hash;
        }
    }

    public bool Equals(SequenceEqualityComparer<T> other)
    {
        if (ReferenceEquals(null, other))
            return false;

        if (ReferenceEquals(this, other))
            return true;

        return this.comparer.Equals(other.comparer);
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as SequenceEqualityComparer<T>);
    }

    public override int GetHashCode()
    {
        return comparer.GetHashCode();
    }
}

это имеет несколько особенностей:

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

  2. An IEqualityComparer<T> может быть принят в основу сравнения предметы в коллекции.


использовать в LINQ SequenceEqual чтобы проверить равенство последовательности, потому что метод Equals проверяет равенство ссылок.

bool isEqual = list1.SequenceEqual(list2);

на SequenceEqual() метод занимает секунду IEnumerable<T> последовательность в качестве параметра и выполняет сравнение, элемент-за-элементом, с целевым (первой) последовательности. Если две последовательности содержат тот же номер элементов, и каждый элемент первой последовательности равен соответствующему элементу второго последовательность (используя по умолчанию компаратор проверки на равенство) то SequenceEqual() returns true. В противном случае, false возвращается.

или если вы не заботитесь о порядке использования элементов Enumerable.All способ:

var isEqual = list1.All(list2.Contains);

вторая версия также требует другой проверки для Count, потому что она вернет true, даже если list2 содержится больше элементов, чем list1.