How to loop recursive data type - c#

I reading hierarchical data into a recursive data structure.
public class Items
{
public string Name { get; set; }
public List<Items> Children { get; set; }
}
So its similar to a tree. My problem is, I have no idea how I can loop over all elements or find a inner element with a specific Name. Since it can be very complex/deep I can not really work with nested loops since I don't know how deep it will get.
How can I have a loop over all elements in such a structure?

Since you need solution without recursion, here is one:
public Items FindByName(Items root, string targetName)
{
var stack = new Stack<Items>();
stack.Push(root);
Items node;
while (true)
{
node = stack.Pop();
if (node == null)
{
// not found ..
}
if (node.Name == targetName)
{
break;
}
foreach (var child in node.Children)
{
stack.Push(child);
}
}
return node;
}

void RecursiveMethod(Items items)
{
if (items.Children != null)
{
foreach (Items i in items.Children)
{
RecursiveMethod(i);
}
}
if (items.Name == "YourName")
{
// Do your stuff..
}
}

You need some traversal algorithm implementation.
There are several ones, either recursive, or non-recursive. It depends on your particular use-cases, which one to choose.
E.g., a kind of non-recursive, lazy width traversal:
public static class TreeVisitor
{
public static IEnumerable<TNodeType> WidthTraversal<TNodeType>(TNodeType root, Func<TNodeType, IEnumerable<TNodeType>> getChildNodesFunc)
where TNodeType : class
{
if (root == null)
{
throw new ArgumentNullException(nameof(root));
}
if (getChildNodesFunc == null)
{
throw new ArgumentNullException(nameof(getChildNodesFunc));
}
var visited = new HashSet<TNodeType>();
var queue = new Queue<TNodeType>();
yield return root;
visited.Add(root);
queue.Enqueue(root);
while (queue.Count > 0)
{
var parent = queue.Dequeue();
foreach (var child in getChildNodesFunc(parent))
{
if (child == default(TNodeType))
continue;
if (!visited.Contains(child))
{
yield return child;
visited.Add(child);
queue.Enqueue(child);
}
}
}
}
}
Usage:
var rootItem = new Items
{
Name = "Root",
Children = new List<Items>
{
new Items { Name = "Child1" },
new Items { Name = "Child2" },
// etc
}
};
foreach (var item in TreeVisitor.WidthTraversal(rootItem, _ => _.Children))
{
// ...
}

I would do it this way:
class Program
{
static void Main(string[] args)
{
List<Item> items = new List<Item>() { new Item { Name = "Pasta", Children = new List<Item>() { new Item { Name = "Pasta", Children = null } } } };
List<Item> pastas = GetItemsByName(items, "Pasta");
}
private static List<Item> GetItemsByName(List<Item> items, string name)
{
List<Item> found = new List<Item>();
foreach (Item item in items)
{
if (item.Name == name)
{
found.Add(item);
}
if (item.Children != null)
{
found.AddRange(GetItemsByName(item.Children, name));
}
}
return found;
}
}
public class Item
{
public string Name { get; set; }
public List<Item> Children { get; set; }
}

Related

What is a cleaner or more efficient way of iterating over nested collections of objects

