从字符串数组中递归地创建树层次结构

时间:2019-08-11 14:50:03

标签: c# linq recursion tree

我有一个用“!”分隔的字符串数组。我试图在我的自定义类PivotGroup中分解该字符串并递归创建树层次结构。例如,我的目标是分解字符串数组

 string[] paths = new string[] {
            "ROOT!ZZZ!AAA!EEE!15712",
            "ROOT!ZZZ!AAA!EEE!15722",
            "ROOT!ZZZ!AAA!EEE!13891"}

到PivotGroup类中,例如PivotGroup包含ChildGroups[],它们嵌入了数组字符串。

例如:

     PivotGroup pgGroup = new PivotGroup();
     pgGroup.ChildGroups[0] = PivotGroup[]; // Key:Book Level 3 Value: "AAA"

现在在Book Level 3子组中,我需要设置Book Level 4的值为“ EEE”,在ChildGroups中的“ EEE”中,我需要创建另一个childGroup数组,在这种情况下,其大小为3,称为Book Level 5并为以下15712、15722、13891中的每一个设置另一个PivotGroup

这是我的PivotGroup类和嵌入式类Objects:

public class PivotGroup
{
    public PivotGroup() { }

    public PivotGroup(PivotGroupKey groupKey, PivotRow data, PivotGroup[] childGroups, bool leaf, int groupLevel)
    {
        GroupKey = groupKey;
        Data = data;
        ChildGroups = childGroups;
        Leaf = leaf;
        GroupLevel = groupLevel;
    }

    public PivotGroupKey GroupKey { get; private set; }
    public PivotRow Data { get; private set; }
    public PivotGroup[] ChildGroups { get; set; }
    public bool Leaf { get; private set; }
    public int GroupLevel { get; private set; }

    public override string ToString()
    {
        return GroupKey + ", GroupLevel: " + GroupLevel + ", Children: " +
            ChildGroups.Length + (Leaf ? " (Leaf)" : "");
    }
}

public class PivotGroupKey
{
    public PivotGroupKey()
    {
    }

    public PivotGroupKey(string keyGroup, string keyValue)
    {
        if(keyGroup != null)
            KeyGroup = string.Intern(keyGroup);

        if (keyValue != null)
            KeyValue = string.Intern(keyValue);
    }

    public string KeyGroup { get; private set; }
    public string KeyValue { get; private set; }

    public override string ToString()
    {
        return KeyGroup + ": " + KeyValue;
    }
}

public class PivotRow
{
    public PivotRow()
    {
    }

    public PivotRow(string key, params object[] data) : this(key, true, data) { }

    public PivotRow(string key, bool entitled, params object[] data)
    {
        Data = data;
        Key = null;
        Entitled = entitled;
    }

    public object[] Data { get; private set; }
    public bool Entitled { get; private set; }
    public string Key { get { return null; } set { } }
}

我尝试过的主程序:

public class BookLevels 
{
    public string Root { get; set; }
    public string BookLevel2 { get; set; }
    public string BookLevel3 { get; set; }
    public string BookLevel4 { get; set; }
    public string BookLevel5 { get; set; }
}

class Program
{
    static void BuildTree(string[] paths)
    {
        var BookPaths = paths.Select(x => x.Split('!'))
            .Select(x => new BookLevels
            {
                Root = x[0],
                BookLevel2 = x[1],
                BookLevel3 = x[2],
                BookLevel4 = x[3],
                BookLevel5 = x[4]
            }).GroupBy(z => new { z.BookLevel3, z.BookLevel4 }).ToArray();


        var BookLevel3Cnt = BookPaths.Select(q => q.Key.BookLevel3).Count();

        PivotGroup root = new PivotGroup(
            new PivotGroupKey("Total", ""),
            new PivotRow(null, new string[8]),
            new PivotGroup[BookLevel3Cnt], false, 0);

        foreach (var booklevel3 in BookPaths)
        {
            AddChildren(root, booklevel3);
        }
    }

    private static void AddChildren(PivotGroup root, IGrouping<object, BookLevels> booklevel, int index = 0)
    {
        root.ChildGroups[index] = new PivotGroup(
        new PivotGroupKey("Book Level " + (index + 3).ToString(), booklevel.Key.ToString()),
        new PivotRow(null, new string[8]),
        AddChildren(root, booklevel[index], index + 1), false, 0);
    }

