是否有更好的方法来获取每个项与谓词匹配的子序列?

说我有一个IEnumerable。 例如,{2,1,42,0,9,6,5,3,8}。

我需要获得与谓词匹配的项目的“运行”。 例如,如果我的谓词是

bool isSmallerThanSix(int number){...} 

我想获得以下输出:{{2,1},{0},{5,3}}

是否有内置function可以实现这一目标?

到目前为止我有这个:

 public static IEnumerable<IEnumerable> GetSequences(this IEnumerable source, Func selector) { if (source == null || selector == null) { yield break; } IEnumerable rest = source.SkipWhile(obj => !selector(obj)); while (rest.Count() > 0) { yield return rest.TakeWhile(obj => selector(obj)); rest = rest .SkipWhile(obj => selector(obj)) .SkipWhile(obj => !selector(obj)); } } 

这看起来很有效,但是我在半夜写的,因此从星期二开始效率低十五。 是否有更好的,最好是内置(因此经过良好测试)的方式?

非常感谢你们的时间,

利雅。

据我所知,没有一种构建方法。 但是,在IEnumerable上调用Count扩展方法效率不高,因为它必须枚举列表以获取计数。 因此,我想出了具有相同效果的这一点。

 public static IEnumerable> GetSequences(this IEnumerable source, Func selector) { // omitted null checks for brevity var list = new List(); foreach(var item in source) { if (selector.Invoke(item)) { list.Add(item); } else if (list.Count > 0) { yield return list; list = new List(); } } if (list.Count > 0) yield return list; } 

正如Jon Skeet所提到的,在这种情况下使用SkipWhileTakeWhile似乎效率很低,因为它们会在迭代器上的迭代器上创建迭代器。 您可以在调试示例时将其检查出来,当您逐步尝试查找下一个序列时它会变得有点疯狂,即使示例很简单也是如此。

我怀疑你的代码在所有情况下都不会真正起作用。 特别是,SkipWhile和TakeWhile被懒惰地评估 – 如果调用代码实际上没有读取所有产生的IEnumerables (或者更糟糕的是,缓冲它们并以不同的顺序读取它们!)我强烈怀疑你’会得到错误的结果。

我怀疑你真的需要这样做:

 public static IEnumerable> GetSequences( this IEnumerable source, Func selector) { List current = new List(); foreach (T element in source) { if (selector(element)) { current.Add(element); } else if (current.Count > 0) { yield return current; current = new List(); } } if (current.Count > 0) { yield return current; } } 

(这忽略了错误检查 – 由于迭代器块的延迟执行,您希望在一个单独的方法中执行此操作,然后将此方法作为私有方法调用 – 在编写生产质量迭代器块时,这是一种非常常见的模式。)

List的选择有点武断,顺便说一下 – 你当然可以使用LinkedList代替。 或者,如果您选择List,则可以返回IEnumerable>而不是IEnumerable> ,这可以使调用者更容易处理结果。

不确定我知道我在说什么,但这不会阻止我说话……

定义一种新的迭代器类型是否有意义,它可以在当前完成时跳到下一个子序列?

使用Java和Integer示例语法,客户端代码如下所示:

 Iterator2 iter = getSequencences(someCollection, someCriteria) while (iter.hasNextSequence()) { iter.nextSequence(); while (iter.hasNext()) { Integer i = iter.next(); } } 

*鸭子*

通过直接与枚举器一起工作,可以产生完全延迟的结果。

我可能会使用Garry和Jon提供的版本,因为它更简单并完成工作,但这很有趣。

 public static class Program { static IEnumerable> GetSequences(this IEnumerable source, Func selector) { var enumerator = source.GetEnumerator(); while (enumerator.MoveNext()) if (selector(enumerator.Current)) yield return TakeWhile(enumerator, selector); yield break; } static IEnumerable TakeWhile(IEnumerator enumerator, Func selector) { do { yield return enumerator.Current; } while (enumerator.MoveNext() && selector(enumerator.Current)); yield break; } static void Main() { var nums = new[] { 2, 1, 42, 0, 9, 6, 5, 3, 8 }; var seqs = nums.GetSequences(i => i < 6); Console.WriteLine(string.Join(",", seqs.Select(s => string.Format("{{{0}}}", string.Join(",", s.Select(i => i.ToString()).ToArray()) )).ToArray())); } } 

您可以利用GroupBy将按顺序运行项目的事实。

  public static IEnumerable> GetSequences (this IEnumerable source, Func predicate) { bool flag = false; int id = 0; return source.GroupBy(x => { bool match = predicate(x); if (match != flag) { id += 1; flag = match; } return new { keep = match, id = id }; }) .Where(g => g.Key.keep) .Select(g => g.AsEnumerable()); } 

这是一种测试方法:

  public static void Test1() { List myList = new List() {2,1,42,0,9,6,5,3,8}; foreach (var g in myList.GetSequences(i => i < 6)) { Console.WriteLine("g"); foreach (int i in g) { Console.WriteLine(i); } } }