I'm working on building an object with nested hierarchy from a flattened list of BOM items (bill of materials) from SQL that contain the BOM level and the parent they belong to. Basically each parent that is an assembly of smaller sub parts needs to have its sub parts added to a collection within that object. I believe we have some parts that may iterate up to 10 levels deep, and I don't like the way this pattern is going.
This data set is from a stored procedure that runs a complicated CTE query, and I'm not interested in trying to make this work with Entity Framework, as we have a lot of databases in the mix, and we need better control over the SQL that we're writing (hence the stored procedure). Ultimately this will end up as JSON that I'll render as a collapsible tree in a browser.
Here's the ugly part that I'm hoping to clean up a bit. The gist of what's going on here before I begin iterating over the various levels is:
Establish the first item (incomplete, but this is the main item).
Execute a stored procedure (with parameter) to return a datatable.
Create a flattened list of the objects I want to nest.
Get the total number of nested levels that exist on the BOM.
Iterate over the various levels in a very ugly way.
public BOMItemModel GetItemBOM(string item)
{
try
{
var bom = new BOMItemModel { PLPartNumber = item, BOMLevel = 0 };
var par = new Hashtable();
par.Add("#Item", item);
var dt = db.GetDataTable("[part].[getItemBOM]", par);
var bomList = new List<BOMItemModel>();
foreach(DataRow r in dt.Rows)
{
bomList.Add(MapBomItem(r));
}
var bomLevels = bomList.Max(x => x.BOMLevel);
for(var i = 1; i < bomLevels; i++)
{
foreach (var b in bomList.Where(x => x.BOMLevel == i).ToList())
{
if (i == 1)
{
bom.SubItems.Add(b);
}
else
{
if (i == 2)
{
var lvl2Items = bom.SubItems;
var parent1 = lvl2Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent1 != null) parent1.SubItems.Add(b);
}
if (i == 3)
{
var lvl2Items = bom.SubItems;
foreach(var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
var parent2 = lvl3Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent2 != null) parent2.SubItems.Add(b);
}
}
if (i == 4)
{
var lvl2Items = bom.SubItems;
foreach (var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
foreach (var lvl3 in lvl3Items)
{
var lvl4Items = lvl3.SubItems;
var parent3 = lvl4Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent3 != null) parent3.SubItems.Add(b);
}
}
}
if (i == 5)
{
var lvl2Items = bom.SubItems;
foreach (var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
foreach (var lvl3 in lvl3Items)
{
var lvl4Items = lvl3.SubItems;
foreach (var lvl4 in lvl4Items)
{
var lvl5Items = lvl4.SubItems;
var parent4 = lvl5Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent4 != null) parent4.SubItems.Add(b);
}
}
}
}
if (i == 6)
{
var lvl2Items = bom.SubItems;
foreach (var lvl2 in lvl2Items)
{
var lvl3Items = lvl2.SubItems;
foreach (var lvl3 in lvl3Items)
{
var lvl4Items = lvl3.SubItems;
foreach (var lvl4 in lvl4Items)
{
var lvl5Items = lvl4.SubItems;
foreach (var lvl5 in lvl5Items)
{
var lvl6Items = lvl5.SubItems;
var parent5 = lvl6Items.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber);
if (parent5 != null) parent5.SubItems.Add(b);
}
}
}
}
}
}
}
}
return bom;
}
catch (Exception ex)
{
Log.Error(ex.Message, ex);
}
return null;
}
This is what my model or class looks like:
public class BOMItemModel
{
public BOMItemModel()
{
SubItems = new List<BOMItemModel>();
}
public string ParentPLNumber { get; set; }
public string PartNumber { get; set; }
public string PartListName { get; set; }
public string ItemNumber { get; set; }
public string PLPartNumber { get; set; }
public string PartType { get; set; }
public string PLManufacturer { get; set; }
public string PLDescription { get; set; }
public decimal Qty { get; set; }
public int BOMLevel { get; set; }
public List<BOMItemModel> SubItems { get; set; }
}
The last property on this class is where I'm stuffing sub items of the parent item, and this can nest several levels deep.
Yes, the following would be much better:
for (var i = 1; i <= bomLevels; i++)
{
foreach (var b in bomList.Where(x => x.BOMLevel == i).ToList())
{
if (i == 1)
{
bom.SubItems.Add(b);
}
else
{
var parent = bomList.FirstOrDefault(x => x.PLPartNumber == b.ParentPLNumber && b.BOMLevel - 1 == x.BOMLevel);
if (parent != null) parent.SubItems.Add(b);
}
}
}
Also, I would throw an error when parent is not found. For a more generic approach have a look at my other example: Converting table in tree

Removing cloned nodes from list

