В C# более эффективный способ сравнения двух коллекций

У меня две коллекции

List<Car> currentCars = GetCurrentCars();
List<Car> newCars = GetNewCars();

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

Я ищу более эффективный способ сравнить эти коллекции и получить результаты:

  1. список автомобилей, которые находятся в newCars, а не в currentCars
  2. список автомобилей, которые не находятся в newCars и в currentCars

тип автомобиля имеет свойство int Идентификатор.

был ответ, который уже удален, говоря Что я имею в виду, говоря эффективно: меньше кода, меньше механики и более читаемые случаи

Итак, думая таким образом, какие у меня есть случаи?

что было бы меньше кода, меньше механики и более читаемых случаев?

8 ответов


можно использовать Except:

var currentCarsNotInNewCars = currentCars.Except(newCars);
var newCarsNotInCurrentCars = newCars.Except(currentCars);

но это не имеет никакого преимущества над foreach решение. Просто выглядит чище.
Кроме того, имейте в виду тот факт, что вам нужно реализовать IEquatable<T> для Car класс, поэтому сравнение выполняется по идентификатору, а не по ссылке.

Performancewise, лучшим подходом было бы не использовать List<T> но Dictionary<TKey, TValue> С ID в качестве ключа:

var currentCarsDictionary = currentCars.ToDictionary(x => x.ID);
var newCarsDictionary = newCars.ToDictionary(x => x.ID);

var currentCarsNotInNewCars = 
    currentCarsDictionary.Where(x => !newCarsDictionary.ContainsKey(x.Key))
                         .Select(x => x.Value);

var newCarsNotInCurrentCars = 
    newCarsDictionary.Where(x => !currentCarsDictionary.ContainsKey(x.Key))
                     .Select(x => x.Value);

вы можете сделать это так:

// 1) List of cars in newCars and not in currentCars
var newButNotCurrentCars = newCars.Except(currentCars);

// 2) List of cars in currentCars and not in newCars
var currentButNotNewCars = currentCars.Except(newCars);

код использует перечисли.Кроме метод расширения (доступен в .Net 3.5 и более).

Я считаю, что это соответствует вашим критериям "меньше кода, меньше механики и более читаемый".


Если вы начнете с них в HashSets Вы можете использовать Except метод.

HashSet<Car> currentCars = GetCurrentCars();
HashSet<Car> newCars = GetNewCars();

currentCars.Except(newCars);
newCars.Except(currentCars);

Это было бы намного быстрее с набором, чем список. (Под капотом список просто делает foreach, наборы могут быть оптимизированы).


вы можете использовать LINQ...

        List<Car> currentCars = new List<Car>();
        List<Car> newCars = new List<Car>();

        List<Car> currentButNotNew = currentCars.Where(c => !newCars.Contains(c)).ToList();
        List<Car> newButNotCurrent = newCars.Where(c => !currentCars.Contains(c)).ToList();

...но не обманывайтесь. Это может быть меньше кода для вас, но там определенно будут некоторые для циклов где-то

EDIT: не понял, что есть метод Except: (


Я бы переопределить Equals на Car для сравнения по id, а затем вы можете использовать IEnumerable.Except метод расширения. Если вы не можете переопределить Equals вы можете создать свой собственный IEqualityComparer<Car> который сравнивает два автомобиля по id.

class CarComparer : IEqualityComparer<Car>
{
    public bool Equals(Car x, Car y)
    {
        return x != null && y != null && x.Id == y.Id;
    }

    public int GetHashCode(Car obj)
    {
        return obj == null ? 0 : obj.Id;
    }
}

Если вы ищете эффективность, реализуйте IComparable на автомобилях (сортировка по вашему уникальному идентификатору) и используйте SortedList. Затем вы можете пройти через свои коллекции вместе и оценить свои чеки в O(n). Это, конечно, поставляется с дополнительной стоимостью для списка вставок для поддержания отсортированного характера.


вы можете скопировать меньший список в коллекцию на основе хэш-таблицы, такую как HashSet или Dictionary, а затем повторить второй список и проверить, существует ли элемент в хэш-таблице.

Это уменьшит время от O(N^2) в наивном foreach внутри случая foreach до O (N).

Это лучшее, что вы можете сделать, не зная больше о списках (вы можете сделать мало лучше, если списки отсортированы, например, но, так как вы должны " коснуться" каждый автомобиль хотя бы один раз, чтобы проверить, есть ли он в новом списке автомобилей, вы никогда не сможете сделать лучше, чем O(N))


если сравнение свойства Id будет достаточно, чтобы сказать, если автомобиль равен другому, чтобы избежать какого-то цикла, вы можете переопределить список своим собственным классом, который отслеживает элементы и использует IEqualityComparer по всей коллекции, вот так:

class CarComparer : IList<Car>, IEquatable<CarComparer>
{
    public bool Equals(CarComparer other)
    {
        return object.Equals(GetHashCode(),other.GetHashCode());
    }

    public override int GetHashCode()
    {
        return _runningHash;
    }

    public void Insert(int index, Car item)
    {
        // Update _runningHash here
        throw new NotImplementedException();
    }

    public void RemoveAt(int index)
    {
        // Update _runningHash here
        throw new NotImplementedException();
    }

    // More IList<Car> Overrides ....
}

тогда вам просто нужно переопределить Add, Remove и т. д. и любые другие методы, которые могут повлиять на элементы в списке. Затем вы можете сохранить закрытую переменную, которая является хэшем какой-то Идентификаторы элементов в списке. Когда переопределение Equals методы вы можете просто сравнить эту частную переменную. Не самый чистый подход (поскольку вы должны идти в ногу со своей хэш-переменной), но это приведет к тому, что вам не придется делать цикл для сравнения. Если бы это был я,Я бы просто использовал Linq, как некоторые упоминали здесь...