使用 LINQ 从树数据结构获取级别的最佳方法是什么?

发布于 2024-10-24 23:05:09 字数 851 浏览 1 评论 0原文

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

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 函数的开销能够减少。或者也许有更好的方法可以不用这个功能直接获取!

任何想法!

I have a collection demonstrates a tree data structure, it's node is:

Node:
Id
Name
ParentID

Now, I want to get the level for each node in this collection, I tried with the following code but I wonder if it's the best way to implement that.

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

I think that the overhead in GetLevel function is able to decrease. or maybe there is a better way to get it directly without this function!

Any idea!

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

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

发布评论

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

评论(6

拔了角的鹿 2024-10-31 23:05:09

通过这个 Node 类,您可以轻松做到这一点。

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

With this Node class you can do this easily.

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

Create a tree and show the level of each node:

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);
}
秋凉 2024-10-31 23:05:09

您可以使用广度优先遍历在 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)

You could do it top-down in n steps with Breadth First Traversal. Your approach is n*log(n)as far as I can see?

A quick hack in LinqPad with a node class with Level field

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();
}

Outputs:

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

But this all depends on the complexity of the Linq parts (.Where)

江湖正好 2024-10-31 23:05:09

既然您说您需要获取此集合中每个节点的级别,您可能会急切生成从节点到级别的映射。

通过适当的广度优先遍历,这可以在 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;
}

Since you say you need to get the level for each node in this collection, you might as produce a map from node to level eagerly.

This can be done in O(n) time with a proper breadth-first traversal.

(Untested):

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;
}
昔梦 2024-10-31 23:05:09

下面是您正在做的事情的更紧凑版本,请注意,我在测试中使用了简化的数据结构,并且 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) });

A more compact version of what you're doing is below, note I used a simplified data structure for my test and Flatten returns an IEnumerable < Tree > of every node in the tree from the variable tree on down. I would make the recursive depth function part of the tree class if you have access to that source. If you are doing this often or your trees are huge (or both) I'm particular to the solution of caching the depth in a dictionary or the tree structure itself. If you don't do it often this will work fine. I use it for going through relatively small tree structures from a GUI and no one has ever thought the operation was slow. Complexity is O(N log N) average case for getting the depth of every node. If you would like to see all of the code I can put it in tomorrow.

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) });
强辩 2024-10-31 23:05:09

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

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

源代码如下。

Right now you are processing a lot of things multiple times.
I would recursively build the levels. This way you don't have any processing overhead.

Also, if you run this functionality a lot, I would put in the level in the node class as a variable which is computed automatically when a node is added.

Source code follows.

苦行僧 2024-10-31 23:05:09

尝试像这样使用 .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;
};

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

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

Try using .ToDictionary like this:

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;
};

This is fairly efficient since your final query is going to recurse thru all of the nodes. The expense to build a dictionary is therefore cheap.

Depending how deep your nodes go you might find that building a dictionary is quicker than doing multi-level brute force searches in any case.

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