在 C# 中将平面 objects 列表转换为嵌套 objects 列表(递归?)

Convert a list of flat objects to list of nested objects in C# (recursive?)

我希望有人能帮助我...

假设我有这个基本书签 class:

public class FlatBookmark
{
    public int PageIndex { get; set; }
    public string Path { get; set; }
}

还有一个 'flat' 书签列表:

List<FlatBookmark> flatBookmarks = new List<FlatBookmark>()
{
    new FlatBookmark() { Path = "Category 1||Page 1", PageIndex = 0 },
    new FlatBookmark() { Path = "Category 1||Page 1||Attachment 1", PageIndex = 1 },
    new FlatBookmark() { Path = "Category 1||Page 1||Attachment 2", PageIndex = 2 },
    new FlatBookmark() { Path = "Category 1||Page 2", PageIndex = 3 },
    new FlatBookmark() { Path = "Category 1||Page 2", PageIndex = 4 }, // Ignore (path already exists)
    new FlatBookmark() { Path = "Category 1||Page 2", PageIndex = 5 }, // Ignore (path already exists)
    new FlatBookmark() { Path = "Category 1||Page 3", PageIndex = 6 },
    new FlatBookmark() { Path = "", PageIndex = 123 }, // Empty or null paths should be completely ignored
    new FlatBookmark() { Path = null, PageIndex = 321 }, // Empty or null paths should be completely ignored
    new FlatBookmark() { Path = "Category 2||Page 1", PageIndex = 7 },
    new FlatBookmark() { Path = "Category 2||Page 2", PageIndex = 8 },
    new FlatBookmark() { Path = "Category 1||Page 1||Attachment 1", PageIndex = 9 }, // Create a new 'Category1' root, because it is separated by the previous one
    new FlatBookmark() { Path = "Category 1||Page 1||Attachment 1", PageIndex = 10 }, // Ignore (path already exists)
    new FlatBookmark() { Path = "Category 1||Page 1||Attachment 2", PageIndex = 11 },
};

我现在想用 嵌套 书签填充一个新的 List<Bookmark>,在路径的 任何给定字符串 上拆分,标题成为路径的最后一部分。

public class Bookmark
{
    public int PageIndex { get; set; }
    public string Title { get; set; }
    public List<Bookmark> Bookmarks; // Nested children
}

像这样:

Category 1: PageIndex=0
    Page 1: PageIndex=0
        Attachment 1: PageIndex=1
        Attachment 2: PageIndex=2
    Page 2: PageIndex=3
    Page 3: PageIndex=6
Category 2: PageIndex=7
    Page 1: PageIndex=7
    Page 2: PageIndex=8
Category 1: PageIndex=9
    Page 1: PageIndex=9
        Attachment 1: PageIndex=9
        Attachment 2: PageIndex=11

我为它创建了一个单元测试:

// Category 1
Assert.AreEqual("Category 1", bookmarks[0].Title);
Assert.AreEqual(0, bookmarks[0].PageIndex);

Assert.AreEqual("Page 1", bookmarks[0].Bookmarks[0].Title);
Assert.AreEqual(0, bookmarks[0].Bookmarks[0].PageIndex);

Assert.AreEqual("Attachment 1", bookmarks[0].Bookmarks[0].Bookmarks[0].Title);
Assert.AreEqual(1, bookmarks[0].Bookmarks[0].Bookmarks[0].PageIndex);

Assert.AreEqual("Attachment 2", bookmarks[0].Bookmarks[0].Bookmarks[1].Title);
Assert.AreEqual(2, bookmarks[0].Bookmarks[0].Bookmarks[1].PageIndex);

Assert.AreEqual("Page 2", bookmarks[0].Bookmarks[1].Title);
Assert.AreEqual(3, bookmarks[0].Bookmarks[1].PageIndex);

Assert.AreEqual("Page 3", bookmarks[0].Bookmarks[2].Title);
Assert.AreEqual(6, bookmarks[0].Bookmarks[2].PageIndex);

// Category 2
Assert.AreEqual("Category 2", bookmarks[1].Title);
Assert.AreEqual(7, bookmarks[1].PageIndex);

Assert.AreEqual("Page 1", bookmarks[1].Bookmarks[0].Title);
Assert.AreEqual(7, bookmarks[1].Bookmarks[0].PageIndex);

Assert.AreEqual("Page 2", bookmarks[1].Bookmarks[1].Title);
Assert.AreEqual(8, bookmarks[1].Bookmarks[1].PageIndex);

// Category 1 again (not combined with the first one, because there was another category in between)
Assert.AreEqual("Category 1", bookmarks[2].Title);
Assert.AreEqual(9, bookmarks[2].PageIndex);

