Преобразовать список плоских объектов в список вложенных объектов в C# (рекурсивный?) - PullRequest
1 голос
/ 17 февраля 2020

Я надеюсь, что кто-то может помочь мне с этим ...

Допустим, у меня есть этот базовый класс закладок c:

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

И "плоский" список закладок:

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

Я застрял на рекурсивная часть (я думаю ...), я могу l oop через каждый FlatBookmark и разделить путь (string[] parts = bookmark.Split('/')), а затем для каждой части я хочу как-то оглянуться назад, если у нее есть родители, или должен иметь каких-либо родителей, а если нет: создать их ...

Может кто-то, возможно, указать мне правильное направление, как создать метод, подобный этому? на количество вложенных элементов ...

--- ОБНОВЛЕНИЕ ---

  • Обновлен список flatBookmark с другим разделителем
  • Убрано требование «Путь» в выходной закладке (достаточно заголовка)
  • Дополнительное требование: порядок плоских закладок должен быть сохранились; несколько закладок на одном уровне с одним и тем же названием должны объединяться только в том случае, если между ними нет другого названия

--- РЕШЕНИЕ ---

Вот решение, которое я придумал:

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

1 Ответ

0 голосов
/ 18 февраля 2020

Я даю вам отправную точку в виде псевдокода:

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
}

Кроме того, вам придется проверять, когда путь пуст (это означает, что вы прошли весь путь до дочерних закладок).

Запрашиваемая вами уникальная функция должна быть раскрыта при проверке, если только что найденная закладка имеет тот же заголовок, что и ранее сохраненная.

Если вы не можете изменить их путь, вы можете всегда создавайте новые плоские закладки с измененными путями.

Я надеюсь, что дал вам несколько идей для решения вашей проблемы

...