嵌套产生返回 IEnumerable> 并带有惰性求值

2024-01-13

我写了一个LINQ扩展方法SplitBetween类似于String.Split.

> new List<int>(){3,4,2,21,3,2,17,16,1}
> .SplitBetween(x=>x>=10)

[3,4,2], [3,2], [], [1]

Source:

// partition sequence into sequence of contiguous subsequences
// behaves like String.Split
public static IEnumerable<IEnumerable<T>> SplitBetween<T>(this IEnumerable<T> source, 
                                                          Func<T, bool> separatorSelector, 
                                                          bool includeSeparator = false)
{
    var l = new List<T>();
    foreach (var x in source)
    {
        if (separatorSelector(x))
        {
            if (includeSeparator)
            {
                l.Add(x);
            }
            yield return l;
            l = new List<T>();
        }
        else
        {
            l.Add(x);
        }
    }
    yield return l;
}

本着 LINQ 的精神,我认为这个方法应该进行惰性评估。然而,我的实现对外部 IEnumerable 进行了惰性评估,但是不超过内部 IEnumerable。我怎样才能解决这个问题?

外部行为如何是懒惰的演示。认为ThrowingEnumerable<int> is an IEnumerable<int>当任何人试图迭代它时它就会爆炸(参见 Skeet 的 Edulinq)。

(new List<int>(){1,2,3,10,1})
.Concat(Extensions.ThrowingEnumerable<int>())
.SplitBetween(x=>x>=10)
.First().ToList();

[1,2,3]

但内心的行为不懒

(new List<int>(){1,2,3,10,1})
.Concat(Extensions.ThrowingEnumerable<int>())
.SplitBetween(x=>x>=10)
.ElementAt(2).First();

BOOM

我们期望这里有 1。


Edit:你的方法没有任何问题,除了当你枚举它时,抛出可枚举确实会“繁荣”。这就是它的目的。它没有一个合适的GetEnumerator对其进行定义。所以你的代码没有表现出真正的问题。在第一种情况下,通过这样做First,你只是枚举直到第一个结果集(只是{ 1, 2, 3 } )被获得并且没有枚举抛出可枚举(这意味着Concat没有被执行)。但在第二个示例中,您要求的元素位于2分割后,这意味着它也会枚举投掷可枚举,并且会“繁荣”。这里的关键是要理解ElementAt 枚举集合直到索引要求并且本质上不是懒惰的(它不可能是)。

我不确定完全懒惰是否是正确的选择。问题在于,惰性分割为外部序列和内部序列的整个过程在一个枚举器上运行,这可能会根据枚举器状态产生不同的结果。例如,您仅枚举外部序列,内部序列将不再是您所期望的。或者,如果只枚举一半的外部序列和一个内部序列,那么其他内部序列的状态会是什么?你的方法是最好的。

下面的方法是懒惰的(仍然会繁荣,因为这是有保证的),因为它不使用中间具体实现,但可能比原来的方法慢,因为它多次遍历列表:

public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, 
                                                     Func<T, bool> separatorPredicate, 
                                                     bool includeEmptyEntries = false,
                                                     bool includeSeparators = false)
{
    int prevIndex = 0;
    int lastIndex = 0;
    var query = source.Select((t, index) => { lastIndex = index; return new { t, index }; })
                      .Where(a => separatorPredicate(a.t));
    foreach (var item in query)
    {
        if (item.index == prevIndex && !includeEmptyEntries)
        {
            prevIndex++;
            continue;
        }

        yield return source.Skip(prevIndex)
                           .Take(item.index - prevIndex + (!includeSeparators ? 0 : 1));
        prevIndex = item.index + 1;
    }

    if (prevIndex <= lastIndex)
        yield return source.Skip(prevIndex);
}

总的来说,你原来的方法是最好的。如果您需要完全懒惰的东西,那么我的以下答案很合适。请注意,它仅适用于以下情况:

foreach (var inners in outer)
    foreach (var item in inners)
    { 
    }

and not

var outer = sequence.Split;
var inner1 = outer.First;
var inner2 = outer.ElementAt; //etc

换句话说,不适合同一内部序列的多次迭代。如果您完全意识到这种危险的构造:


原答案:

这不使用中间具体集合,不ToList在源可枚举上,并且完全是惰性/迭代器式的:

public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source,
                                                     Func<T, bool> separatorPredicate,
                                                     bool includeEmptyEntries = false,
                                                     bool includeSeparator = false)
{
    using (var x = source.GetEnumerator())
        while (x.MoveNext())
            if (!separatorPredicate(x.Current))
                yield return x.YieldTill(separatorPredicate, includeSeparator);
            else if (includeEmptyEntries)
            {
                if (includeSeparator)
                    yield return Enumerable.Repeat(x.Current, 1);
                else
                    yield return Enumerable.Empty<T>();
            }
}

static IEnumerable<T> YieldTill<T>(this IEnumerator<T> x, 
                                   Func<T, bool> separatorPredicate,
                                   bool includeSeparator)
{
    yield return x.Current;

    while (x.MoveNext())
        if (!separatorPredicate(x.Current))
            yield return x.Current;
        else
        {
            if (includeSeparator)
                yield return x.Current;
            yield break;
        }
}

简短、甜蜜、简单。我添加了一个附加标志来表示是否要返回空集(默认情况下它会忽略)。如果没有这个标志,代码会更加简洁。

