【问题标题】:What is the best way to get the level from a tree data structure using LINQ?使用 LINQ 从树数据结构中获取关卡的最佳方法是什么?
【发布时间】:2011-03-21 11:41:45
【问题描述】:

我有一个集合演示了一个树形数据结构,它的节点是:

Node:
Id
Name
ParentID

现在,我想为这个集合中的每个节点获取level,我尝试使用以下代码,但我想知道这是否是实现它的最佳方式。

Func<int, int> GetLevel = (nodeID) =>
{
    int _level = 0;
    var _node = source.First(p => p.Id == nodeID);

    // while hasn't reached the root yet
    while (_node .ParentID.HasValue)
    {
        _level++;
        _node = source.First(p => p.Id == _node.ParentID);
    }
    return _level;
};

// source is a collection of Node.

var query =   from node in source
              select new
              {
                  Id = node.Id,
                  Name = node.Name,
                  ParentID = node.ParentID,
                  Level = GetLevel(node.Id)
              };

我认为GetLevel 函数的开销能够减少。或者也许有更好的方法可以不用这个函数直接获取!

任何想法!

【问题讨论】:

    标签: c# linq linq-to-objects


    【解决方案1】:

    有了这个Node class,您可以轻松做到这一点。

    public class Subject
    {
        public int Id { get; set; }
        public int? ParentId { get; set; }
        public string Name { get; set; }
    }
    

    创建一棵树并显示每个节点的级别:

    var list = new List<Subject>
    {
        new Subject {Id = 0, ParentId = null, Name = "A"},
        new Subject {Id = 1, ParentId = 0, Name = "B"},
        new Subject {Id = 2, ParentId = 1, Name = "C"},
        new Subject {Id = 3, ParentId = 1, Name = "D"},
        new Subject {Id = 4, ParentId = 2, Name = "E"},
        new Subject {Id = 5, ParentId = 3, Name = "F"},
        new Subject {Id = 6, ParentId = 0, Name = "G"},
        new Subject {Id = 7, ParentId = 4, Name = "H"},
        new Subject {Id = 8, ParentId = 3, Name = "I"},
    };
    var rootNode = Node<Subject>.CreateTree(list, n => n.Id, n => n.ParentId).Single();
    
    foreach (var node in rootNode.All)
    {
        Console.WriteLine("Name {0} , Level {1}", node.Value.Name, node.Level);
    }
    

    【讨论】:

    • 延续rootNode.All 的foreach 循环返回错误Foreach 语句无法对方法组进行操作。我认为All 正在解决Linq,但不知道如何解决这个问题。仅供参考,我有多个根,因此寻找一种方法来访问它们。感谢通用方法,它解决了将递归算法转换为迭代算法的主要问题!
    • 我解决了这个问题:foreach(Node&lt;Leaf&gt; n in rootNode) { foreach(Node&lt;Leaf&gt; l in n.SelfAndDescendants) { //l.Value.ID, l.Value.pID, l.Count&lt;Leaf&gt;(), etc...; }}。这将返回外部 foreach 中的根,并在下一个 foreach 循环中返回根下的所有内容。
    【解决方案2】:

    您可以使用广度优先遍历在n 步骤中自上而下地执行此操作。据我所知,您的方法是n*log(n)

    使用带有Level 字段的节点类在 LinqPad 中快速破解

    class Node
    {
        public string Id;
        public string ParentID;
        public int Level;
        public Node SetLevel(int i)
        {
            Level = i;
            return this;
        }
    }
    
    void Main()
    {
        var source = new List<Node>(){
         new Node(){ Id = "1" },
         new Node(){ Id = "2", ParentID="1"},
         new Node(){ Id = "3", ParentID="1"},
         new Node(){ Id = "4", ParentID="2"}
         };
    
        var queue = source.Where(p => p.ParentID == null).Select(s => s.SetLevel(0)).ToList();
        var cur = 0;
    
        while (queue.Any())
        {
            var n = queue[0];
            queue.AddRange(source.Where(p => p.ParentID == n.Id).Select(s => s.SetLevel(n.Level + 1)));
            queue.RemoveAt(0);
        }
        source.Dump();
    }
    

    输出:

    Id ParentID Level
     1  null      0
     2    1       1 
     3    1       1
     4    2       2
    

    但这一切都取决于 Linq 部分的复杂性 (.Where)

    【讨论】:

      【解决方案3】:

      既然你说你需要得到这个集合中每个节点的关卡,你可以急切地生成一个从节点到关卡的地图。

      这可以通过适当的广度优先遍历在 O(n) 时间内完成。

      (未测试):

      public static Dictionary<Node, int> GetLevelsForNodes(IEnumerable<Node> nodes)
      {
          //argument-checking omitted.
      
          // Thankfully, lookup accepts null-keys.
          var nodesByParentId = nodes.ToLookup(n => n.ParentID);
      
          var levelsForNodes = new Dictionary<Node, int>();
      
          // Singleton list comprising the root.
          var nodesToProcess = nodesByParentId[null].ToList();
      
          int currentLevel = 0;
      
          while (nodesToProcess.Any())
          {
              foreach (var node in nodesToProcess)
                  levelsForNodes.Add(node, currentLevel);
      
              nodesToProcess = nodesToProcess.SelectMany(n => nodesByParentId[n.Id])
                                             .ToList();
              currentLevel++;
          }
      
          return levelsForNodes;
      }
      

      【讨论】:

        【解决方案4】:

        下面是您正在执行的操作的更紧凑版本,请注意,我在测试中使用了简化的数据结构,并且 Flatten 从变量树向下返回树中每个节点的 IEnumerable。如果您可以访问该源,我会将递归深度函数作为树类的一部分。如果您经常这样做,或者您的树很大(或两者兼而有之),我特别喜欢在字典或树结构本身中缓存深度的解决方案。如果你不经常这样做,这会很好。我用它从 GUI 中遍历相对较小的树结构,没有人认为操作很慢。复杂度是获取每个节点深度的 O(N log N) 平均情况。如果您想查看所有代码,我可以明天将其放入。

        Func<Tree, int> Depth = null;
        Depth = p => p.Parent == null ? 0 : Depth(p.Parent) + 1;
        
        var depth = tree.Flatten().Select(p => new { ID = p.NodeID(), HowDeep = Depth(p) });
        

        【讨论】:

          【解决方案5】:

          现在您正在多次处理很多事情。 我会递归地构建关卡。这样您就没有任何处理开销。

          另外,如果您经常运行此功能,我会将节点类中的级别作为变量放入,该变量会在添加节点时自动计算。

          源代码如下。

          【讨论】:

            【解决方案6】:

            尝试像这样使用.ToDictionary

            var dictionary =
                source.ToDictionary(n => n.Id, n => n.ParentId);
            
            Func<int, int> GetLevel = nid =>
            {
                var level = -1;
                if (dictionary.ContainsKey(nid))
                {
                    level = 0;
                    var pid = dictionary[nid];
                    while (pid.HasValue)
                    {
                        level++;
                        pid = dictionary[pid.Value];
                    }
                }
                return level;
            };
            

            这是相当有效的,因为您的最终查询将通过所有节点进行递归。因此,建立字典的费用很便宜。

            根据节点的深度,您可能会发现在任何情况下构建字典都比进行多级暴力搜索要快。

            【讨论】:

              猜你喜欢
              • 2011-05-15
              • 2017-12-11
              • 1970-01-01
              • 1970-01-01
              • 2023-02-01
              • 1970-01-01
              • 1970-01-01
              • 2015-03-08
              • 1970-01-01
              相关资源
              最近更新 更多