IEnumerable-возвращаемые элементы в диапазоне по обе стороны элемента
мне нужно получить элемент из 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>
.
8 ответов
этот метод расширения, находит первый элемент последовательности, удовлетворяющий заданному предикату, а затем возвращает этот элемент вместе с определенным числом соседних элементов. Он обрабатывает конечные случаи.
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);
}
}
}
EDIT: обновленный ответ из-за обновления вопроса**
метод расширения должен это сделать, теперь поддерживает любой тип 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()));
живой пример:http://rextester.com/rundotnet?code=ACKDD76841
предполагая, что вы можете получить индекс среднего элемента:
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();
(the Dump()
вызов помощью linqpad)
EDIT:
благодаря комментарию Гейба, избавился от двух дополнительных Skip()
/Take()
:
enumerable.Skip((index < range) ? 0 : index-range)
.Take(((index < range) ? index : range) + range + 1)
.Dump();
следующее дает правильные ответы для нелинейных последовательностей и эффективно, e.g:
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);
}
вы можете сделать что-то подобное.
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);
поскольку элементы уникальны, ниже функция будет работать для вас.
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
может быть так просто:
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);
}
}
}
}
}
Это версия на основе очереди, только я реализую свою собственную очередь (в отличие от других, которые используют 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;
}
}