如何有效地比较C#中两个大对象列表的属性?

时间:2017-11-29 17:17:56

标签: c#

我有两个对象列表的数据集,它们的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人,这种方法需要几分钟。有没有办法让这个方法更有效率,同时仍然返回我需要它做的事情?

2 个答案:

答案 0 :(得分:3)

如果您可以将个人ID作为密钥更改为Dictionary<int, Person>,那么这对您有用。这将在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方法而不是自定义方法的东西。

答案 1 :(得分:3)

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

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

在那里,您正在使用O(n)的logartihmic复杂度进行搜索,这与foreach循环相结合,总复杂度为O(n ^ 2)。相反,您可以预先创建一个字典,这需要一些时间,但查找速度要快得多。字典应该具有ID作为键,并且可以在foreach循环之前轻松创建:

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

之后,您的查找会更快,如下所示:

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