我昨天问过如何在集合中第一个标志实例之后选择所有内容,答案是使用SkipWhile,它非常有用。但现在逻辑已经改变了,我需要一种方法来选择它的最后一个实例及其后的所有内容。
更详细一点: 该列表包含一个包含许多配置的有序列表,每个列表都有一个名为IsTop的标志。我需要做的是找到IsTop == true的最后一个实例,抓住它以及之后的所有内容。
这可以在LINQ中完成,还是必须ToArray()并手动完成,可以这么说?
答案 0 :(得分:4)
您可以使用Reverse
来处理此问题,并将SkipWhile
替换为TakeWhile
。
var query = sequence.Reverse()
.TakeWhile(item => !item.IsTop)
.Reverse(); //to get back in the original order; remove if not needed
不幸的是,上面的方法不包括IsTop
为真的最后一项,这样做有点复杂,而这样做的“最简单”方法会涉及多次迭代序列,因此,它实际上应该仅用于List
,Array
或其他可以按索引访问项目的数据结构(即IList
)。这是一个能够处理它的方法:
public static IEnumerable<T> Foo<T>(IList<T> data, Func<T, bool> isDivisor)
{
int itemsToTake = data.Reverse()
.TakeWhile(isDivisor)
.Count() + 1;
return data.Skip(data.Count - itemsToTake);
}
另一种更“正确”的方法依赖于辅助方法。此方法将在谓词指示项目时对项目进行分组。如果谓词返回true,则将其添加到“当前组”,如果为false,则前一组为“已完成”并启动新组。这个辅助方法如下:
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(
this IEnumerable<T> source, Func<T, bool> predicate)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> list = new List<T>() { iterator.Current };
while (iterator.MoveNext())
{
if (predicate(iterator.Current))
{
list.Add(iterator.Current);
}
else
{
yield return list;
list = new List<T>() { iterator.Current };
}
}
yield return list;
}
}
使用它实际上相当简单:
var query = sequence.GroupWhile(item => !item.IsTop)
.Last();
从概念上讲,这可以模拟我们所做的最好的事情。我们正在创建一个组,其中每个组从一个IsTop
项目转到下一个项目,然后我们只想要最后一个组(或第一个组,用于您的其他问题)。
答案 1 :(得分:1)
您可以编写自己的简单扩展方法来执行此操作:
// takes items until the first one where predicate is true;
// includes the first item where predicate is true
public static IEnumerable<TSource> TakeUntil<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
{
foreach (var item in source)
{
yield return item;
if (predicate(item))
break;
}
}
public static IEnumerable<TSource> TakeLastUntil<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate
)
{
return source.Reverse().TakeUntil(predicate).Reverse();
}
使用类似:
var myList = new[]
{
new { IsTop = false, S = 'a' },
new { IsTop = true, S = 'b' },
new { IsTop = false, S = 'c' },
new { IsTop = true, S = 'd' },
new { IsTop = false, S = 'e' },
}.ToList();
myList.TakeLastUntil(x => x.IsTop); // has d and e
这可能会超出必要的范围迭代列表。如果这是一个问题(例如,因为你有一个很长的列表)并且你正在使用某种IList<T>
而不仅仅是IEnumerable<T>
,那么应该可以更有效地为{{{{{{ 1}}。