我需要从IEnumerable中获取一个元素,然后返回自身以及两边的一系列元素。
所以,像这样:
var enumerable = new[] {54, 107, 24, 223, 134, 65, 36, 7342, 812, 96, 106};
var rangeSize = 2;
var range = enumerable.MySelectRange(x => x == 134, rangeSize);
会返回类似{ 24, 223, 134, 65, 36 }
的内容。
(此项目使用.Net 3.5)
修改 好吧,人们似乎已经陷入了一连串的冲动。 我已经改变了这个例子,希望能让我更清楚自己所追求的是什么。
请记住,这不一定是IEnumerable<int>
,但实际上是IEnumerable<TSomething>
。
答案 0 :(得分:5)
此扩展方法查找序列中满足给定谓词的第一个元素,然后返回该元素及其一定数量的相邻元素。它处理最终案件。
public static IEnumerable<T> FirstAndNeighbours<T>(
this IEnumerable<T> source,
Func<T,bool> predicate,
int numOfNeighboursEitherSide)
{
using (var enumerator = source.GetEnumerator())
{
var precedingNeighbours = new Queue<T>(numOfNeighboursEitherSide);
while(enumerator.MoveNext())
{
var current = enumerator.Current;
if (predicate(current))
{
//We have found the first matching element. First, we must return
//the preceding neighbours.
foreach (var precedingNeighbour in precedingNeighbours)
yield return precedingNeighbour;
//Next, return the matching element.
yield return current;
//Finally, return the succeeding neighbours.
for (int i = 0; i < numOfNeighboursEitherSide; ++i)
{
if (!enumerator.MoveNext())
yield break;
yield return enumerator.Current;
}
yield break;
}
//No match yet, keep track of this preceding neighbour.
if (precedingNeighbours.Count >= numOfNeighboursEitherSide)
precedingNeighbours.Dequeue();
precedingNeighbours.Enqueue(current);
}
}
}
答案 1 :(得分:2)
编辑:由于问题更新而更新了答案**
扩展方法应该这样做,现在支持任何类型T
public static IEnumerable<T> Range<T>(this IEnumerable<T> enumerable, Func<T,bool> selector, int size)
{
Queue<T> queue = new Queue<T>();
bool found = false;
int count = 0;
foreach(T item in enumerable)
{
if(found)
{
if(count++ < size)
{
yield return item;
}
else
{
yield break;
}
}
else
{
if(queue.Count>size)
queue.Dequeue();
if(selector(item))
{
found = true;
foreach(var stackItem in queue)
yield return stackItem;
yield return item;
}
else
{
queue.Enqueue(item);
}
}
}
用法接近您的要求
var enumerable = new[] {54, 107, 24, 223, 134, 65, 36, 7342, 812, 96, 106};
Console.WriteLine(String.Join(",",enumerable.ToArray()));
var rangeSize = 2;
var range = enumerable.Range((x) => x == 134, rangeSize);
Console.WriteLine(String.Join(",",range.ToArray()));
答案 2 :(得分:2)
假设您可以获得中间元素的索引:
var enumerable = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int range = 2;
int index = 10;
enumerable.Skip(index-range).Take(range)
.Union(enumerable.Skip(index).Take(1))
.Union(
enumerable.Skip(index+1).Take(range)
).Dump();
(Dump()
来电是LinqPad)
修改强>
感谢Gabe的评论,摆脱了额外的两个Skip()
/ Take()
:
enumerable.Skip((index < range) ? 0 : index-range)
.Take(((index < range) ? index : range) + range + 1)
.Dump();
答案 3 :(得分:1)
以下给出了非线性序列的正确答案,并且是有效的,例如:
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 0, 1, 2, 3, 4, 5, 600, 700, 800, 900, 1000 };
IEnumerable<int> range = enumerable.PivotRange(PivotValue, RangeSize);}
//3, 4, 5, 600, 700.
代码 - 通用版
public static IEnumerable<T> PivotRange<T>(
this IEnumerable<T> source, T pivot, int size) where T : IComparable<T>
{
T[] left = new T[size];
int lCount = 0, rCount = 0;
IEnumerator<T> enumerator = source.GetEnumerator();
while (enumerator.MoveNext())
{
T item = enumerator.Current;
if (item.CompareTo(pivot) == 0)
{
int start = lCount > size ? lCount % size : 0;
int end = Math.Min(size, lCount);
for (int i = start; i < start + end; i++)
yield return left[i % size];
yield return pivot;
while (enumerator.MoveNext() && rCount++ < size)
yield return enumerator.Current;
break;
}
if (size <= 0) continue;
left[lCount++ % size] = item;
}
}
更新 - 单元测试
[Test]
public void Linear()
{
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 3, 4, 5, 6, 7 }, range);
}
[Test]
public void NonLinear()
{
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 0, 1, 2, 3, 4, 5, 600, 700, 800, 900, 1000 };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 3, 4, 5, 600, 700 }, range);
}
[Test]
public void NoLeft()
{
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 5, 600, 700, 800, 900, 1000 };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 5, 600, 700 }, range);
}
[Test]
public void NoRight()
{
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 0, 1, 2, 3, 4, 5 };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 3, 4, 5 }, range);
}
[Test]
public void ZeroRange()
{
const int PivotValue = 5;
const int RangeSize = 0;
int[] enumerable = new[] { 0, 1, 2, 3, 4, 5 };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 5 }, range);
}
[Test]
public void LeftShorterThanRange()
{
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 4, 5, 6, 7, 8 };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 4, 5, 6, 7 }, range);
}
[Test]
public void RightShorterThanRange()
{
const int PivotValue = 5;
const int RangeSize = 2;
int[] enumerable = new[] { 2, 3, 4, 5, 6, };
int[] range = enumerable.PivotRange(PivotValue, RangeSize).ToArray();
CollectionAssert.AreEqual(new[] { 3, 4, 5, 6 }, range);
}
答案 4 :(得分:0)
你可以这样做。
var enumerable = new[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
var rangeSize = 2;
var index = enumerable.FirstOrDefault(x=> x == 2);
var range = enumerable.Skip(index + 1).Take(5);
答案 5 :(得分:0)
这是一个基于队列的版本,只有我实现自己的队列(与使用Queue
的其他队列不同)。实现我自己的队列的优点是效率稍高。
public static IEnumerable<T> MySelectRange<T>(this IEnumerable<T> source,
Func<T, bool> selector,
int rangeSize)
{
var firstN = new T[rangeSize];
int pos = 0;
bool found = false;
foreach (T item in source)
{
if (found)
if (pos++ <= rangeSize)
yield return item;
else
break;
if (selector(item))
{
found = true;
for (int i = Math.Max(0, pos - rangeSize); i < pos; i++)
yield return firstN[i % rangeSize];
yield return item;
pos = 0;
}
else if (rangeSize > 0)
firstN[pos++ % rangeSize] = item;
}
}
答案 6 :(得分:0)
尽管这些元素是独一无二的,但下面的功能对你有用。
public static IEnumerable<T> MySelectRange<T>(this IEnumerable<T> me, Func<T, bool> pred, int range)
{
var first = me.TakeWhile(i => !pred(i)).TakeLast(range);
var second = me.SkipWhile(i => !pred(i)).Take(range + 1);
return first.Concat(second);
}
注意:TakeLast似乎来自Rx库
答案 7 :(得分:0)
可以这么简单:
public IEnumerable<T> GetRange<T>(IEnumerable<T> enumerable, int rangeSize, T value)
{
for (int i = 0; i < enumerable.Count(); ++i)
{
if (enumerable.ElementAt(i).Equals(value))
{
for (int j = Math.Max(0, i - rangeSize); j < Math.Min(i + rangeSize + 1, enumerable.Count()); ++j)
{
yield return enumerable.ElementAt(j);
}
}
}
}
}