フラットオブジェクトのリストをC#のネストされたオブジェクトのリストに変換します(再帰?)

2020-02-18 c# recursion nested data-conversion

誰かがこれを手伝ってくれることを願っています...

この基本的なブックマーククラスがあるとします。

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

私は再帰的な部分に行き詰まっています(そう思います...)、各FlatBookmarkループしてパスを分割し( string[] parts = bookmark.Split('/') )、次に必要な部分ごとにどういうわけかそれが親を持っているか、または親を持っているべきかどうかを振り返り、そうでなければ:それらを作成します...

誰かがこのようなメソッドを作成する方法の正しい方向に私を向けることができますか?

ネストされた要素の量に基本的に制限はありません...

--- 更新 ---

  • 別のセパレータでflatBookmarkリストを更新しました
  • 任意の文字列でパスの分離を要求する
  • 出力ブックマークの「パス」の要件を削除しました(タイトルで十分です)
  • 追加要件: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;
}

Answers

私はあなたに出発点を疑似コードとして与えます:

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
}

その上で、パスが空になったときに確認する必要があります(つまり、子ブックマークまでナビゲートしたことを意味します)

先ほど保存したものと同じタイトルのブックマークが見つかったかどうかを確認するとき、あなたが尋ねたユニークな機能はカバーされるべきです。

パスを調整できない場合は、常にパスを調整して新しいフラットブックマークを作成できます。

私はあなたにあなたの問題を解決するためのいくつかのアイデアを与えたことを願っています

Related