3

I store a series of objects into the database that have a pretty simple structure. Represented as a model, they looks similar to

public SiteMenuItem 
{
    public int Id {get;set;}
    public string Name {get;set;}
    public int? ParentId {get;set;}
    public int? Order {get;set;}

    public virtual List<SiteMenuItem> Children {get;set;} //null on initialization 
}

When I retrieve these values from the database, I transform these values into a nested-list tree-like structure for use in our web-app. I do that in a fashion similar to:

private static List<SiteMenuItem> GetChildren(List<SiteMenuItem> menu)
{
    // For every SiteMenuItem with a parentId, place the child under their parent.
    menu.ForEach(menuItem => menuItem.Children = menu.Where(n => n.ParentId == menuItem.Id).ToList());
    // Remove all orphaned items.
    menu.RemoveAll(n => n.ParentId != null);

    return menu;
}

This function works great for transforming my flat list into a list of hierarchical items. However, I'm attempting to figure out how to do the reverse.

I need to figure out how to traverse the list of items (possibly recursively?) and return them to a flat list of items with the ParentId properly populated.

What is the best way to do this?

Edit

The problem becomes even more complicated as I need to also track the order in which the items are stored. For example given the following hierarchy, I need to set the proper ordering.

The following objects:

[  
   {  
      "Id":1,
      "Name":"Menu Item 1",
      "Children":[  
         {  
            "Id":2,
            "Name":"Sub Menu Item 1",
            "Children":[  

            ]
         },
         {  
            "Id":3,
            "Name":"Sub Menu Item 2",
            "Children":[  

            ]
         }
      ]
   },
   {  
      "Id":4,
      "Name":"Menu Item 2",
      "Children":[  

      ]
   },
   {  
      "Id":5,
      "Name":"Menu Item 3",
      "Children":[  
         {  
            "Id":6,
            "Name":"Sub Menu Item 1",
            "Children":[  
               {  
                  "Id":7,
                  "Name":"Sub Menu Item 2"
               }
            ]
         },
         {  
            "Id":8,
            "Name":"Sub Menu Item 1",
            "Children":[  

            ]
         }
      ]
   }
]

Would be transformed into:

[  
   {  
      "Id":1,
      "Name":"Menu Item 1",
      "Parent":"",
      "Order":1
   },
   {  
      "Id":2,
      "Name":"Sub Menu Item 1",
      "Parent":1,
      "Order":1
   },
   {  
      "Id":3,
      "Name":"Sub Menu Item 2",
      "Parent":1,
      "Order":2
   },
   {  
      "Id":4,
      "Name":"Menu Item 2",
      "Parent":"",
      "Order":2
   },
   {  
      "Id":5,
      "Name":"Menu Item 3",
      "Parent":"",
      "Order":3
   },
   {  
      "Id":6,
      "Name":"Sub Menu Item 1",
      "Parent":5,
      "Order":1
   },
   {  
      "Id":7,
      "Name":"Sub Sub Menu Item 1",
      "Parent":6,
      "Order":1
   },
   {  
      "Id":8,
      "Name":"Sub Menu Item 2",
      "Parent":5,
      "Order":2
   }
]

Visually the menu would look at such

Menu Item 1 | Menu Item 2 | Menu Item 3
     |                           |
Sub1 - Sub2                 Sub1 - Sub2
                              /
                          SubSub1      

The children should effectively always know their order within the original lists (arrays).

EDIT 2

I created a modified solution based off the linked [DUPLICATE] that allows me to maintain the proper ordering (albeit in a slightly different fashion) and it allows me to track the proper parents of each node.

public static IEnumerable<SiteMenuItemEditViewModel> FlattenItems(IEnumerable<SiteMenuItemEditViewModel> parents)
{
    var stack = new Stack<SiteMenuItemEditViewModel>();
    var order = 0;

    foreach (var item in parents.Reverse())
    {
        item.parentId = null;
        stack.Push(item);
    }

    while (stack.Count > 0)
    {
        var current = stack.Pop();

        current.order = order;
        current.menuItems.Reverse();

        yield return current;

        foreach (var child in current.children)
        {
            child.parentId = current.id;
            stack.Push(child);
        }

        order++;
    }
}
JD Davis
  • 3,517
  • 4
  • 28
  • 61
  • Can you set the Ids or are they dictated by an auto-increment primary key from the DB and need to be stable throughout the lifetime? Can you add stuff, delete and so on? Just flattening the list is pretty trivial if the ID / parent IDs are given and you don't have to keep track of inserts, deletes and edits, so I assume there is more to it. – Frank J Oct 17 '16 at 17:22
  • The ids are an identity property within the database. – JD Davis Oct 17 '16 at 17:25
  • 2
    Can you use SelectMany? – Casey Oct 17 '16 at 17:26
  • Another question: Are you allowed to change the SiteMenuItem model (e.g. add a property) or is that fixed? – Frank J Oct 17 '16 at 17:27
  • I've never tried SelectMany, but the documentation does seem somewhat promising. – JD Davis Oct 17 '16 at 17:28
  • @FrankJ I can modify the C# model to my heart's content. – JD Davis Oct 17 '16 at 17:28
  • @Casey I think `SelectMany` may be problematic because the nesting can be many levels deep. Even with `SelectMany` that would still require some recursive approach. – PiotrWolkowski Oct 17 '16 at 17:32
  • Looking back, SelectMany will only allow a single level deep where I will need to go N-Levels deep. Recursion seems to be my only real option. – JD Davis Oct 17 '16 at 17:53
  • Isn't it already persisted in the store as a flat list (ie. table?). Wouldnt that then be the easiest access point, (retrieving and filtering directly from the store without creating a hierarchy) in this case? – Igor Oct 17 '16 at 18:04
  • This is being transmitted to and from a web-app. In the web-app it needs to be a hierarchy for my UI to work correctly. However, it needs to be flattened to be saved back to the database. – JD Davis Oct 17 '16 at 18:06
  • 2
    See http://stackoverflow.com/q/11830174/1260204, pretty much the same question I think with 2 good answers. – Igor Oct 17 '16 at 18:10
  • See also e.g. http://stackoverflow.com/questions/141467/recursive-list-flattening and http://stackoverflow.com/questions/13409194/is-it-possible-to-implement-a-recursive-selectmany. – Peter Duniho Oct 17 '16 at 18:13
  • As the marked duplicate shows, you can use `SelectMany()` recursively. You can even declare the recursive method as anonymous if you really want to (but I think the named method is clearer). If you have a constraint that prevents use of these answers, post a new question, include a good [mcve], and explain clearly what _specifically_ you are having trouble with, including what the code does now and what you want instead. – Peter Duniho Oct 17 '16 at 18:13
  • can you be more clear about what order you want? maybe an example of what the output would be? – zacaj Oct 17 '16 at 18:33
  • I updated the original output with example JSON. I will try to provide a visual as well. – JD Davis Oct 17 '16 at 18:38
  • @zacaj I've updated the OP with somewhat of a visual. Essentially, when the data comes in. The children should set a property `Order` equal to their position within their respective array. – JD Davis Oct 17 '16 at 18:42
  • @Jdsfighter seems like just doing a depth first traversal and adding each node to a list as you go would suffice, unless I'm missing some aspect – zacaj Oct 17 '16 at 19:01
  • @zacaj I believe that's correct. – JD Davis Oct 17 '16 at 19:08

0 Answers0