简体   繁体   中英

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 (eg 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);
    }
}

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, ie uses default reference equality, the last line could be replaced with shorter and better performing

var sameObjects = updatedObjects.Except(addedObjects);

You can use and 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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM