【发布时间】:2020-04-01 03:59:37
【问题描述】:
是否可以编写一个高阶函数,使IEnumerable 被多次使用,但只在一次传递中,而不会将所有数据读入内存? [请参阅下面的编辑以澄清我正在寻找的内容。]
例如,在下面的代码中,可枚举是mynums(我在上面标记了.Trace(),以便查看我们枚举它的次数)。目标是弄清楚它是否有任何大于 5 的数字,以及所有数字的总和。处理一个可枚举两次的函数是Both_TwoPass,但它会枚举它两次。相比之下,Both_NonStream 只枚举了一次,但代价是将其读入内存。原则上,可以像Any5Sum 所示以单次传递和流式传输方式执行这两项任务,但这是特定的解决方案。是否可以编写一个与Both_* 具有相同签名的函数,但这是两全其美?
(在我看来,这应该可以使用线程来实现。有没有更好的解决方案,比如使用async?)
编辑
以下是关于我正在寻找的内容的说明。我所做的是在方括号中对每个属性进行了非常实际的描述。
我正在寻找具有以下特征的函数Both:
- 它有签名
(S1, S2) Both<T, S1, S2>(this IEnumerable<T> tt, Func<IEnumerable<T>, S1>, Func<IEnumerable<T>, S2>)(并产生“正确”的输出!) - 它只迭代第一个参数
tt一次。 [我的意思是当通过mynums(定义如下)时,它只输出mynums: 0 1 2 ...once。这排除了函数Both_TwoPass。] - 它以流方式处理来自第一个参数
tt的数据。 [我的意思是,例如,没有足够的内存将tt中的所有项目同时存储在内存中,因此排除了函数Both_NonStream。]
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp
{
static class Extensions
{
public static IEnumerable<T> Trace<T>(this IEnumerable<T> tt, string msg = "")
{
Console.Write(msg);
try
{
foreach (T t in tt)
{
Console.Write(" {0}", t);
yield return t;
}
}
finally
{
Console.WriteLine('.');
}
}
public static (S1, S2) Both_TwoPass<T, S1, S2>(this IEnumerable<T> tt, Func<IEnumerable<T>, S1> f1, Func<IEnumerable<T>, S2> f2)
{
return (f1(tt), f2(tt));
}
public static (S1, S2) Both_NonStream<T, S1, S2>(this IEnumerable<T> tt, Func<IEnumerable<T>, S1> f1, Func<IEnumerable<T>, S2> f2)
{
var tt2 = tt.ToList();
return (f1(tt2), f2(tt2));
}
public static (bool, int) Any5Sum(this IEnumerable<int> ii)
{
int sum = 0;
bool any5 = false;
foreach (int i in ii)
{
sum += i;
any5 |= i > 5; // or: if (!any5) any5 = i > 5;
}
return (any5, sum);
}
}
class Program
{
static void Main()
{
var mynums = Enumerable.Range(0, 10).Trace("mynums:");
Console.WriteLine("TwoPass: (any > 5, sum) = {0}", mynums.Both_TwoPass(tt => tt.Any(k => k > 5), tt => tt.Sum()));
Console.WriteLine("NonStream: (any > 5, sum) = {0}", mynums.Both_NonStream(tt => tt.Any(k => k > 5), tt => tt.Sum()));
Console.WriteLine("Manual: (any > 5, sum) = {0}", mynums.Any5Sum());
}
}
}
【问题讨论】:
-
在您的第一句话中,我认为您的意思是
IEnumerable的 元素 被多次使用(处理),而IEnumerable仅被使用/枚举一次。至于你关于线程和async的括号,我认为只枚举一次是一个单独的目标,而不是处理枚举数据的线程。我不确定线程是否简化了这里的任何事情。您必须让一个线程进行枚举并将数据排队以供每个处理线程处理,这只有在该处理比您在此处显示的 CPU 密集度高得多的情况下才有意义。 -
线程可能的工作方式大致如下:创建两个由
tt支持的专用IEnumerables、tt1和tt2,然后启动一个线程来执行f1(tt1)和另一个执行f2(tt2)(并在完成后收集结果。tt1和tt2将在tt处生成相同的项目,但重要的是,它们将被互锁,因此它们会导致一个项目从tt中读取,tt1和tt2中的每一个都会产生该项目。我想我可能只需要尝试写一下就知道它到底有多可行。
标签: c# ienumerable