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;
    }
}