    static void Main(string[] args)
    {
        string[] paths = new string[] {
            "ROOT!ZZZ!AAA!EEE!15712",
            "ROOT!ZZZ!AAA!EEE!15722",
            "ROOT!ZZZ!AAA!EEE!13891",
            "ROOT!ZZZ!AAA!DDD!15712",
            "ROOT!ZZZ!AAA!DDD!15722",
            "ROOT!ZZZ!AAA!DDD!13891",
            "ROOT!ZZZ!BBB!DDD!15812",
            "ROOT!ZZZ!BBB!DDD!15822",
            "ROOT!ZZZ!BBB!DDD!13891",
        };

        BuildTree(paths);
        Console.WriteLine();
        Console.ReadLine();
    }

我认为我的问题可能是我创建破坏字符串的Linq语句的方式,因为我不确定如何递归地处理它。

2 个答案:

答案 0 :(得分:0)

我不确定哪个属性属于哪个属性。另外,为了简单起见并能够专注于递归算法,我重新定义了组类,如下所示(这并不意味着您必须更改类,而是改编我的算法):

public class PivotGroup
{
    public string Key { get; set; }
    public List<PivotGroup> ChildGroups { get; } = new List<PivotGroup>();
    public override string ToString() => Key; // Makes debugging easier.
}

这个想法是路径的值进入键。我将ChildGroups列为一个列表,以便能够连续添加子级。我的BuildTree返回了根

static PivotGroup BuildTree(string[] paths)
{
    var root = new PivotGroup { Key = "ROOT" };
    foreach (string path in paths) {
        AddChildren(root, path.Split('!').Skip(1).ToList());
    }
    return root;
}

递归部分进入AddChildren。我将路径转换为List<string>,以便能够删除添加的部分。 AddChildren假定path中的第一项是要添加的第一个子项。

static void AddChildren(PivotGroup group, List<string> path)
{
    string key = path[0];
    int index = group.ChildGroups.FindIndex(g => g.Key == key);
    PivotGroup child;
    if (index >= 0) { // A child with this key exists.
        child = group.ChildGroups[index]; // Select this existing child.
    } else { // This key is missing. Add a new child.
        child = new PivotGroup { Key = key };
        group.ChildGroups.Add(child);
    }
    if (path.Count > 1) {
        path.RemoveAt(0); // Remove the added child key and add the rest recursively.
        AddChildren(child, path);
    }
}

我们通过沿着树走来添加孩子,并在必要时添加新孩子。

这将以递归方式打印树:

private static void PrintTree(PivotGroup group, int level)
{
    Console.WriteLine(new String(' ', 2 * level) + group.Key);
    foreach (PivotGroup child in group.ChildGroups) {
        PrintTree(child, level + 1);
    }
}
string[] paths = new string[] {
    "ROOT!ZZZ!AAA!EEE!15712",
    ...
};

PivotGroup root = BuildTree(paths);
PrintTree(root, 0);
Console.ReadKey();

我们也可以使用循环而不是递归,因为我们一次添加一个分支:

static PivotGroup BuildTree(string[] paths)
{
    var root = new PivotGroup { Key = "ROOT" };
    foreach (string path in paths) {
        PivotGroup group = root;
        string[] pathElements = path.Split('!');
        for (int i = 1; i < pathElements.Length; i++) { // Element [0] is ROOT, we skip it.
            string key = pathElements[i];
            int index = group.ChildGroups.FindIndex(g => g.Key == key);
            PivotGroup child;
            if (index >= 0) { // A child with this key exists.
                child = group.ChildGroups[index]; // Select this existing child.
            } else { // This key is missing. Add a new child.
                child = new PivotGroup { Key = key };
                group.ChildGroups.Add(child);
            }
            group = child;
        }
    }
    return root;
}

List<T>.FindIndex对于大型列表而言效率不高。如果数据量很大,并且顺序无关紧要,请切换到Dictionary<string, PivotGroup>。如果需要对数据进行排序,请使用SortedDictionary<string, PivotGroup>

答案 1 :(得分:0)

这是一些简单的递归代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
           string[] paths = new string[] {
            "ROOT!ZZZ!AAA!EEE!15712",
            "ROOT!ZZZ!AAA!EEE!15722",
            "ROOT!ZZZ!AAA!EEE!13891"};

            List<List<string>> inputData = paths.Select(x => x.Split(new char[] {'!'}).ToList()).ToList();

            Node root = new Node();
            Node.ParseTree(root, inputData);
        }
    }
    public class Node
    {
        public string name { get; set; }
        public List<Node> children { get; set; }

        public static void ParseTree(Node parent, List<List<string>> inputData)
        {
            parent.name = inputData.First().FirstOrDefault();
            var groups = inputData.Select(x => x.Skip(1)).GroupBy(x => x.Take(1).FirstOrDefault());
            foreach (var group in groups)
            {
                if (group.Key != null)
                {
                    if (parent.children == null) parent.children = new List<Node>();
                    Node newNode = new Node();
                    parent.children.Add(newNode);
                    ParseTree(newNode, group.Select(x => x.Select(y => y).ToList()).ToList());
                }
            }
        }
    }
}