IEnumerable - 返回元素两侧范围内的项目

时间:2011-08-22 12:06:51

标签: c# linq 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 个答案:

答案 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()));

实例:http://rextester.com/rundotnet?code=ACKDD76841

答案 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);
                }
            }
        }
    }
}