[英]Using LINQ, how would you filter out all but one item of a particular criteria from a list?
I realize my title probably isn't very clear so here's an example: 我意识到我的标题可能不是很清楚所以这是一个例子:
I have a list of objects with two properties, A and B. 我有一个具有两个属性A和B的对象列表。
public class Item
{
public int A { get; set; }
public int B { get; set; }
}
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 2, B = 1 },
new Item() { A = 2, B = 2 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
Using LINQ, what's the most elegant way to collapse all the A = 2 items into the first A = 2 item and return along with all the other items? 使用LINQ,将所有A = 2项目折叠成第一个A = 2项目并返回所有其他项目的最优雅方法是什么? This would be the expected result. 这将是预期的结果。
var list = new List<Item>
{
new Item() { A = 0, B = 0 },
new Item() { A = 0, B = 1 },
new Item() { A = 1, B = 0 },
new Item() { A = 2, B = 0 },
new Item() { A = 3, B = 0 },
new Item() { A = 3, B = 1 },
}
I'm not a LINQ expert and already have a "manual" solution but I really like the expressiveness of LINQ and was curious to see if it could be done better. 我不是LINQ专家,已经有一个“手动”解决方案,但我真的很喜欢LINQ的表现力,并且很想知道它是否可以做得更好。
How about: 怎么样:
var collapsed = list.GroupBy(i => i.A)
.SelectMany(g => g.Key == 2 ? g.Take(1) : g);
The idea is to first group them by A
and then select those again (flattening it with .SelectMany
) but in the case of the Key
being the one we want to collapse, we just take the first entry with Take(1)
. 我们的想法是首先用A
对它们进行分组,然后再次选择它们(用.SelectMany
平它)但是在Key
是我们想要折叠的那个的情况下,我们只用Take(1)
取第一个条目。
One way you can accomplish this is with GroupBy
. 您可以通过GroupBy
实现此目的。 Group the items by A
, and use a SelectMany
to project each group into a flat list again. 按A
分组项目,并使用SelectMany
将每个组再次投影到一个平面列表中。 In the SelectMany
, check if A
is 2
and if so Take(1)
, otherwise return all results for that group. 在SelectMany
,检查A
是否为2
,如果是则Take(1)
,否则返回该组的所有结果。 We're using Take
instead of First
because the result has to be IEnumerable
. 我们使用Take
而不是First
因为结果必须是IEnumerable
。
var grouped = list.GroupBy(g => g.A);
var collapsed = grouped.SelectMany(g =>
{
if (g.Key == 2)
{
return g.Take(1);
}
return g;
});
One possible solution (if you insist on LINQ): 一种可能的解决方案(如果你坚持使用LINQ):
int a = 2;
var output = list.GroupBy(o => o.A == a ? a.ToString() : Guid.NewGuid().ToString())
.Select(g => g.First())
.ToList();
Group all items with A=2
into group with key equal to 2
, but all other items will have unique group key (new guid), so you will have many groups having one item. A=2
所有项目分组,密钥等于2
,但所有其他项目将具有唯一的组密钥(新guid),因此您将拥有许多具有一个项目的组。 Then from each group we take first item. 然后从每个组我们采取第一项。
Yet another way: 另一种方式:
var newlist = list.Where (l => l.A != 2 ).ToList();
newlist.Add( list.First (l => l.A == 2) );
An alternative to other answers based on GroupBy
can be Aggregate
: 基于GroupBy
其他答案的替代方法可以是Aggregate
:
// Aggregate lets iterate a sequence and accumulate a result (the first arg)
var list2 = list.Aggregate(new List<Item>(), (result, next) => {
// This will add the item in the source sequence either
// if A != 2 or, if it's A == 2, it will check that there's no A == 2
// already in the resulting sequence!
if(next.A != 2 || !result.Any(item => item.A == 2)) result.Add(next);
return result;
});
What about this: 那这个呢:
list.RemoveAll(l => l.A == 2 && l != list.FirstOrDefault(i => i.A == 2));
if you whould like more efficient way it would be: 如果你想要更有效的方式,那将是:
var first = list.FirstOrDefault(i => i.A == 2);
list.RemoveAll(l => l.A == 2 && l != first);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.