DaveUK
DaveUK

Reputation: 1580

Comparing two lists of nested lists and returning the added/changed/removed items

I've looked at many similar questions on stackoverflow, but I haven't seen an exact match for my problem.

I need to compare two "lists of nested lists" and capture the differences. One is an "old" list and the other is a "new" list. When comparing the nested lists, they can be considered equal if all of the NESTED list items (the MyObject.Ids) are present in both lists in order (you can assume that the nested MyObject.Ids lists are already sorted and that there are no duplicates). The MyObject.Id and MyObject.Name properties are not considering in the equality comparison, but they are still important metadata for MyObject's which should not get lost.

I am not looking for a boolean indicator of equality. Instead I need to create three new lists which capture the differences between the old and new lists (e.g. a list of items which were Added, a list of items which were Removed, and a list of items which were present in both lists).

Below is an example of some code which does exactly what I want! What I would like to know is how to make this shorter/better/simpler (cutting out one of the for loops would be a good start). To make things trickier, please assume that you cannot make any changes to the MyObject class or use any custom Equals/IEqualityComparer etc implementations.

public class MyObject
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public List<Guid> Ids { get; set; }
}

...

// Get the list of existing objects (assume this returns some populated list)

List<MyObject> existingObjects = GetExistingObjects();

// Create a list of updated objects

List<MyObject> updatedObjects = new List<MyObject>()
{
    new MyObject()
    {
        Ids = new List<Guid>() { new Guid("48af3cb9-945a-4ab9-91e4-7ee5765e5304"), new Guid("54b5128a-cf53-436c-9d88-2ef7abd15140") }
    },

    new MyObject()
    {
        Ids = new List<Guid>() { new Guid("0485382f-8f92-4a71-9eba-09831392ceb9"), new Guid("3d8b98df-caee-41ce-b802-2f0c5f9742de") }
    }
};

// Do the comparison and capture the differences

List<MyObject> addedObjects = new List<MyObject>();
List<MyObject> removedObjects = new List<MyObject>();
List<MyObject> sameObjects = new List<MyObject>();

foreach (MyObject obj in updatedObjects)
{
    if (existingObjects.Any(list => list.Ids.SequenceEqual(obj.Ids)))
    {
        sameObjects.Add(obj);
        continue;
    }

    addedObjects.Add(obj);
}

foreach (MyObject obj in existingObjects)
{
    if (!updatedObjects.Any(list => list.Ids.SequenceEqual(obj.Ids)))
    {
        removedObjects.Add(obj);
    }
}

Upvotes: 0

Views: 3564

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205629

Here is a little shorter (due to elimination of the second loop) and little better (due to elimination of the ineffective search contained in the second loop). Still O(N^2) time complexity due to ineffective search contained in the loop though.

var addedObjects = new List<MyObject>();
var removedObjects = new List<MyObject>(existingObjects);
var sameObjects = new List<MyObject>();
foreach (var newObject in updatedObjects)
{
    int index = removedObjects.FindIndex(oldObject => oldObject.Ids.SequenceEqual(newObject.Ids));
    if (index < 0)
        addedObjects.Add(newObject);
    else
    {
        removedObjects.RemoveAt(index);
        sameObjects.Add(newObject);
    }
}

Update: A shorter, but IMO definitely not better (in fact worse performance wise) version

var addedObjects = updatedObjects.Where(newObject => !existingObjects.Any(oldObject => oldObject.Ids.SequenceEqual(newObject.Ids))).ToList();
var removedObjects = existingObjects.Where(oldObject => !updatedObjects.Any(newObject => newObject.Ids.SequenceEqual(oldObject.Ids))).ToList();
var sameObjects = updatedObjects.Where(newObject => !addedObjects.Any(addedObject => addedObject.Ids.SequenceEqual(newObject.Ids))).ToList();

If MyObject does not define custom equality comparison, i.e. uses default reference equality, the last line could be replaced with shorter and better performing

var sameObjects = updatedObjects.Except(addedObjects);

Upvotes: 2

Ygalbel
Ygalbel

Reputation: 5519

You can use Intersect and Except function in Linq
With Intersect you will get existing object,
and with Except you will get new objects.

Example of Except from MSDN:

double[] numbers1 = { 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 };
double[] numbers2 = { 2.2 };

IEnumerable<double> onlyInFirstSet = numbers1.Except(numbers2);

foreach (double number in onlyInFirstSet)
    Console.WriteLine(number);

Upvotes: 0

Related Questions