LINQ 的迭代器块

发布于 2024-09-19 12:01:03 字数 893 浏览 8 评论 0原文

我很难找到正确的 LINQ 语法用于以下迭代器块:

class Program
{
    class Operation
    {
        public IEnumerable<Operation> NextOperations { get; private set; }
    }
    class Item { }

    static Item GetItem(Operation operation)
    {
        return new Item();
    }

    static IEnumerable<Item> GetItems(IEnumerable<Operation> operations)
    {
        foreach (var operation in operations)
        {
            yield return GetItem(operation);

            foreach (var item in GetItems(operation.NextOperations))  // recursive
                yield return item;
        }
    }

    static void Main(string[] args)
    {
        var operations = new List<Operation>();
        foreach (var item in GetItems(operations))
        {
        }
    }
}

也许我所拥有的已经尽善尽美了?对于这个特定的代码,显式 foreach 内的 yield return 确实是正确的解决方案?

I'm having a hard time finding the right LINQ syntax to use for the following iterator block:

class Program
{
    class Operation
    {
        public IEnumerable<Operation> NextOperations { get; private set; }
    }
    class Item { }

    static Item GetItem(Operation operation)
    {
        return new Item();
    }

    static IEnumerable<Item> GetItems(IEnumerable<Operation> operations)
    {
        foreach (var operation in operations)
        {
            yield return GetItem(operation);

            foreach (var item in GetItems(operation.NextOperations))  // recursive
                yield return item;
        }
    }

    static void Main(string[] args)
    {
        var operations = new List<Operation>();
        foreach (var item in GetItems(operations))
        {
        }
    }
}

Maybe what I have is as good as it gets? For this particular code, yield return inside an explicit foreach is indeed the right solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

等待我真够勒 2024-09-26 12:01:21

我认为你的实施很好。但是,如果您想使用 LINQ(并使其稍微短一点,但不是显着缩短),那么您可以使用迭代所有操作并返回当前项和所有操作的查询来实现 GetItems其他递归生成的项目:

static IEnumerable<Item> GetItems(IEnumerable<Operation> operations) 
{ 
    return from op in operations
           from itm in (new[] { GetItem(op) }).Concat
                       (GetItems(op.NextOperations));
           select itm;
} 

对于每个操作,我们生成一个序列,其中包含当前操作的项目,后跟所有递归生成的项目。通过使用嵌套的 from 子句,您可以迭代此集合以获得“平面”结构。

我认为您可以通过使用支持“将元素附加到前面”操作的功能性(不可变)列表来使其变得更好 - 这正是我们在嵌套 from 中所做的。使用我的函数式编程书中的FuncList(这不再是懒惰的)但序列):

static FuncList<Item> GetItems(IEnumerable<Operation> operations) 
{ 
    return (from op in operations
            from itm in FuncList.Cons(GetItem(op), GetItems(op.NextOperations));
            select itm).AsFuncList();
} 

正如 Jon 提到的,没有使用查询编写递归方面的好方法(您可以使用 lambda 函数而不是方法来编写递归查询 - 但这也好不了多少)。

I think that your implementation is good. However, if you want to use LINQ (and make it a little bit - but not significantly - shorter), then you can implement GetItems using a query that iterates over all operations and returns current item followed by all other recursively generated items:

static IEnumerable<Item> GetItems(IEnumerable<Operation> operations) 
{ 
    return from op in operations
           from itm in (new[] { GetItem(op) }).Concat
                       (GetItems(op.NextOperations));
           select itm;
} 

For each operation, we generate a sequence containing the item for the current one followed by all recursively generated items. By using a nested from clause, you can iterate over this collection to get a "flat" structure.

I think you could make it a bit nicer by using a functional (immutable) list which supports operation for "appending an element to the front" - which is exactly what we're doing in the nested from. Using FuncList from my functional programming book (this is no longer lazy sequence though):

static FuncList<Item> GetItems(IEnumerable<Operation> operations) 
{ 
    return (from op in operations
            from itm in FuncList.Cons(GetItem(op), GetItems(op.NextOperations));
            select itm).AsFuncList();
} 