Assert.AreEqual("Page 1", bookmarks[2].Bookmarks[0].Title);
Assert.AreEqual(9, bookmarks[2].Bookmarks[0].PageIndex);

Assert.AreEqual("Attachment 1", bookmarks[2].Bookmarks[0].Bookmarks[0].Title);
Assert.AreEqual(9, bookmarks[2].Bookmarks[0].Bookmarks[0].PageIndex);

Assert.AreEqual("Attachment 2", bookmarks[2].Bookmarks[0].Bookmarks[1].Title);
Assert.AreEqual(11, bookmarks[2].Bookmarks[0].Bookmarks[1].PageIndex);

我卡在了递归部分(我想...),我可以遍历每个 FlatBookmark,然后拆分路径 (string[] parts = bookmark.Split('/')),然后对于每个部分我想以某种方式回顾它是否有任何 parents,或者应该有任何 parent,如果没有:创建它们...

有人能为我指明如何创建这样的方法的正确方向吗?

嵌套元素的数量应该基本没有限制...

--- 更新 ---

--- 解决方案 ---

这是我想出的解决方案:

public List<Bookmark> CreateNestedBookmarks(List<FlatBookmark> flatBookmarks, string separator = "/")
{
    // Create a 'root' bookmark list (happens for every nested level, with recursion)
    var bookmarks = new List<Bookmark>();

    foreach (var flatBookmark in flatBookmarks)
    {
        if(!string.IsNullOrWhiteSpace(flatBookmark.Path))
        {
            // Only use the title
            string title = flatBookmark.Path.Split(new string[] { separator }, StringSplitOptions.None)[0];
            bool exists = bookmarks.LastOrDefault()?.Title == title;

            // Add the bookmark to the list if it's not already added before and not empty/null
            if (!exists && !string.IsNullOrWhiteSpace(title))
            {
                bookmarks.Add(new Bookmark()
                {
                    Title = title,
                    PageIndex = flatBookmark.PageIndex
                });
            }
        }
    }

    // Fetch the nested bookmarks for each 'root'-bookmark
    foreach(var bookmark in bookmarks)
    {
        bool found = false;
        List<FlatBookmark> childBookmarks = new List<FlatBookmark>();

        foreach (var flatBookmark in flatBookmarks)
        {
            if(!string.IsNullOrWhiteSpace(flatBookmark.Path))
            {
                string[] splittedPath = flatBookmark.Path.Split(new string[] { separator }, StringSplitOptions.None);
                string title = splittedPath[0];

                if (title == bookmark.Title)
                {
                    // Strip the first part of the path
                    flatBookmark.Path = string.Join(separator, splittedPath.Skip(1).Take(splittedPath.Length - 1));
                    childBookmarks.Add(flatBookmark);
                    found = true;
                }
                else
                {
                    // Stop iteration when a new title is found, with this way it keeps the input order intact
                    // Multiple bookmarks on the same level with the same title should only be combined when there is no different title in between
                    // Cat1
                    // Cat2
                    // Cat1
                    if (found)
                    {
                        break;
                    }
                }
            }
        }

        // Create nested bookmarks (if they exist)
        if(childBookmarks.Count > 0)
        {
            bookmark.Bookmarks = CreateNestedBookmarks(childBookmarks, separator);
        }
    }
    return bookmarks;
}

我给你一个伪代码的起点:

public List<Bookmark> ParseFlatBookmarks (List<FlatBookmark> flatBookmarks)
{
    // define the list of local root bookmarks.

    // loop over the FLAT bookmarks

        // look only for the root bookmark in each path
        // (ex. in Path = "Category 1/Page 1" you will look for "Category 1")

        // then you check if found bookmark is already present in your root bookmarks list, if it is not than you create it and add it to the list.

    // loop over found root bookmarks

        // create a list of all the FLAT bookmarks that has current root bookmark as root
        // (ex. if you have "Category 1" as current root bookmark you will have a list containing "Category 1/Page 1", "Category 1/Page 1/Attachment 1", etc. and the last one will be "Category 1/Page 3".

        // to recursively navigate the FLAT bookmarks you will need to adjust their path.
        // To do that just remove root bookmark from path
        // (ex. if you currently have "Category 1" as root bookmark "Category 1/Page 1" as FLAT bookmark, you will have to set its path as "Page 1")

        // recursive call passing currently root bookmark's FLAT bookmark children as parameter
        // add found bookmarks to the currently root bookmark

    // return list of root bookmarks
}

最重要的是,您必须检查路径何时为空(这意味着您已经导航到 child 书签

当您检查刚刚找到的书签是否与之前存储的书签具有相同的标题时,应该涵盖您所要求的独特功能。

如果您无法调整它们的路径,您可以随时使用调整后的路径创建新的平面书签。

希望我给了你一些解决问题的想法