比较两个差异列表

时间:2016-07-22 04:57:53

标签: c# .net linq

我有以下情况:

class A
{
   public A(string name, int age) { Name = name; Age = age; }
   public string Name;
   public int Age;
}

List<A> one = 
  new List<A>() { new A("bob", 15), new A("john", 10), new A("mary", 12) };
List<A> two = 
  new List<A>() { new A("bob", 15), new A("mary", 15), new A("cindy", 18) }; 

我想在这些列表之间做差异,并获取john仅在列表1中的信息,cindy仅在列表2中,并且mary在两个列表中但是它不是完全匹配(年龄不同) )。我的目标是以并排比较的方式提供这些信息。

有人可以建议如何有效地做到这一点(而不是做三次通过,一次是针对第一次不存在的东西,另一种是第二次不存在的东西,第三种是存在于两者中的东西但是不同)

如果我错过任何重复的问题,我很抱歉,我能找到的只能处理布尔结果,而不是实际细节。

3 个答案:

答案 0 :(得分:2)

var result = 
    one.Select(a => Tuple.Create(a, "one")) // tag list one items
    .Concat(two.Select(a => Tuple.Create(a, "two"))) // tag list two items
    .GroupBy(t => t.Item1.Name) // group items by Name
    .ToList(); // cache result

var list_one_only = result.Where(g => g.Count() == 1 && g.First().Item2 == "one");
var list_two_only = result.Where(g => g.Count() == 1 && g.First().Item2 == "two");
var both_list_diff = result.Where(g => g.Count() == 2 && g.First().Item1.Age != g.Skip(1).First().Item1.Age);

这将返回一个列表列表,其中每个内部列表将是1个项目(具有原始项目的元组以及它来自哪个列表)或者将有2个项目(相同的名称,可能相同)年龄,以及哪个年龄从哪个名单开始)。

我不确定你想要结果的结构是什么,所以我把它留在那里。否则从这里你可以做另一个选择来过滤掉两个列表中的相同记录(&#34; bob&#34;)等。

此解决方案应该只遍历两个列表一次。

答案 1 :(得分:1)

传球将是隐含的或“明确的”#34;。而通过显式我的意思是通过一些Linq扩展方法隐藏。所以你可以做到以下几点:

var results = (from item in one.Concat(two).Select(x => x.Name).Distinct()
                let inFirst = one.Find(x => x.Name == item)
                let inSecond = two.Find(x => x.Name == item)
                let location = inFirst != null 
                            && inSecond != null 
                                ? 2 : inSecond != null ? 1 : 0
                select new
                {
                    Name = item,
                    location = location == 0 ? "First" : location == 1 ? "Second" : "Both",
                    ExactMatch = (location != 2 || inFirst.Age == inSecond.Age) 
                                  ? "YES" : $"One: { inFirst.Age } | Two: { inSecond.Age }"
                }).ToList();

结果:

{ Name = bob, location = Both, ExactMatch = YES }
{ Name = john, location = First, ExactMatch = YES }
{ Name = mary, location = Both, ExactMatch = One: 12 | Two: 15 }
{ Name = cindy, location = Second, ExactMatch = YES }

如果您担心性能问题,请使用高效的数据结构进行查找O(1)。以下33ms项目列表的10000完成,而上述内容在5000ms左右结束:

var oneLookup = one.ToLookup(x => x.Name, x => x);
var twoLookup = two.ToLookup(x => x.Name, x => x);

var results = (from item in one.Concat(two).Select(x => x.Name).Distinct()
                let inFirst = oneLookup[item].FirstOrDefault()
                let inSecond = twoLookup[item].FirstOrDefault()
                let location = inFirst != null
                            && inSecond != null
                                ? 2 : inSecond != null ? 1 : 0
                select new
                {
                    Name = item,
                    location = location == 0 ? "First" : location == 1 ? "Second" : "Both",
                    ExactMatch = (location != 2 || inFirst.Age == inSecond.Age)
                                  ? "YES" : $"One: { inFirst.Age } | Two: { inSecond.Age }"
                }).ToList();

答案 2 :(得分:1)

  

有人可以建议如何有效地做到这一点(而不是做三次通过,一次是针对先不存在的东西,另一种针对第二种不存在的东西,第三种是东西存在于两者中但不同)

在一次传递中产生差异的唯一方法是,如果两个序列按身份键(在您的情况下为Name)排序。但是,排序会带来额外的成本,并且该过程也无法在LINQ中编码。

你真正需要的是full outer join,它没有自然的LINQ支持。因此,经典方法需要两次传递 - left other join用于存在于一个中并最终存在于第二个中的内容,而right antijoin用于仅存在于第二个中的内容。这是迄今为止最有效的LINQ方式。

查询可能是这样的:

var result =
    (from e1 in one
     join e2 in two on e1.Name equals e2.Name into match
     from e2 in match.DefaultIfEmpty()
     select new
     {
         e1.Name,
         In = e2 == null ? "A" : "A,B",
         Age = e2 == null || e1.Age == e2.Age ? e1.Age.ToString() : $"A:{e1.Age} B:{e2.Age}"
     })
    .Concat
    (from e2 in two
     join e1 in one on e2.Name equals e1.Name into match
     where !match.Any()
     select new { e2.Name, In = "B", Age = e2.Age.ToString() })
    .ToList();

从您的示例数据中生成以下内容:

{ Name = bob, In = A,B, Age = 15 }
{ Name = john, In = A, Age = 10 }
{ Name = mary, In = A,B, Age = A:12 B:15 }
{ Name = cindy, In = B, Age = 18 }

当然你可以输出你想要的任何东西。如您所见,您需要考虑具有两个匹配元素的唯一位置是查询的第一部分。