【问题标题】:Comparing two lists of nested lists and returning the added/changed/removed items比较两个嵌套列表列表并返回添加/更改/删除的项目
【发布时间】:2015-11-29 21:14:31
【问题描述】:

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

我需要比较两个“嵌套列表列表”并捕捉差异。一个是“旧”列表,另一个是“新”列表。在比较嵌套列表时,如果所有 NESTED 列表项(MyObject.Ids)都按顺序出现在两个列表中,则可以认为它们相等(您可以假设嵌套的 MyObject.Ids 列表已经排序并且没有重复)。 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);
    }
}

【问题讨论】:

    标签: c# list nested comparison


    【解决方案1】:

    这里稍微短一些(由于消除了第二个循环)并且稍微好一点(由于消除了第二个循环中包含的无效搜索)。由于循环中包含的无效搜索,仍然是 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 魔法或以某种方式删除剩余的 foreach 吗?
    • 另一个小问题,既然类型是众所周知的,为什么在 foreach 中使用 var 而不是 MyObject 更好?
    • @DaveUK (1) 一个循环是我能想到的最好的。请注意,LINQ 中没有魔法,实际上大多数 LINQ 方法都是循环。即使上述内容可以用 3 条 LINQ 语句以某种方式表达,我也永远不会采用这种方式,因为它效率极低。 (2) 我不是说它是否更好——这只是个人喜好。但是让我问你同样的问题 - lambda 表达式支持显式定义参数的类型,为什么你不这样做 - 在你的例子中,为什么不 .Any((List&lt;MyObject&gt; list) =&gt; ...。案例是相同的,而且好处是更少的冗长。
    【解决方案2】:

    您可以在 Linq 中使用 Intersect 和 Except 函数
    使用 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);
    

    【讨论】:

    • 我知道这两个 Linq 函数,但您的示例与我的问题不同。您的示例在两个列表上运行,而不是两个“列表列表”,即嵌套列表。这似乎是复杂性出现的地方。
    • 现在我看到你有一个嵌套列表。也许您可以使用“SelectMany”来扁平化“updatedObjects”并在一个长列表中获取所有 Id。
    • 扁平化列表将丢失 Id 所属的 MyObject 的上下文。在示例中看到,最终您需要返回不同的实际 MyObject,而不仅仅是该 MyObject 中的 Id。
    • 我认为查看问题的最清晰方法是实际采用我上面的示例代码,运行它并查看它产生了什么(它将产生正确的输出)然后看看你是否可以使用任何技术来缩短/改进代码。这就是挑战:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-22
    • 2020-12-19
    相关资源
    最近更新 更多