8

假设您有一个返回惰性枚举对象的函数:

struct AnimalCount
{
    int Chickens;
    int Goats;
}

IEnumerable<AnimalCount> FarmsInEachPen()
{
    ....
    yield new AnimalCount(x, y);
    ....
}

您还有两个函数使用两个单独IEnumerable的 s,例如:

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多次打电话..

4

4 回答 4

4

我不认为有一种方法可以做你想做的事,因为ConsumeChickens(IEnumerable<int>)并且ConsumeGoats(IEnumerable<int>)被顺序调用,它们中的每一个都单独枚举一个列表 - 你如何期望它在没有两个单独的列表枚举的情况下工作?

根据情况,更好的解决方案是拥有ConsumeChicken(int)ConsumeGoat(int)方法(每个都消耗一个项目,并交替调用它们。像这样:

foreach(var animal in animals)
{
    ConsomeChicken(animal.Chickens);
    ConsomeGoat(animal.Goats);
}

这将仅枚举animals集合一次。


另外,请注意:根据您的 LINQ 提供者以及您正在尝试做什么,可能会有更好的选择。例如,如果您尝试使用 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)
}

将导致单个查询,并在数据库端进行求和,这比将整个表拉过来并在客户端进行求和要好得多。

于 2013-03-28T20:01:37.097 回答
2

你提出问题的方式,没有办法做到这一点。 IEnumerable<T>是一个可枚举的拉取——也就是说,你可以GetEnumerator到序列的前面,然后反复询问“给我下一个项目”(MoveNext/ Current)。您不能在一个线程上同时从animals.Select(a => a.Chickens)and中提取两种不同的东西animals.Select(a => a.Goats)。您必须先做一个,然后再做另一个(这需要实现第二个)。

BlueRaja 提出的建议是稍微改变问题的一种方法。我建议走那条路。

另一种选择是使用IObservable<T>Microsoft 的响应式扩展 (Rx),即推送可枚举的. 我不会详细说明你将如何做到这一点,但这是你可以研究的。

编辑:

以上假设ConsumeChickens并且ConsumeGoats都返回void或至少不返回IEnumerable<T>自己 - 这似乎是一个明显的假设。如果蹩脚的反对者真的发表评论,我将不胜感激。

于 2013-03-28T20:11:37.883 回答
2

实际上很简单的方法来实现您将 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)
}   
于 2013-03-28T21:21:57.207 回答
1

我想通了,这在很大程度上要归功于@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);
}
于 2013-03-28T20:21:55.793 回答