“Unzip"IEnumerable动态c#或最佳替代方案
本文关键字:最佳 方案 动态 Unzip quot IEnumerable | 更新日期: 2023-09-27 18:06:35
假设您有一个返回惰性枚举对象的函数:
struct AnimalCount
{
int Chickens;
int Goats;
}
IEnumerable<AnimalCount> FarmsInEachPen()
{
....
yield new AnimalCount(x, y);
....
}
您也有两个函数使用两个单独的IEnumerable
,例如:
ConsumeChicken(IEnumerable<int>);
ConsumeGoat(IEnumerable<int>);
你怎么能调用ConsumeChicken
和ConsumeGoat
没有a)转换FarmsInEachPen()
ToList()事先,因为它可能有两个无数的记录,b)没有多线程。
:
ConsumeChicken(FarmsInEachPen().Select(x => x.Chickens));
ConsumeGoats(FarmsInEachPen().Select(x => x.Goats));
但不强制双枚举。
我可以用多线程来解决这个问题,但是每个列表都有一个缓冲队列,这会变得不必要的复杂。
所以我正在寻找一种方法将AnimalCount
枚举数拆分为两个int
枚举数,而不完全评估AnimalCount
。在锁步中同时运行ConsumeGoat
和ConsumeChicken
没有问题。
我能感觉到解决办法就在我的掌握之外,但我还没有完全做到。我正在考虑一个辅助函数,它返回一个IEnumerable
被输入到ConsumeChicken
中,每次使用迭代器时,它都会在内部调用ConsumeGoat
,从而在锁步中执行两个函数。当然,除非我不想多次调用ConsumeGoat
.
我不认为有一种方法可以做你想做的事情,因为ConsumeChickens(IEnumerable<int>)
和ConsumeGoats(IEnumerable<int>)
被顺序调用,它们每个单独枚举一个列表-如果没有两个单独的列表枚举,你怎么能期望它工作?
根据具体情况,更好的解决方案是使用ConsumeChicken(int)
和ConsumeGoat(int)
方法(它们各自消耗一个单个项),并交替调用它们。这样的:
foreach(var animal in animals)
{
ConsomeChicken(animal.Chickens);
ConsomeGoat(animal.Goats);
}
这将只枚举一次animals
集合。
另外,注意:根据你的LINQ-provider和你想要做的事情,可能有更好的选择。例如,如果您试图使用linq-to-sql或linq-to-entities从数据库中获取鸡和山羊的总数,那么下面的查询..
from a in animals
group a by 0 into g
select new
{
TotalChickens = g.Sum(x => x.Chickens),
TotalGoats = g.Sum(x => x.Goats)
}
将产生一个查询,并在数据库端进行求和,这比将整个表拉过来并在客户端进行求和要好得多。
你提出问题的方式,没有办法做到这一点。IEnumerable<T>
是一个拉枚举——也就是说,你可以把GetEnumerator
放到序列的前面,然后反复问"给我下一个项目"(MoveNext
/Current
)。你不能,在一个线程上,有两个不同的东西从animals.Select(a => a.Chickens)
和animals.Select(a => a.Goats)
在同一时间拉。你必须先做一件,然后再做另一件(这就需要实现第二件事)。
BlueRaja提出的建议是稍微改变问题的一种方法。我建议走那条路。
另一种选择是利用来自Microsoft的响应式扩展(Rx)的IObservable<T>
,一个push enumerable。我不会详细说明你会怎么做,但这是你可以研究的东西。
编辑:
上面假设ConsumeChickens
和ConsumeGoats
都返回void
,或者至少它们自己不返回IEnumerable<T>
——这似乎是一个显而易见的假设。如果那个蹩脚的评论者能发表评论的话,我会很感激的。
实际上简单的方法来实现你是什么是转换FarmsInEachPen返回值推送集合或IObservable和使用ReactiveExtensions与它工作
var observable = new Subject<Animals>()
observable.Do(x=> DoSomethingWithChicken(x. Chickens))
observable.Do(x=> DoSomethingWithGoat(x.Goats))
foreach(var item in FarmsInEachPen())
{
observable.OnNext(item)
}
我明白了,这在很大程度上要归功于@Lee给我指明的道路。
您需要在两个zip之间共享单个枚举器,并使用适配器函数将正确的元素投影到序列中。
private static IEnumerable<object> ConsumeChickens(IEnumerable<int> xList)
{
foreach (var x in xList)
{
Console.WriteLine("X: " + x);
yield return null;
}
}
private static IEnumerable<object> ConsumeGoats(IEnumerable<int> yList)
{
foreach (var y in yList)
{
Console.WriteLine("Y: " + y);
yield return null;
}
}
private static IEnumerable<int> SelectHelper(IEnumerator<AnimalCount> enumerator, int i)
{
bool c = i != 0 || enumerator.MoveNext();
while (c)
{
if (i == 0)
{
yield return enumerator.Current.Chickens;
c = enumerator.MoveNext();
}
else
{
yield return enumerator.Current.Goats;
}
}
}
private static void Main(string[] args)
{
var enumerator = GetAnimals().GetEnumerator();
var chickensList = ConsumeChickens(SelectHelper(enumerator, 0));
var goatsList = ConsumeGoats(SelectHelper(enumerator, 1));
var temp = chickensList.Zip(goatsList, (i, i1) => (object) null);
temp.ToList();
Console.WriteLine("Total iterations: " + iterations);
}