如何在最后一个实例后选择所有内容?

时间:2013-11-21 18:33:27

标签: c# linq

我昨天问过如何在集合中第一个标志实例之后选择所有内容,答案是使用SkipWhile,它非常有用。但现在逻辑已经改变了,我需要一种方法来选择它的最后一个实例及其后的所有内容。

更详细一点: 该列表包含一个包含许多配置的有序列表,每个列表都有一个名为IsTop的标志。我需要做的是找到IsTop == true的最后一个实例,抓住它以及之后的所有内容。

这可以在LINQ中完成,还是必须ToArray()并手动完成,可以这么说?

2 个答案:

答案 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为真的最后一项,这样做有点复杂,而这样做的“最简单”方法会涉及多次迭代序列,因此,它实际上应该仅用于ListArray或其他可以按索引访问项目的数据结构(即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}}。