I am making a filtereditems class, that will be displayed as a treeview in WPF. The filtereditems class contains only certain node items from a treeitems class that contain certain criteria. I am able clone all of the tree items and add them to the filtereditems list. From there I find nodes that do not meet the criteria and remove them appropriately. However I have found that using the clones renders me unable to remove these items. Is there something I should know about cloned items and why they can't be removed from my collection?
public class Node: INotifyPropertyChanged, ICloneable
{
public Name { get;set;}
public ID {get;set;}
public ParentNode {get;set;}
public ObservableCollection<Nodes> ChildNodes{get;set;}
public object Clone()
{
Node toReturn = new Node();
toReturn.Name = this.Name;
toReturn.ID = this.ID;
toReturn.ParentNode = this.ParentNode;
foreach (Node child in this.ChildNodes)
{
toReturn.ChildNodes.Add((Node) child.Clone());
}
return toReturn;
}
}
public void filterStart(ChildNodesListViewDataSource _filterStart)
{
if (this.FilterString != null && this.Entity != null)
{
this.TotalItemsNumber = 0;
this.FilterItemsNumber = 0;
this.FilterTreeItems.Clear();
foreach (Node y in TreeItems)
{
this.FilterTreeItems.Add((Node)y.Clone());
foreach (Node x in FilterTreeItems)
{
FilterRoot(x);
}
}
TakeOutTrash();
public bool FilterRoot(Node FilterItems)
{
bool HasMatchingChildren = false;
if (FilterItems.ChildNodes != null || FilterItems.ChildNodes.Count !=0)
{
foreach (Node FilterItemsComponenents in FilterItems.ChildNodes)
{
if (FilterRoot(FilterItemsComponenents))
{
HasMatchingChildren = true;
}
}
}
string NameOfFilterItem = FilterItems.Name.ToUpper();
string FilterStringUpperCase = FilterString.ToUpper();
bool FilterStringCheck = NameOfFilterItem.Contains(FilterStringUpperCase);
if (!FilterStringCheck && !HasMatchingChildren)
{
trimIDs.TrashCan.Add(FilterItems);
return false;
}
else
{
return true;
}
}
public void TakeOutTrash()
{
foreach (Node node in trimIDs.TrashCan)
{
this.FilterTreeItems.Remove(node);
}
}
public class TrimIDs
{
public IList<ComponentNodeViewModel> TrashCan { get; set;}
{
TrashCan = new List<ComponentNodeViewModel>();
}
}
I actually solved my issue by creating another clone method that clones anything Node that is not in the List of Nodes not to clone: (Hope this helps anyone that was in my same predicament).
public object Clone(IList<Node> ListNotToClone)
{
NodetoReturnFiltered = new Node();
toReturnFiltered.Name = this.Name;
toReturnFiltered.ID = this.ID;
toReturnFiltered.ParentNode= this.ParentNode;
foreach (Node child in this.ComponentNodes)
{
if (!ListNotToClone.Contains(child))
{
toReturnFiltered.ComponentNodes.Add((Node)child.Clone(ListNotToClone));
}
}
return toReturnFiltered;
}
I then used this method :
public void TakeOutTrash()
{
foreach (ComponentNodeViewModel root in this.FilterTreeItems)
{
FilterHolder = (ComponentNodeViewModel)root.Clone(trimIDs.TrashCan);
}
}
And passed FilterHolder back into FilterTreeItems in the FilterStart method,
this.FilterTreeItems.Clear();
this.FilterTreeItems.Add(FilterHolder);

How to get parent node in a tree structure like this

How it would be possible to get a parent when tree structure is like this:
public class TreeModel
{
public int ID { get; set; }
public List<TreeModel> Children { get; set; }
}
Let's say we can't add a parent element item to this class (public TreeModel Parent { get; set; }).
Edit
How to get element m22 (ID=22) parent m2 (ID=2) from the m1? I thought we could iterate through m1 and somehow return parent when condition is right.
var m1 = new TreeModel() { ID = 1 };
var m2 = new TreeModel() { ID = 2 };
var m21 = new TreeModel() { ID = 21 };
var m22 = new TreeModel() { ID = 22 };
var m3 = new TreeModel() { ID = 3 };
m1.Children.Add(m2);
m2.Children.Add(m21);
m2.Children.Add(m22);
m1.Children.Add(m3);
var parent = m1.GetParent(p => p.ID == 22); //<-- How?
public IEnumerable<TreeModel> GetAllDescendants(IEnumerable<TreeModel> rootNodes)
{
var descendants = rootNodes.SelectMany(_ => GetAllDescendants(_.Children));
return rootNodes.Concat(descendants);
}
public static TreeModel GetParent(this TreeModel rootNode, Func<TreeModel, bool> childSelector)
{
var allNodes = GetAllDescendants(new [] { rootNode });
var parentsOfSelectedChildren = allNodes.Where(node => node.Children.Any(childSelector));
return parentsOfSelectedChildren.Single();
}
m1.GetParent(_ => _.ID == 22);
Obtain a flat list of all nodes
Search this list for the node whose direct children contains m22
Use this code pattern. It simplifies the code because you don't have to explicitly add nodes to the children and each node knows who its parent is and who its children are. Also it is all type safe.
class Program
{
static void Main(string[] args)
{
var m1=new TreeModel() { ID=1 };
var m2=new TreeModel(m1) { ID=2 };
var m21=new TreeModel(m2) { ID=21 };
var m22=new TreeModel(m2) { ID=22};
var m3=new TreeModel(m1) { ID=3 };
var item=m1.RecursiveFind((p) => p.ID==22);
var parent=item.Parent;
// parent.ID == 2
var root=item.Root;
// root.ID == 1;
}
}
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
When you derive from Tree<T>, you create custom tree structures that you design what the node class is (TreeModel here) and how to handle parents, children and siblings if needed.
What about:
public class SaneTreeModel: TreeModel
{
public SaneTreeModel Parent { get; set; }
}
}
I would approach this by first finding the first element that satisfies the condition (ID == 22 in your example) and then finding the parent of this element. Not the best solution but maybe you will need them separately for something else.
public TreeModel GetParent(Func<TreeModel, bool> function)
{
return GetParent(Where(function));
}
private TreeModel GetParent(TreeModel treeModel)
{
if (Children == null) return null;
if (Children.Contains(treeModel)) return this;
foreach (TreeModel child in Children)
{
TreeModel result = child.GetParent(treeModel);
if (result != null)
return result;
}
return null;
}
private TreeModel Where(Func<TreeModel, bool> function)
{
if (Children == null) return null;
foreach (TreeModel child in Children)
{
if (function(child))
return child;
TreeModel result = child.Where(function);
if (result != null)
return result;
}
return null;
}
If you put this code block in your TreeModel class, the example you provided will return m2
Absolutely not, with a child node like that you can't get its parent. Simply because there isn't any reference to it.
To get the parent of the node, you have to either add the parent field or save the reference in somewhere else (by a variable or something).
EDIT
#Zulis If you search from the root node, you can definitely find the node you want. But as I said, with just the child node you can't do that.
But I think you should avoid searching because that would be slow

StackOverFlowException inside a recursive function

I have a table in database as follows:
MenuItem
-------------
MenuItemId 1 --------+
MenuItemName |
ParentId * --------+
Now I have written a recursive function to get all the Parent MenuItems with their children.
private ICollection<MenuItem> GetAllChildrenOfSpecificMenuItemRecursively(MenuItem menuItem, IEnumerable<MenuItem> menuItems)
{
ICollection<MenuItem> Children = null;
foreach (MenuItem mi in menuItems)
{
if (mi.ParentMenuItemId != null)
{
if (mi.ParentMenuItemId == menuItem.MenuItemId)
{
Children.Add(mi);
}
else
{
return GetAllChildrenOfSpecificMenuItemRecursively(mi, menuItems);
}
}
}
return Children;
}
Now, I am calling it from another function as follows:
public IEnumerable<MenuItem> GetAllParentMenuItemsWithChildren()
{
List<MenuItem> MenuItems = new List<MenuItem>();
IEnumerable<MenuItem> AllMenuItems = null;
using (MaxContext entityContext = new MaxContext())
{
AllMenuItems = (from e in entityContext.MenuItemSet select e).ToList();
foreach (MenuItem menuItem in entityContext.MenuItemSet)
{
if (menuItem.ParentMenuItemId == null)
{
menuItem.Children = GetAllChildrenOfSpecificMenuItemRecursively(menuItem, AllMenuItems);
MenuItems.Add(menuItem);
}
}
}
return MenuItems;
}
But it gives me stackoverflowException inside the recursive function. I am sure that I am making a minor mistake in that function. Can anybody point out that mistake?
Your GetAllChildrenOfSpecificMenuItemRecursively() always recurses with mi. It should recurse with mi.ParentMenuItemId instead.
I think there is a simpler (and maybe faster) way of doing this without using recursion. Something like this:
public ICollection<MenuItem> GetMenuItemsAsTreeList()
{
AllMenuItems = entityContext.MenuItemSet.ToList();
Dictionary<int, MenuItem> dic = AllMenuItems.ToDictionary(n => n.Id, n => n);
List<MenuItem> rootMenuItems = new List<MenuItem>();
foreach (MenuItem menuItem in AllMenuItems)
{
if (menuItem.ParentMenuItemId.HasValue)
{
MenuItem parent = dic[menuItem.ParentMenuItemId.Value];
menuItem.ParentMenuItem = parent;
parent.SubMenuItems.Add(menuItem);
}
else
{
rootMenuItems.Add(menuItem);
}
}
return rootMenuItems;
}
Why are you always passing the same menuItems colleciton into recursive function?
your code should be something along the lines of:
private IEnumerable<MenuItem> GetAllChildrenOfSpecificMenuItemRecursively(MenuItem menuItem)
{
var children = new List<MenuItem>();
foreach (MenuItem mi in menuItem.Children)
{
// Why are you checking this?
if (mi.ParentMenuItemId != null)
{
// Why are you checking this?
if (mi.ParentMenuItemId == menuItem.MenuItemId)
{
children.Add(mi);
}
else
{
children.AddRange(GetAllChildrenOfSpecificMenuItemRecursively(mi))
}
}
}
return Children;
}
From method name, this is all that it should be doing:
private IEnumerable<MenuItem> GetAllChildrenOfSpecificMenuItemRecursively(MenuItem menuItem)
{
var children = new List<MenuItem>();
if (menuItem.Children == null) return children;
foreach (MenuItem mi in menuItem.Children)
{
children.Add(mi);
children.AddRange(GetAllChildrenOfSpecificMenuItemRecursively(mi));
}
return Children;
}
Edit:
public class MenuItem
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
public virtual MenuItem Parent { get; set; }
[InverseProperty("Parent")]
public virtual ICollection<MenuItem> Children { get; set; }
}