As Jon mentioned, there is no good way of writing the recursive aspect using query (you can write recursive query using lambda functions instead of methods - but that's not much better).

南烟 2024-09-26 12:01:18

LINQ 通常不擅长使用标准查询运算符进行递归。您可以编写上述内容的更通用的形式,但您找不到执行此遍历的简洁标准 LINQ 方式。

LINQ isn't generally good at recursion, using the standard query operators. You could write a more general purpose form of the above, but you won't find a neat standard LINQ way of doing this traversal.

清眉祭 2024-09-26 12:01:15

也许我所拥有的已经尽善尽美了?

非常好。我们可以让它稍微好一点。

对于这个特定的代码,在显式 foreach 中yield return 确实是正确的解决方案?

这是一个合理的解决方案。它很容易阅读并且清晰正确。正如我之前提到的,缺点是如果树非常深,性能可能会不好。

我将这样做:

static IEnumerable<T> AllNodes(this T root, Func<T, IEnumerable<T>> getChildren) 
{
    var stack = new Stack<T>();
    stack.Push(root);
    while(stack.Count > 0)
    {
        var current = stack.Pop();
        yield return current;
        foreach(var child in getChildren(current).Reverse())
            stack.Push(child);
    }
} 

static void Main()      
{      
    var operation = whatever;
    var items = from op in operation.AllNodes(x=>x.NextOperations)
                select GetItem(op);
    foreach (var item in items)      
    {      
    }      
} 

请注意,仅当您关心迭代“按顺序”进行时,才需要调用 Reverse() 。例如,假设操作 Alpha 具有子操作 Beta、Gamma 和 Delta,并且 Delta 具有子操作 Zeta 和 Omega。遍历是这样的:

push Alpha
pop Alpha
yield Alpha
push Delta
push Gamma 
push Beta
pop Beta
yield Beta
pop Gamma
yield Gamma
pop Delta
yield Delta
push Omega
push Zeta
pop Zeta
yield Zeta
pop Omega
yield Omega

现在堆栈是空的,所以我们完成了,并且我们以“预序遍历”顺序获取项目。如果你不关心顺序,如果你需要的只是确保获得所有这些,那么就不必费心反转子级,你会按照 Alpha、Delta、Omega、Zeta 的顺序获得它们,伽玛,贝塔。

有道理吗?

Maybe what I have is as good as it gets?

It's pretty good. We can make it slightly better.

For this particular code, yield return inside an explicit foreach is indeed the right solution?

It's a reasonable solution. It's easy to read and clearly correct. The down side is, as I mentioned earlier, that the performance is potentially not good if the tree is extremely deep.

Here's how I would do this:

static IEnumerable<T> AllNodes(this T root, Func<T, IEnumerable<T>> getChildren) 
{
    var stack = new Stack<T>();
    stack.Push(root);
    while(stack.Count > 0)
    {
        var current = stack.Pop();
        yield return current;
        foreach(var child in getChildren(current).Reverse())
            stack.Push(child);
    }
} 

static void Main()      
{      
    var operation = whatever;
    var items = from op in operation.AllNodes(x=>x.NextOperations)
                select GetItem(op);
    foreach (var item in items)      
    {      
    }      
} 

Note that the call to Reverse() is necessary only if you care that the iteration go "in order". For example, suppose operation Alpha has child operations Beta, Gamma and Delta, and Delta has children Zeta and Omega. The traversal goes like this:

push Alpha
pop Alpha
yield Alpha
push Delta
push Gamma 
push Beta
pop Beta
yield Beta
pop Gamma
yield Gamma
pop Delta
yield Delta
push Omega
push Zeta
pop Zeta
yield Zeta
pop Omega
yield Omega

and now the stack is empty so we're done, and we get the items in "preorder traversal" order. If you don't care about the order, if all you need is to make sure you get all of them, then don't bother to Reverse the children, and you'll get them in the order Alpha, Delta, Omega, Zeta, Gamma, Beta.

Make sense?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文