如何根据集合的已知元素提取IEnumerable的一部分?

本文关键字:提取 IEnumerable 一部分 元素 何根 集合 | 更新日期: 2023-09-27 18:07:10

我有一个集合,特别是一个IList<T>。我知道集合中的两个元素,startElementendElement

是否有一个LINQ查询,将返回可枚举从startElementendElement,包括吗?

我想使用sequence.SkipWhile(p=>p!=startElement).TakeWhile(q=>q!=endElement),但错过了最后一个元素…

如何根据集合的已知元素提取IEnumerable的一部分?

这没有使用LINQ,但它可能是最直接/可读的方法。

        int startIndex = sequence.IndexOf(startElement), 
            endIndex = sequence.IndexOf(endElement);
        var range = sequence.GetRange(
                         startIndex, 
                         // +1 to account for zero-based indexing
                         1 + endIndex - startIndex
                    );

请注意,从技术上讲,这比其他方法效率低,但如果内存中已经有一个illist,那么差异可能小于1毫秒,这对于可读代码来说是一个很小的牺牲。

但是,我建议用Stopwatch来包装代码块,以针对您的具体情况进行测试。

这将是最有效的,因为它不会创建任何不必要的枚举器对象,并且只遍历列表一次。

var result = new List<T>();
var inSequence = false;
for (var i = 0; i < list.Length; i++)
{
    var current = list[i];
    if (current == startElement) inSequence = true;
    if (!inSequence) continue;
    result.add(current);
    if (current == endElement) break;
}

这不会处理缺少endElement的情况,但是您可以通过将result = null指定为for循环的最后一行来轻松地做到这一点,其中i = list.Length - 1

George写了一个更灵活的扩展,你可以在这里找到它:https://stackoverflow.com/a/31940000/5106041

旧版本

:

public static class MyExtensions
{
    public static IEnumerable <TData> InBetween <TData> (this IEnumerable <TData> Target, TData StartItem, TData EndItem)
    {
        var Comparer = EqualityComparer <TData>.Default;
        var FetchData = false;
        var StopIt = false;
        foreach (var Item in Target) {
            if (StopIt)
                break;
            if (Comparer.Equals (Item, StartItem))
                FetchData = true;
            if (Comparer.Equals (Item, EndItem))
                StopIt = true;
            if (FetchData)
                yield return Item;
        }
        yield break;
    }
}

那么,现在你可以这样使用它:

sequence.InBetween (startElement, endElement);

它不会迭代整个序列。注意这里有很多read - made扩展http://linqlib.codeplex.com/

我假设您不想使用额外的内存,也不想超出底层迭代方法的算法复杂性,因此在我建议的实现中不允许使用ToList, GroupBy, IndexOf。

另外,为了不对元素类型施加约束,我使用谓词。

    public static class EnumerableExtensions
    {
        /// <summary>
        /// This one works using existing linq methods.
        /// </summary>
        public static IEnumerable<T> GetRange<T>(this IEnumerable<T> source, Func<T, bool> isStart, Func<T, bool> isStop)
        {
            var provideExtraItem = new[] { true, false };
            return source
                .SkipWhile(i => !isStart(i))
                .SelectMany(i => provideExtraItem, (item, useThisOne) => new {item, useThisOne })
                .TakeWhile(i => i.useThisOne || !isStop(i.item))
                .Where(i => i.useThisOne)
                .Select(i => i.item);
        }
        /// <summary>
        /// This one is probably a bit faster.
        /// </summary>
        public static IEnumerable<T> GetRangeUsingIterator<T>(this IEnumerable<T> source, Func<T, bool> isStart, Func<T, bool> isStop)
        {
            using (var iterator = source.GetEnumerator())
            {
                while (iterator.MoveNext())
                {
                    if (isStart(iterator.Current))
                    {
                        yield return iterator.Current;
                        break;
                    }
                }
                while (iterator.MoveNext())
                {
                    yield return iterator.Current;
                    if (isStop(iterator.Current))
                        break;
                }
            }
        }
    }

这些方法可以用作扩展方法:

new[]{"apple", "orange", "banana", "pineapple"}.GetRange(i => i == "orange", i => i == "banana")

我能想到的最好的是:

var subSection = TestData.SkipWhile(p => p != startElement).ToList();
var result = subSection.Take(subSection.IndexOf(endElement) + 1);