recursive function in collection

I am using c# and List collection and loaded the values. Once it is done I am trying to read them recursively but some how I am not able to achieve this.
the following is my main code.
private static void Main(string[] args)
{
var node = new Node
{
Name = "N1",
Nodes =
new List<Node>
{
new Node { Name = "N1a" },
new Node { Name = "N1b", Nodes = new List<Node> { new Node { Name = "N1B1" } } },
new Node
{
Name = "N1c",
Nodes =
new List<Node> { new Node { Name = "N1C1", Nodes = new List<Node> {new Node{Name = "N1C1A"} } } }
}
}
};
GetNodes( node );
Console.ReadLine();
}
public class Node
{
public string Name { get; set; }
public IList<Node> Nodes { get; set; }
}
and the function call is following
public static IEnumerable<Node> GetNodes(Node node)
{
if (node == null)
{
return null;
}
Console.WriteLine(node.Name);
foreach (var n in node.Nodes)
{
return GetNodes(n);
}
return null;
}
}
Could any one please help me to fix the recursive function?
If you just want to print the names of all the nodes,
public static void GetNodes(Node node)
{
if (node == null)
{
return;
}
Console.WriteLine(node.Name);
foreach (var n in node.Nodes)
{
GetNodes(n);
}
}
If you want to flatten the tree,
public static IEnumerable<Node> GetNodes(Node node)
{
if (node == null)
{
yield break;
}
yield return node;
foreach (var n in node.Nodes)
{
foreach(var innerN in GetNodes(n))
{
yield return innerN;
}
}
}
public static IEnumerable<Node> GetNodes(Node node)
{
if (node == null) return null;
var nodes = new List<Node>();
nodes.Add(node);
Console.WriteLine(node.Name);
if (node.Nodes != null)
{
foreach (var n in node.Nodes)
{
nodes.AddRange(GetNodes(n));
}
}
return nodes;
}}
Your method only every returns null, or calls itself and then either returns null, or calls itself..... so at the end of the day it returns null or doesn't terminate. If you want to seralize the values you can write them into a list at the same point in the code as you write them to the console.
public static void GetNodes(Node node, List<Node> output)
{
if (node == null)
return;
output.Add(node);
Console.WriteLine(node.Name);
foreach (var n in node.Nodes)
{
GetNodes(n, output);
}
}
You return from your method inside a loop immediately, on teh first iteration. No other iterations is performed.

Categories