比较嵌套列表的两个列表,并返回添加/更改/删除的项

本文关键字:列表 添加 返回 删除 更改 两个 嵌套 比较 | 更新日期: 2023-09-27 18:25:08

我在stackoverflow上看到了许多类似的问题,但我还没有看到与我的问题完全匹配的问题。

我需要比较两个"嵌套列表列表",并捕捉差异。一个是一个"旧"列表,另一个是"新"列表。当比较嵌套列表时,如果所有nested列表项(MyObject.Ids)都按顺序出现在两个列表中,则可以认为它们是相等的(您可以假设嵌套的MyObject.Id列表已经排序,并且没有重复项)。在相等性比较中没有考虑MyObject.Id和MyObject.Name属性,但它们仍然是MyObject的重要元数据,不应丢失。

我不是在寻找一个布尔指标的平等。相反,我需要创建三个新列表,以捕捉新旧列表之间的差异(例如,添加的项目列表、删除的项目列表以及两个列表中都存在的项目列表)。

下面是一些代码的示例,这些代码正是我想要的!我想知道的是如何使其更短/更好/更简单(去掉一个for循环将是一个好的开始)。更棘手的是,请假设您不能对MyObject类进行任何更改,也不能使用任何自定义Equals/IEqualityComparer等实现。

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

比较嵌套列表的两个列表,并返回添加/更改/删除的项

这里稍微短一点(由于消除了第二个循环),稍微好一点(因为消除了包含在第二个环路中的无效搜索)。由于循环中包含的无效搜索,仍然存在O(N^2)时间复杂性。

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

更新:更短,但IMO肯定不是更好(实际上性能更差)的版本

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

如果MyObject没有定义自定义相等比较,即使用默认参考相等,则最后一行可以替换为更短且性能更好的

var sameObjects = updatedObjects.Except(addedObjects);

您可以在Linq中使用IntersectExcept函数
使用Intersect,您将获得现有对象,
使用Except,您将获得新对象。

来自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);