选择 IEnumerable<T> 的下 N 个元素。

4
假设您有一个长度为 N 的 IEnumerable,称为 S。我想从 S 中选择所有长度为 n <= N 的连续子序列。
如果S是字符串,这很容易。长度为n的子串数为(S.Length-n + 1)。例如,"abcdefg"的长度为7,表示它具有长度为3的(5)个子字符串:"abc"、"bcd"、"cde"、"def"和"efg"。
但是,由于 S 可以是任何 IEnumerable,因此不能采用这种方法。如何使用扩展方法来解决这个问题呢?
6个回答

4
F#有一个名为Seq.windowed的库函数可以完成此操作。 http://research.microsoft.com/en-us/um/cambridge/projects/fsharp/manual/FSharp.Core/Microsoft.FSharp.Collections.Seq.html
// windowed : int -> seq<'a> -> seq<array<'a>>
let windowed n (s: seq<_>) =    
    if n <= 0 then Helpers.invalid_arg2 "n" "the window size must be positive"
    { let arr = Array.zero_create n 
      let r = ref (n-1)
      let i = ref 0 
      use e = s.GetEnumerator() 
      while e.MoveNext() do 
          do arr.[!i] <- e.Current
          do i := (!i + 1) % n 
          if !r = 0 then 
              yield Array.init n (fun j -> arr.[(!i+j) % n])
          else 
              do r := (!r - 1) }

太棒了!这非常接近我想要的。我会看看能否将其翻译成C#。 - sassafrass
1
这比必要的要复杂得多。请参见:https://dev59.com/T0bRa4cB1Zd3GeqP3s3Y#12238627 - cdiggins

2
实际上,您可以使用LINQ来解决这个问题,例如:
var subList = list.Skip(x).Take(y);

其中list是一个IEnumerable


我实际上没有尝试过这个,但是这个代码只会返回第一个x元素后的前y个元素,我们需要多次使用Skip函数。记住,我们需要所有连续子序列,而不仅仅是其中一个! :) - sassafrass

0

给未来的读者。

这里有一个小例子。

    private static void RunTakeSkipExample()
    {
        int takeSize = 10; /* set takeSize to 10 */

        /* create 25 exceptions, so 25 / 10 .. means 3 "takes" with sizes of 10, 10 and 5 */
        ICollection<ArithmeticException> allArithExceptions = new List<ArithmeticException>();
        for (int i = 1; i <= 25; i++)
        {
            allArithExceptions.Add(new ArithmeticException(Convert.ToString(i)));
        }

        int counter = 0;
        IEnumerable<ArithmeticException> currentTakeArithExceptions = allArithExceptions.Skip(0).Take(takeSize);
        while (currentTakeArithExceptions.Any())
        {
            Console.WriteLine("Taking!  TakeSize={0}. Counter={1}. Count={2}.", takeSize, (counter + 1), currentTakeArithExceptions.Count());

            foreach (ArithmeticException ae in currentTakeArithExceptions)
            {
                Console.WriteLine(ae.Message);
            }

            currentTakeArithExceptions = allArithExceptions.Skip(++counter * takeSize).Take(takeSize);
        }

    }

输出:

Taking!  TakeSize=10. Counter=1. Count=10.
1
2
3
4
5
6
7
8
9
10
Taking!  TakeSize=10. Counter=2. Count=10.
11
12
13
14
15
16
17
18
19
20
Taking!  TakeSize=10. Counter=3. Count=5.
21
22
23
24
25

你可以通过每个异常的.Message验证每个不同的异常是否被"捕获"。

现在,送上一句电影经典台词!

但是我确实拥有一些特定的技能;这些技能是我在漫长职业生涯中所习得的。这些技能让我成为像你这样的人的噩梦。


0
IEnumerable<IEnumerable<T>> ContiguousSubseqences<T>(this IEnumerable<T> seq, Func<T,bool> constraint)
{
    int i = 0;
    foreach (T t in seq)
    {
        if (constraint(t))
            yield return seq.Skip(i).TakeWhile(constraint);
        i++;
    }
}

0

您可以使用提供索引的 Select 扩展来创建一个包含索引和值的对象,然后将索引除以长度以将它们分组:

var x = values.Select((n, i) => new { Index = i, Value = n }).GroupBy(a => a.Index / 3);

这不太对。你只是将它们分成大小为N的组,而不是产生单独的子序列。例如,当“abcde”和N = 3时,应该产生“abc”,“bcd”,“cde”。但是你的方法却产生了“abc”,“de”。 - sassafrass

0

这是一个新的扩展方法,可以在C#中实现你想要的功能。

static IEnumerable<IEnumerable<T>> Subseqs<T>(this IEnumerable<T> xs, int n) 
{
  var cnt = xs.Count() - n;  
  Enumerable.Range(0, cnt < 0 ? 0 : cnt).Select(i => xs.Skip(i).Take(n));
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接