“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>);

你怎么能调用ConsumeChickenConsumeGoat没有a)转换FarmsInEachPen() ToList()事先,因为它可能有两个无数的记录,b)没有多线程。

基本上

:

ConsumeChicken(FarmsInEachPen().Select(x => x.Chickens));
ConsumeGoats(FarmsInEachPen().Select(x => x.Goats));

但不强制双枚举。

我可以用多线程来解决这个问题,但是每个列表都有一个缓冲队列,这会变得不必要的复杂。

所以我正在寻找一种方法将AnimalCount枚举数拆分为两个int枚举数,而不完全评估AnimalCount。在锁步中同时运行ConsumeGoatConsumeChicken没有问题。

我能感觉到解决办法就在我的掌握之外,但我还没有完全做到。我正在考虑一个辅助函数,它返回一个IEnumerable被输入到ConsumeChicken中,每次使用迭代器时,它都会在内部调用ConsumeGoat,从而在锁步中执行两个函数。当然,除非我不想多次调用ConsumeGoat .

“Unzip"IEnumerable动态c#或最佳替代方案

我不认为有一种方法可以做你想做的事情,因为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。我不会详细说明你会怎么做,但这是你可以研究的东西。

编辑:

上面假设ConsumeChickensConsumeGoats都返回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);
}