代码之家  ›  专栏  ›  技术社区  ›  Razor

递归LINQ调用

  •  15
  • Razor  · 技术社区  · 14 年前

    我正在尝试用父子关系构建一些数据的XML树,但是在同一个表中。

    两个重要领域是

    竞争性 父母竞争

    一些数据可能是

    竞争性d=1, parentCompetitionID=空

    竞争性d=2, 家长竞争ID=1

    竞争性d=3, 家长竞争ID=1

    我所得到的中断查询只是以平面格式显示结果。鉴于我正在使用XML,需要某种递归功能。我可以使用正常的for循环递归来完成这项工作,但希望看到linq版本。感谢您的帮助。

    var results = 
            from c1 in comps
            select new {
                c.CompetitionID,
                SubComps=
                    from sc in comps.Where (c2 => c2.CompetitionID == c1.CompetitionID)
                    select sc
            };
    

    更新

    我找到了一篇有趣的文章 here 这演示了如何递归调用lambda委托。这是密码。谢谢克里斯!

    Func<int, int> factoral = x => x <= 1 ? 1 : x + factoral(--x);
    
    Func<int, int> factoral = null;
    
    factoral = x => x <= 1 ? 1 : x + factoral(--x);
    

    ^添加代码格式以显示lamba函数 技巧是先将空值赋给func委托。

    6 回复  |  直到 11 年前
        1
  •  6
  •   Fedor    14 年前

    不知道如何编写递归LINQ。但我认为这里不需要递归。一棵树只需两步就可以建成:

    Dictionary<int, Competition> dic = comps.ToDictionary(e => e.CompetitionID);
    foreach (var c in comps)
        if (dic.ContainsKey(c.ParentCompetitionID))
            dic[c.ParentCompetitionID].Children.Add(c);
    var root = dic[1];
    

    根变量现在包含完整的树。

    以下是要测试的完整示例:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace ConsoleApplication2
    {
        class Competition
        {
            public int CompetitionID;
            public int ParentCompetitionID;
            public List<Competition> Children=new List<Competition>();
            public Competition(int id, int parent_id) 
            { 
                CompetitionID = id; 
                ParentCompetitionID = parent_id; 
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                List<Competition> comps = new List<Competition>()
                {
                    new Competition(1, 0), 
                    new Competition(2,1),
                    new Competition(3,1),
                    new Competition(4,2),
                    new Competition(5,3)
                };
    
                Dictionary<int, Competition> dic = comps.ToDictionary(e => e.CompetitionID);
                foreach (var c in comps)
                    if (dic.ContainsKey(c.ParentCompetitionID))
                        dic[c.ParentCompetitionID].Children.Add(c);
                var root = dic[1];
            }
        }
    }
    
        2
  •  2
  •   asgerhallas    14 年前

    我知道我来这里有点晚了。但您说过您已经有了一个使用foreach:)的版本,所以如果它实际上应该是递归的并使用linq,这将是一个解决方案:

    internal class Competition
    {
        public int CompetitionID;
        public int ParentCompetitionID;
    
        public Competition(int id, int parentId)
        {
            CompetitionID = id;
            ParentCompetitionID = parentId;
        }
    }
    
    internal class Node
    {
        public Node(int id, IEnumerable<Node> children)
        {
            Children = children;
            Id = id;
        }
    
        public IEnumerable<Node> Children { get; private set; }
        public int Id { get; private set; }
    }
    
    internal class Program
    {
        static void Main(string[] args)
        {
            var comps = new List<Competition>
                            {
                                new Competition(1, 0),
                                new Competition(2, 1),
                                new Competition(3, 1),
                                new Competition(4, 2),
                                new Competition(5, 3)
                            };
    
            Node root = ToTree(0, comps);
        }
    
        static readonly Func<int, IEnumerable<Competition>, Node> ToTree = 
            (nodeId, competitions) => new Node(nodeId, from c in competitions where c.ParentCompetitionID == nodeId select ToTree(c.CompetitionID, competitions));
    }
    
        3
  •  1
  •   Iñaki Elcoro    14 年前

    您可以得到一个类似树的结构,将LINQ和递归与委托结合在一起。在这个示例中,我使用这样的XML结构:

    <Competitions>
      <Competition ID="1" />
      <Competition ID="2" ParentCompetitionID="1" />
      <Competition ID="3" ParentCompetitionID="1" />
      <Competition ID="4" />
    </Competitions>
    

    因此,要在代码中存储节点数据并方便导航,请创建如下类:

    class Competition
    {
       public int CompetitionID { get; set; }
    
       public IEnumerable<Competition> Childs { get; set; }
    }
    

    现在,使用linq-to-xml将XML文件加载到一个xdocument中。在此之后,声明一个委托,该委托迭代文档中的所有XML元素,选择具有与委托的ID参数匹配的ID的节点。选择每个节点时,它再次调用委托,传递要查找的父节点的ID。它首先将id参数设置为空,因此,选择firts根节点:

        var doc = XDocument.Load("tree.xml");
    
        //Declare the delegate for using it recursively
        Func<int?, IEnumerable<Competition>> selectCompetitions = null;
    
        selectCompetitions = (int? id) =>
        {
           return doc.Elements("Competitions").Elements().Where(c => 
           {
             //If id is null return only root nodes (without ParentCompetitionID attribute)
             if (id == null)
                return c.Attribute("ParentCompetitionID") == null;
             else
                //If id has value, look for nodes with that parent id
                return  c.Attribute("ParentCompetitionID") != null &&
                        c.Attribute("ParentCompetitionID").Value == id.Value.ToString();
            }).Select(x => new Competition() 
                           { 
                          CompetitionID = Convert.ToInt32(x.Attribute("ID").Value),
                          //Always look for childs with this node id, call again to this
                          //delegate with the corresponding ID
                          Childs = selectCompetitions(Convert.ToInt32(x.Attribute("ID").Value))
                           });
    };
    
    var competitions = selectCompetitions(null);
    

    要测试它,您可以执行一个简单的循环方法,将树打印到控制台:

    private static void Write(IEnumerable<Competition> competitions, int indent)
    {
       foreach (var c in competitions)
       {
           string line = String.Empty;
    
           for (int i = 0; i < indent; i++)
           {
              line += "\t";
           }
    
           line += "CompetitionID = " + c.CompetitionID.ToString();
    
           Console.WriteLine(line);
    
           if (c.Childs != null && c.Childs.Count() > 0)
           {
               int id = indent + 1;
               Write(c.Childs, id);
            }
       }
    }
    

    希望有帮助!

        4
  •  0
  •   Joseph Yaduvanshi    14 年前

    我用LINQ做了一些非常相似的事情 group by

    我不使用Linq的查询语法,如果这是错误的,请原谅我:

    var results = from c in comps
        group c by c.ParentCompetitionID into g
        select new { ParentId = g.Key, ChildId = g };
    

    当然,如果您的类看起来像:

    class Competition {
       int Id;
       string Description;
       Competition ParentCompetition;
    }
    

    然后,您可以按整个竞争对手分组,而不是仅按ID分组,这使得生成XML变得更快、更容易。

    var results = from c in comps
        group c by c.ParentCompetition into g
        select new { Parent = g.Key, Child = g };
    
        5
  •  0
  •   Allon Guralnek    14 年前
    class Competition
    { 
       int ID { get; set;}
       int ParentID { get; set; }
       IEnumerable<Competition> Children { get; set; } 
    }
    
    public IEnumerable<Competition> GetChildren(
       IEnumerable<Competition> competitions, int parentID)
    {
       IEnumerable<Competition> children =
          competitions.Where(c => c.ParentID == parentID);
    
       if (children.Count() == 0)
          return null; 
    
       return children.Select(
          c => new Competition { ID = c.ID, Children = GetChildren(c.ID) };
    }
    

    然后您只需调用getchildren,将根的ID作为parentID传递,这将返回一个树结构。您也可以更改 Competition 对象到您选择的XML API。

    我知道这不是你要找的,但是Afaik Linq不支持递归。然而,linq的lin部分意味着语言集成,这正是我使用的。

        6
  •  0
  •   leppie    14 年前

    虽然不能使用单个查询来完成此操作(除非您使用CTE直接调用SQL),但可以将查询数限制在树的深度。

    代码太长,无法粘贴,但基本步骤如下:

    1. 收集根节点并添加到“所有”节点
    2. 收集父节点位于“所有”节点中的节点(将列表传递给查询)
    3. 将步骤2中的节点添加到“所有”节点
    4. 重复2-3,直到步骤2返回0个节点(我想应该是树的深度+1)。

    您可以最小化在步骤2中传递给查询的节点数量。SQL Server倾向于使用超过2000个条目的列表进行轰炸。(不过,SQL Compact没有这样的问题)。