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。旁注:2foreach
(一个跳过第一部分,另一个迭代直到找到结尾)可能会产生很多简单的代码。不,如果使用与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")