如何根据集合的已知元素提取IEnumerable的一部分?
本文关键字:提取 IEnumerable 一部分 元素 何根 集合 | 更新日期: 2023-09-27 18:07:10
我有一个集合,特别是一个IList<T>
。我知道集合中的两个元素,startElement
和endElement
。
是否有一个LINQ查询,将返回可枚举从startElement
到endElement
,包括吗?
我想使用sequence.SkipWhile(p=>p!=startElement).TakeWhile(q=>q!=endElement)
,但错过了最后一个元素…
这没有使用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);