感谢您提出这个问题,这将在我的扩展方法库中! :)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌套产生返回 IEnumerable> 并带有惰性求值 的相关文章

  • 在 OpenCL 中将函数作为参数传递

    是否可以在 OpenCL 1 2 中将函数指针传递给内核 我知道可以用C实现 但不知道如何在OpenCL的C中实现 编辑 我想做这篇文章中描述的同样的事情 在 C 中如何将函数作为参数传递 https stackoverflow com q
  • 捕获 foreach 条件中抛出的异常

    我有一个foreach在 foreach 本身的条件下循环期间中断的循环 有没有办法try catch抛出异常然后继续循环的项 这将运行几次 直到异常发生然后结束 try foreach b in bees exception is in
  • 处理 fanart.tv Web 服务响应 JSON 和 C#

    我正在尝试使用 fanart tv Webservice API 但有几个问题 我正在使用 Json Net Newtonsoft Json 并通过其他 Web 服务将 JSON 响应直接反序列化为 C 对象 这里的问题是元素名称正在更改
  • 在 Xcode4 中使用 Boost

    有人设置 C Xcode4 项目来使用 Boost 吗 对于一个简单的 C 控制台应用程序 我需要在 Xcode 中设置哪些设置 Thanks 用这个来管理它 和这个
  • TextBox 焦点的 WinForms 事件?

    我想添加一个偶数TextBox当它有焦点时 我知道我可以用一个简单的方法来做到这一点textbox1 Focus并检查布尔值 但我不想那样做 我想这样做 this tGID Focus new System EventHandler thi
  • 转到 C# WPF 中的第一页

    我正在 WPF 中使用导航服务 为了导航到页面 我使用 this NavigationService Navigate new MyPage 为了返回我使用 this NavigationService GoBack 但是如何在不使用的情况
  • 是否有与 C++11 emplace/emplace_back 函数类似的 C# 函数?

    从 C 11 开始 可以写类似的东西 include
  • 范围和临时初始化列表

    我试图将我认为是纯右值的内容传递到范围适配器闭包对象中 除非我将名称绑定到初始值设定项列表并使其成为左值 否则它不会编译 这里发生了什么 include
  • 事件日志写入错误

    很简单 我想向事件日志写入一些内容 protected override void OnStop TODO Add code here to perform any tear down necessary to stop your serv
  • 用于从字符串安全转换的辅助函数

    回到 VB6 我编写了一些函数 让我在编码时无需关心字符串的 null 和 数字的 null 和 0 等之间的区别 编码时 没有什么比添加特殊情况更能降低我的工作效率了用于处理可能导致一些不相关错误的数据的代码 9999 10000 如果我
  • “MyClass”的类型初始值设定项引发异常

    以下是我的Windows服务代码 当我调试代码时 我收到错误 异常 CSMessageUtility CSDetails 的类型初始值设定项引发异常 using System using System Collections Generic
  • 如何排列表格中的项目 - MVC3 视图 (Index.cshtml)

    我想使用 ASP NET MVC3 显示特定类型食品样本中存在的不同类型维生素的含量 如何在我的视图 Index cshtml 中显示它 an example 这些是我的代码 table tr th th foreach var m in
  • 过期时自动重新填充缓存

    我当前缓存方法调用的结果 缓存代码遵循标准模式 如果存在 则使用缓存中的项目 否则计算结果 在返回之前将其缓存以供将来调用 我想保护客户端代码免受缓存未命中的影响 例如 当项目过期时 我正在考虑生成一个线程来等待缓存对象的生命周期 然后运行
  • 是否有一个 C++ 库可以从 PDF 文件中提取文本,例如 PDFBox for Java? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 去年 我使用 PDFBox 在 Java 中创建了一个应用程序来获取某些 PDF 文件中的原始文本 现在
  • 为什么我使用google'smtp'无法发送电子邮件?

    我有以下程序使用 smtp gmail com 587 发送电子邮件 namespace TestMailServer class Program static void Main string args MailMessage mail
  • .NET UI 元素线程限制的原因

    我们知道 除了实例化元素的线程之外 不可能从任何线程执行操作任何 UI 元素属性的代码 我的问题是 为什么 我记得当我们使用 COM 用户界面元素时 在 COM Visual Basic 6 0 时代 所有 UI 元素都是使用 COM 类和
  • 如何打开 Windows 资源管理器窗口并选择特定文件夹

    我有一个 winform 应用程序 这个 winform 应用程序创建了几个文件 我想在我的应用程序中实现 查找目标 功能来显示这些文件 这些文件位于同一文件夹中 看图片 假设我创建了几个文件 C Test 文件夹 该文件夹包含以下文件 C
  • 热重载时调用方法

    我正在使用 Visual Studio 2022 和 C 制作游戏 我想知道当您热重新加载应用程序 当它正在运行时 时是否可以触发一些代码 我基本上有 2 个名为 UnloadLevel 和 LoadLevel 的方法 我想在热重载时执行它
  • Swagger 为 ASP.CORE 3 中的字典生成错误的 URL

    当从查询字符串中提取的模型将字典作为其属性之一时 Swagger 会生成不正确的 URL 如何告诉 Swagger 更改 URL 中字典的格式或手动定义输入参数模式而不自动生成 尝试使用 Swashbuckle 和 NSwag 控制器 pu
  • Azure函数版本2.0-应用程序blobTrigger不工作

    我有一个工作功能应用程序 它有一个 blob 输入和一个事件中心输出 在测试版中工作 随着最新的更改 我的功能不再起作用 我尝试根据发行说明更新 host json 文件 但它没有引用 blob 触发器 version 2 0 extens

随机推荐