【问题标题】:Mapping a flat list to a hierarchical list with parent IDs C#将平面列表映射到具有父 ID 的分层列表 C#
【发布时间】:2014-08-27 16:21:25
【问题描述】:

我有一个简单的类别列表,如以下类所示

public class FlatCategoryList
{
    public List<FlatCategory> Categories { get; set; }
}
public class FlatCategory
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string ParentID { get; set; }
}

我正在尝试将我的平面类别列表映射到如下所示的层级结构:

public class HieraricalCategoryList
{
    public List<Category> Categories { get; set; }
}
public class Category
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string ParentID { get; set; }

    public List<Category> ChildCategories { get; set; }
}

我的问题是,鉴于可能存在无限数量的子层,实现这一目标的最佳方法是什么?

public HieraricalCategoryList MapCategories(FlatCategoryList flatCategoryList)
{
    var hieraricalCategoryList = new HieraricalCategoryList();

    //Do something here to map the flat category list to the hierarichal one...

    return hieraricalCategoryList;
}

【问题讨论】:

  • 关键是不要使用递归。
  • 只是为了更好的编程。您应该使您的属性 IEnumerable 等,而不是列表。这样,您可以将任何继承 IEnumerable 的内容设置为那些属性,例如数组、列表或您创建的任何继承 IEnumerable 的自定义内容。
  • 如果您必须在各处进行大量自定义映射,则有一个很棒的库,称为 AutoMapper。 automapper.org

标签: c#


【解决方案1】:
public HieraricalCategoryList MapCategories(FlatCategoryList flatCategoryList)
{
    var categories = (from fc in flatCategoryList.Categories
                      select new Category() {
                          ID = fc.ID,
                          Name = fc.Name,
                          ParentID = fc.ParentID
                      }).ToList();

    var lookup = categories.ToLookup(c => c.ParentID);

    foreach(var c in categories)
    {
        // you can skip the check if you want an empty list instead of null
        // when there is no children
        if(lookup.Contains(c.ID))
            c.ChildCategories = lookup[c.ID].ToList();
    }

    return new HieraricalCategoryList() { Categories = categories };
}

【讨论】:

  • if(lookup.Contains(c.ID)) 这里根本不需要。事实上,这可能是有害的,因为如果节点没有子节点,则最好为子集合提供一个空列表到 null
  • 添加了一条评论。
  • @MarcinJuraszek 我在使用您的代码时遇到了问题,请您在stackoverflow.com/questions/28454466/…提出问题
【解决方案2】:

进行这种转换的一种非常简单且高性能的方法是创建一个查找,在该查找中您将 ID 值映射到应该是该 ID 值的子节点的节点。可以在节点的单遍中创建此查找。之后,您可以再次遍历所有节点,将它们的子集合分配为查找中它们的 ID 值的值。

请注意,如果查找映射到您要转换的类型的对象,而不是从其转换,这会更简单。

var lookup = list.Categories
    .Select(category => new Category()
    {
        ID = category.ID,
        Name = category.Name,
        ParentID = category.ParentID,
    })
    .ToLookup(category => category.ParentID);

foreach (var category in lookup.SelectMany(x => x))
    category.ChildCategories = lookup[category.ID].ToList();

var newList = new HieraricalCategoryList()
{
    Categories = lookup[null].ToList(),
};

【讨论】:

    【解决方案3】:

    改进了建议的答案

    public HieraricalCategoryList MapCategories(FlatCategoryList flatCategoryList)
    {
        var categories = (from fc in flatCategoryList.Categories
                          select new Category() {
                              ID = fc.ID,
                              Name = fc.Name,
                              ParentID = fc.ParentID
                          }).ToList();
    
        var lookup = categories.ToLookup(c => c.ParentID);
    
        foreach(var c in rootCategories)//only loop through root categories
        {
            // you can skip the check if you want an empty list instead of null
            // when there is no children
            if(lookup.Contains(c.ID))
                c.ChildCategories = lookup[c.ID].ToList();
        }
    
        //if you want to return only root categories not all the flat list
        //with mapped child 
    
        categories.RemoveAll(c => c.ParentId != 0);//put what ever your parent id is
    
        return new HieraricalCategoryList() { Categories = categories };
    }
    

    【讨论】:

    • 通过根目录查找是错误的。这样非根类别的子类别将不会被填充。
    【解决方案4】:

    使用两遍解决方案。这假设完整的集合可以放入内存。第一遍扫描平面类别列表,并构建一个类别字典,由 ID 索引。此时子集合全部为空,父属性为空。然后第二遍再次扫描它们,构建子集合并设置父属性。

    未经测试的代码:

    var final = new Dictionary<string, Category>();
    var rootCategories = new List<Category>();
    
    // Pass 1
    foreach (var flat in flatList)
    {
      Category cat = new Category() { ID = flat.ID, Name = flat.Name, parent = null }
      cat.Children = new List<Category>();
      final[flat.ID] = cat;
    }
    
    // Pass 2
    foreach (var flat in flatList)
    {
      // find myself -- must exist
      var self = final[flat.ID];
    
      // find parent -- may not exist
      if (final.ContainsKey(flat.ParentID)
      {
        var parent = final[flat.ParentID];
        parent.Children.Add(self);
        self.Parent = parent;     
      }
      else
      {
        rootCategories.Add(self);
      }
    
    }
    

    这将有 O(n) 的运行时间,因为它是两个线性扫描,以及一些字典查找,它们是 O(1)。

    【讨论】:

    • 使用 LINQ ToLookup 扩展方法使执行此操作的代码更简单,同时在功能上等效,如我的回答所示。
    • 我必须承认,我更喜欢 MarcinJuraszek 的回答在风格和易于阅读方面(例如,我发现创建 category 项目列表然后循环遍历该列表而不是循环遍历原始内容更直观列表。此外,他们使用查找来获取单亲的所有孩子可以使事情变得更整洁。如果您还没有查看答案以了解主观上更好的做事方式。:)
    • 我喜欢 LINQ 风格。将其视为 LINQ 之前的答案。
    猜你喜欢
    • 1970-01-01
    • 2013-02-28
    • 1970-01-01
    • 2016-10-16
    • 1970-01-01
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    • 2019-05-26
    相关资源
    最近更新 更多