在 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,如果没有:创建它们...
有人能为我指明如何创建这样的方法的正确方向吗?
嵌套元素的数量应该基本没有限制...
--- 更新 ---
- 用不同的分隔符更新了 flatBookmark 列表
- 要求在任何给定字符串上分隔路径
- 删除了输出书签中'Path'的要求(标题就够了)
- 额外要求:应保留平面书签的顺序;只有当它们之间没有其他标题时,才应合并具有相同标题的同一级别的多个书签
--- 解决方案 ---
这是我想出的解决方案:
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 书签
当您检查刚刚找到的书签是否与之前存储的书签具有相同的标题时,应该涵盖您所要求的独特功能。
如果您无法调整它们的路径,您可以随时使用调整后的路径创建新的平面书签。
希望我给了你一些解决问题的想法
我希望有人能帮助我...
假设我有这个基本书签 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,如果没有:创建它们...
有人能为我指明如何创建这样的方法的正确方向吗?
嵌套元素的数量应该基本没有限制...
--- 更新 ---
- 用不同的分隔符更新了 flatBookmark 列表
- 要求在任何给定字符串上分隔路径
- 删除了输出书签中'Path'的要求(标题就够了)
- 额外要求:应保留平面书签的顺序;只有当它们之间没有其他标题时,才应合并具有相同标题的同一级别的多个书签
--- 解决方案 ---
这是我想出的解决方案:
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 书签
当您检查刚刚找到的书签是否与之前存储的书签具有相同的标题时,应该涵盖您所要求的独特功能。
如果您无法调整它们的路径,您可以随时使用调整后的路径创建新的平面书签。
希望我给了你一些解决问题的想法