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

时间:2015-08-10 22:36:46

标签: c# linq

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

是否有LINQ查询会将可枚举的startElement返回到endElement,包括在内?

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

5 个答案:

答案 0 :(得分:1)

我能想到的最好的是:

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

答案 1 :(得分: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);

它不会迭代整个序列。 请注意,此处http://linqlib.codeplex.com/

中有大量读取扩展名

答案 2 :(得分:1)

这不使用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
                    );

请注意,这在技术上效率低于替代方案,但如果你已经在内存中有一个IList,差异可能会小于一毫秒,这对于可读代码来说是一个小小的牺牲。

但是,我建议使用秒表包装代码块以测试您的具体情况。

答案 3 :(得分:1)

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

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来轻松完成此操作1}}

答案 4 :(得分:1)

我假设您不想使用额外的内存,并且不想超出底层迭代方法的算法复杂性,因此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")