简体   繁体   English

比较LINQ中的列表并返回2个结果对象

[英]Compare list in LINQ and return 2 result objects

I have 2 Lists: List<Car> newCars and List<Car> oldCars . 我有2个列表: List<Car> newCarsList<Car> oldCars

I need to use the List<Car> newCars as the source of truth and return both Present and Absent cars in the List<Car>oldCars list. 我需要使用List<Car> newCars作为事实来源,并在List<Car>oldCars列表中返回当前缺席汽车。

How can I do this? 我怎样才能做到这一点?

My approach: 我的方法:

var present = newCars.Where(c => oldCars.All(w => w.Id != c.Id));
var absent = newCars.Where(c => oldCars.All(w => w.Id == c.Id));

I am quite new to LINQ, and I am not sure of the logic that I have used above. 我对LINQ并不陌生,我不确定上面使用的逻辑。

  1. Can someone help me out to get this working in a better and optimized way? 有人可以帮助我以更好和优化的方式工作吗?

  2. Can this be done in one single query and return 2 result sets (as tuples)? 能否在一个查询中完成并返回2个结果集(作为元组)?

I realize that running the same code with equals and not equals as above can be a costly approach. 我意识到,使用上面的等于或不等于代码运行相同的代码可能是一种昂贵的方法。

You can create custom IEqualityComparer : 您可以创建自定义IEqualityComparer

public class CarEqualityComparer : IEqualityComparer<Car>
{
    public bool Equals(Car x, Car y)
    {
        return x.Id == y.Id;
    }

    public int GetHashCode(Car obj)
    {
        return obj.Id.GetHashCode();
    }
}

And use it like this: 并像这样使用它:

var present = newCars.Intersect(oldCars, new CarEqualityComparer());
var absent = newCars.Except(oldCars, new CarEqualityComparer());

You can utilize an anonymous variable containing the two lists: 您可以利用包含两个列表的匿名变量:

var res = new
{
    present = newCars.Where(n => oldCars.Select(o => o.Id).Contains(n.Id)), // present if the old cars list contains the id from the new list
    absent = newCars.Where(n => !oldCars.Select(o => o.Id).Contains(n.Id)) // absent if the old cars list does not contain the id from the new list
};

When you are saying that new cars are the source of truth, that means you want them always in the list, but you may not have an equivalent old car. 当您说新车是真理的源头时,这意味着您希望它们始终在列表中,但您可能没有同等的旧车。 You are describing a left-join. 您正在描述左联接。

This should get you most of what you want. 这应该为您带来大部分所需的东西。

var results = from n in newCars
    join o in oldCars
    on n.Id == o.Id
    group into cars 
    from oc in cars.DefaultIfEmpty()
    select (NewCar: n, Present: oc != null)

The result is a tuple containing the new car and a boolean Present that is true or false based on whether there is an old car or not. 结果是一个元组,其中包含新车和一个布尔值Present ,根据是否有旧车,该布尔值为truefalse

That gives you a single result set. 这样就可以得到一个结果集。 You wanted two result sets as a tuple. 您想要两个结果集作为元组。 To get this, just extend the above query, grouping by Present . 为此,只需扩展以上查询(按Present分组)即可。 This gives you a result set with exactly two items 这为您提供了一个包含两个项目的结果集

var results = (from r in (from n in newCars
        join o in oldCars
        on n.Id equals o.Id
        into cars
        from oc in cars.DefaultIfEmpty()
        select (NewCar: n, Present: oc != null))
    group r by r.Present into g
    orderby g.Key descending 
    select g.Select(x => x.NewCar).ToList()).ToList();

from which you can get a single tuple containing both lists 从中您可以得到一个包含两个列表的元组

var finalResult = (Present: results[0], Absent: results[1]);

Using an extension method to treat adding to a list (a tiny bit) more functionally, you can use LINQ Aggregate to do this in one pass: 使用扩展方法可以更功能地处理添加到列表(一点点),可以使用LINQ Aggregate完成此操作:

var idsFromOldCars = oldCars.Select(c => c.Id).ToHashSet();
var(present, absent) = newCars.Aggregate((p:new List<Car>(),a:new List<Car>()), (pa,nc) => idsFromOldCars.Contains(nc.Id) ? (pa.p.AfterAdd(nc),pa.a) : (pa.p,pa.a.AfterAdd(nc)));

Where AfterAdd is defined in a static class as: 在静态类AfterAdd定义为:

public static class ListExt {
    public static List<T> AfterAdd<T>(this List<T> head, params T[] tail) {
        head.AddRange(tail);
        return head;
    }
}

But it is much clearer to just use a foreach loop: 但是,仅使用一个foreach循环会更加清楚:

    var idsFromOldCars = oldCars.Select(c => c.Id).ToHashSet();
    var present = new List<Car>();
    var absent = new List<Car>();
    foreach (var nc in newCars) {
        if (idsFromOldCars.Contains(nc.Id))
            present.Add(nc);
        else
            absent.Add(nc);
    }

In both cases, creating the HashSet ensures you don't have to scan through the oldCars list to find each newCar . 在这两种情况下,创建HashSet可以确保您不必浏览oldCars列表即可找到每个newCar If you knew the lists were ordered by Id , you could do a more complicated one-pass solution traveling down the lists in parallel, but that hardly seems worth the complexity even then. 如果您知道列表是由Id排序的,则可以执行一个更复杂的单遍解决方案,以并行方式沿列表向下移动,但是即使那样,这似乎也不值得考虑其复杂性。

NOTE: A Join effectively does something similar, converting the second List into a Lookup (like a Dictionary ) that can be used to find the matches with the first List . 注意: Join有效地执行了类似的操作,将第二个List转换为一个Lookup (如Dictionary ),可用于查找与第一个List的匹配项。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM