【问题标题】:How can I efficiently compare the properties of two large lists of objects in C#?如何有效地比较 C# 中两个大型对象列表的属性?
【发布时间】:2017-11-30 01:43:34
【问题描述】:

我有一个包含两个对象列表的数据集,其 ID 在两个列表中都是一致的,但其他属性可能不同,也可能不同。我怎样才能最有效地检索基于一个或多个属性的不同属性?

我通常的做法是这样的。我的对象设置如下:

   public class Person
        {
            public int ID { get; set; }
            public string Name { get; set; }
            public int Age { get; set; }

            public bool IsEqual(Person other)
            {
                if (Name != other.Name)
                {
                    return false;
                }
                if (Age != other.Age)
                {
                    return false;
                }
                return true;
            }
        } 

IsEqual 比较器用于将其与某个等效对象进行比较。

然后我寻找修改过的人的方法是这样的:

  public static List<Person> FindModifiedPeople(List<Person> listA, List<Person> listB)
        {
            var modifiedPeople = new List<Person>();
            foreach (var personA in listA)
            {
                var matchingPerson = listB.FirstOrDefault(e => e.ID == personA.ID);
                if (matchingPerson == null)
                {
                    continue;
                }

                if (!personA.IsEqual(matchingPerson))
                {
                    modifiedPeople.Add(personA);
                }
            }
            return modifiedPeople;
        }

在我的数据集中,我不关心 listB 中的人而不关心 listA 中的人,因此我不需要遍历这两个列表。我只需要检查 listA 中 listB 中的元素(可能存在也可能不存在)并返回已修改的人员列表(包含 listA 中的元素)。

这种方法适用于相当小的列表,但现在我有两个包含大约 160,000 人的列表,这种方法需要几分钟时间。有什么方法可以使这种方法更有效,同时仍然返回我需要它做的事情?

【问题讨论】:

  • 你必须使用列表吗?如果您有此人的 ID,您不能将它们存储在字典之类的东西中吗?
  • 你真的需要一起比较列表吗?如果对象已更新,为什么不直接在对象内跟踪并将其公开为布尔属性(即“person.IsDirty”)?
  • 虽然我现在意识到person.IsDirty 可能不是最好的命名约定...
  • 另外,不要在这里重新发明轮子,覆盖Equals(object obj)而不是自己写IsEqual()
  • 请注意,您处理对象集合的“常规”方法非常不寻常......列表在几乎任何集合操作中都非常糟糕 - 请参阅 stackoverflow.com/questions/113173/set-operation-in-net-c-sharp 以获得指导......

标签: c#


【解决方案1】:

如果您可以将您的列表更改为Dictionary&lt;int, Person&gt;,并使用此人的 ID 作为密钥,那么这对您有用。这将在 O(n) 中运行,而不是在您的 O(n^2) 中运行。

public static List<Person> FindModifiedPeople(Dictionary<int, Person> dictA, Dictionary<int, Person> dictB)
{
    var modifiedPeople = new List<Person>();
    foreach (var personA in dictA)
    {
        Person matchingPerson;
        if(dictB.TryGetValue(personA.Key, out matchingPerson))
        {
            if (!personA.Value.IsEqual(matchingPerson))
            {
                modifiedPeople.Add(personA.Value);
            }
        }
    }
    return modifiedPeople;
}

您还可以根据需要将返回类型从 List 更改为另一个 Dictionary。

编辑

正如@maccettura 在他的评论中指出的那样,您确实应该覆盖内置的 equals 方法。这将使您的代码看起来像这样。

public override bool Equals(Object obj)
{
    if (obj == null || GetType() != obj.GetType())
        return false;

    var otherPerson = (Person)obj;

    if (Name != otherPerson.Name)
    {
        return false;
    }
    if (Age != otherPerson.Age)
    {
        return false;
    }
    return true;
}

这将允许您的代码处理任何期望使用默认 Equals 方法而不是您的自定义方法的东西。

【讨论】:

  • 我认为也可以提及覆盖 Equals() (不妨给 OP 一些更好的做法)
  • 是的。我什么都没说,只是因为你已经把它放在你的评论中了,但我会在这里添加它。
  • 哇!这产生了巨大的变化!太棒了,谢谢。另外,是的,我知道我应该重写 Equals()。我的同事已经为 Equals 添加了覆盖,因为他希望在 比较 ID 并且不关心属性时返回 true。所以他的覆盖类似于: public override bool Equals(Person other) { if (ID != other.ID) { return false; } 返回真;显然,就我而言,我希望 Equals 专门比较属性。我真的不知道如何解决这个困境,所以我把我的一个单独的方法。
【解决方案2】:

您确定比较是瓶颈吗?我认为问题来自您在这一行中进行的搜索:

var matchingPerson = listB.FirstOrDefault(e => e.ID == personA.ID);

在那里,您正在以 O(n) 的对数复杂度进行搜索,再加上 foreach 循环,总复杂度为 O(n^2)。相反,您可以预先创建一个字典,这需要一些时间,但查找速度要快得多。字典应该以 ID 作为键,并且可以像这样在 foreach LOOP 之前轻松创建:

var dictB = listB.ToDictionary(p => p.ID);

之后,您的查找速度会快很多,如下所示:

    Person matchingPerson;
    if (dictB.TryGetValue(personA.ID, out matchingPerson))
    {
        if (!personA.IsEqual(matchingPerson))
        {
            modifiedPeople.Add(personA);
        }
    }

【讨论】:

  • 你说得对,FirstOrDefault 确实是瓶颈,而不是比较器。我已将其更改为从列表中制作字典(我确实需要将列表用于其他目的),甚至包括该开销,查找速度明显更快。如果可以的话,我会将您和 Michael Sharp 都标记为已接受的答案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-31
  • 2020-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多