C# 如何基于集合的已知元素提取IEnumerable的一部分?

C# 如何基于集合的已知元素提取IEnumerable的一部分?,c#,linq,C#,Linq,我有一个集合,特别是一个IList。我知道集合中有两个元素,startElement和endElement 是否有一个LINQ查询将枚举从startElement返回到endElement,包括在内 我曾考虑过使用sequence.SkipWhile(p=>p!=startElement).TakeWhile(q=>q!=endElement)但这遗漏了最后一个元素…我能想到的最好的方法是: var subSection = TestData.SkipWhile(p => p != sta

我有一个集合,特别是一个
IList
。我知道集合中有两个元素,
startElement
endElement

是否有一个LINQ查询将枚举从
startElement
返回到
endElement
,包括在内


我曾考虑过使用
sequence.SkipWhile(p=>p!=startElement).TakeWhile(q=>q!=endElement)
但这遗漏了最后一个元素…

我能想到的最好的方法是:

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

George编写了一个更灵活的扩展,您可以在这里找到它:

旧版本:

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;
    }
}
它不会迭代整个序列。
请注意,这里有很多可读的扩展

这不使用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,那么差异可能会小于一毫秒,这对于可读代码来说是一个小小的牺牲


不过,我建议您使用秒表包装代码块,以便根据您的具体情况进行测试

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

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;
}
var result=newlist();
var insessue=false;
对于(变量i=0;i

这不会处理缺少
endElement
的情况,但是,通过将
result=null
指定为
for
循环的最后一行,其中
i=list.Length-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;
                }
            }
        }
    }

不,但创建自己的应该很容易。如果你澄清了你对什么是可接受的要求,有人可以找到重复的或建议的代码。如果你知道列表中有这两个元素,按顺序,那么只需使用你的查询和
Concat
endElement
。如果您不知道元素是否按所需顺序存在,那么在将第一个元素返回给调用方之前,您必须完全具体化列表中所需的部分(因为如果
endElement
实际上不存在,您不能返回任何内容,甚至抛出异常)。这将评估序列中所有元素的条件。我相信关键是在所有内容都被获取后立即停止枚举。但linq就是这样工作的,它使用“链接”迭代器,公平地说,引用比较非常便宜,C#只比较内存地址。linq不评估它不需要的内容<一旦条件为false,code>TakeWhile将停止提取元素。您的代码将继续获取它们(并丢弃它们)。如果序列包含一百万个元素,并且所需的元素是1-10,那么代码将在整个一百万元素中循环,并且只返回前十个元素
TakeWhile
将仅循环通过10.Nice。旁注:2
foreach
(一个跳过第一部分,另一个迭代直到找到结尾)可能会产生很多简单的代码。不,如果使用与GetEnumerator相同的迭代器,则不会。查看我的答案GetRangeUsingInterator,了解如何操作。它在条件语句中不使用额外的分支。这将对序列求值三次。是的,但我怀疑它适合这种情况。如果已经有了内存中的IList,那么与其他方法相比,可能不会有毫秒级的差异。Readability>亚毫秒性能改进,IMO.ToArray在不需要的情况下增加内存需求我需要创建数组以使用IndexOf方法,并且内存消耗可以忽略不计,它只是一个浅克隆。由于原始问题没有指定迭代的大小,你永远不知道这是不是微不足道。在我当前的ETL项目中,在很多情况下,我几乎负担不起内存中的整个集合。源ienumerable可以从数据库中带来数十亿条记录,而且元素的大小也可以很高。如果仔细地使用linq方法枚举,这些对象会被垃圾收集,你几乎可以永远迭代。一次从数据库中引入数十亿条记录会让应用程序设计变得糟糕。无论如何我只是简单地回答了这个问题,它是否适合任何特定的情况取决于开发人员。不幸的是,您没有。您的代码甚至没有编译,所以这里没有什么要讨论的。你不可能只做一行。另外,当您添加与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")