Populate hierarchy in Dictionary - c#

I need to build a hierarchy and I am using a dictionary.
I read strings randomly when I am trying to build this
and they have this format:
address.city.streetname.housenumber
address.areacode
address.city.streetname.coAddress
I have a problem figuring out how to populate the entire hierarchy
This is what I have done:
public class JsonElement
{
public string parent { get; set; }
public string name { get; set; }
public List<JsonElement> childrenJsonElements { get; set; }
}
var dictionaryHierarchy = new Dictionary<string, JsonElement>();
List<string> stringList = new List<string>()
{ "address.city.streetname.housenumber",
"address.areacode",
"address.city.streetname.coAddress"};
foreach(string element in stringList)
{
string[] tagsStringArray = element.Split('.');
if (!dictionaryHierarchy.ContainsKey(tagsStringArray[0]))
{
dictionaryHierarchy.Add(tagsStringArray[0], new JsonElement());
}
dictionaryHierarchy = AddElementsToHierarchy();
}
private static Dictionary<string, JsonElement> AddElementsToHierarchy(Dictionary<string,
JsonElement> dictionaryHierarchy, string element)
{
JsonElement jsonElement = new JsonElement();
string[] tagsStringArray = element.Split('.');
if (tagsStringArray.Length < 2)
{
return dictionaryJsonHierarchy;
}
jsonElement = dictionaryHierarchy[tagsStringArray[0]];
int ix = 1;
while (ix < tagsStringArray.Length)
{
if (jsonElement.name != tagsStringArray[ix])
{
jsonElement.parent = tagsStringArray[ix-1];
jsonElement.name = tagsStringArray[ix];
}
else
{
; // This part is for adding children
}
ix++;
}
return dictionaryHierarchy;
}

You have a tree structure made up of JsonElement nodes. This structure is the only data structure you need. Let's redefine JsonElement:
public class JsonElement
{
public string Parent { get; set; }
public string Name { get; set; }
public List<JsonElement> Children { get; } = new List<JsonElement>();
}
We made the properties PascalCase. This is the usual naming convetion. The Children are a read-only property with an initializer which instantiates the list.
As suggested, we add some more examples:
var input = new[] {
"address.city.streetname.housenumber",
"address.areacode",
"address.city.streetname.coAddress",
"person.name.firstname",
"person.name.lastname"
};
Now, we have two different elements at the start of the hierarchy. To enable this scenario, we add a neutral root element with a null name.
var root = new JsonElement();
foreach (string s in input) {
AddElements(root, s.Split('.'));
}
Now, let's create the hierarchy.
Adding elements consists of walking down the tree structure by following the tags (names). If one is missing, we add it.
private static void AddElements(JsonElement node, string[] elements)
{
foreach (string element in elements) {
var child = node.Children.Find(child => child.Name == element);
if (child == null) {
child = new JsonElement {
Parent = node.Name,
Name = element
};
node.Children.Add(child);
}
node = child; // Walk down the tree
}
}
We can test the result with this recursive method:
private static void PrintChildren(JsonElement node, int level = 0)
{
string indent = new String(' ', 4 * level);
foreach (var child in node.Children) {
Console.WriteLine($"{indent}{child.Name}, Parent = {child.Parent}");
PrintChildren(child, level + 1);
}
}
Called with PrintChildren(root); it prints:
address, Parent =
city, Parent = address
streetname, Parent = city
housenumber, Parent = streetname
coAddress, Parent = streetname
areacode, Parent = address
person, Parent =
name, Parent = person
firstname, Parent = name
lastname , Parent = name
See also:
Data Structure and Algorithms - Tree
Tree (data structure)

Related

C# Creating Binary Tree Treeview from folders and files

I am trying to create my own treeview binary tree where it shows folders and children / parents for certain folders.
I have a big list of folders then I have it in a custom object the Index, Parent Name, Children bool, and display name on the tree.
I can create the tree but when it creates it all the children are under the first node and not under the correct ones.
I see that treenode has level and parent if I could set those then this would be easy but I see they are readonly so I’m kinda stuck so far…
List<TreeNodeEnd> lstTreeNod = new List<TreeNodeEnd>();
Dictionary<int, TreeNode> valuePairs = new Dictionary<int, TreeNode>();
foreach (var node in lstTreeNod)
{
TreeNodeCollection items;
TreeNode treeNode;
if (node.TreeIndex == 0)
{
//node.DisplayName = "PDM Vault";
//TreeNode root2 = new TreeNode("PDM Vault", 3, 3, items);
//root.Name = "PDM Vault";
items = treeView1.Nodes;
//items = items.Add(node.DisplayName);
//treeView1.Nodes
}
else //in this ELSE DETERMINE ABOUT FATHER & CHILDREN EQUALS AND NOT NULL
{ // ValuePairs = Final Tree ?
//items = valuePairs[node.TreeIndex - 1].Nodes;
items = valuePairs[node.TreeIndex].Nodes;
TreeNode treeNode = items.Add(node.DisplayName);
}
TreeNode treeNode = items.Add(node.DisplayName);
if (valuePairs.ContainsKey(node.TreeIndex))
{
if (node.HasChildren == true)
valuePairs[node.TreeIndex] = treeNode;
}
else
{
valuePairs.Add(node.TreeIndex, treeNode);
}
}
public class TreeNodeEnd
{
public string DisplayName { get; set; }
public int TreeIndex { get; set; }
public bool HasChildren { get; set; }
public string Father { get; set; }
}

Recursively create a tree hierarchy off an array of strings

I have an array of strings separated by "!". I am trying to break that string up and create a tree hierarchy recursively in my custom class called PivotGroup. For example, what I am aiming at is to break up string array
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891"}
Into the PivotGroup class such as PivotGroup contains ChildGroups[] that embed the array strings.
So for example:
PivotGroup pgGroup = new PivotGroup();
pgGroup.ChildGroups[0] = PivotGroup[]; // Key:Book Level 3 Value: "AAA"
Now within Book Level 3 ChildGroups I need to set Book Level 4 which value is "EEE" and within the ChildGroups of "EEE" I would need to create another childGroup array which size in the case would be 3 called Book Level 5 and set another PivotGroup for each of following 15712, 15722, 13891
Here is my PivotGroup Class and embedded class Objects:
public class PivotGroup
{
public PivotGroup() { }
public PivotGroup(PivotGroupKey groupKey, PivotRow data, PivotGroup[] childGroups, bool leaf, int groupLevel)
{
GroupKey = groupKey;
Data = data;
ChildGroups = childGroups;
Leaf = leaf;
GroupLevel = groupLevel;
}
public PivotGroupKey GroupKey { get; private set; }
public PivotRow Data { get; private set; }
public PivotGroup[] ChildGroups { get; set; }
public bool Leaf { get; private set; }
public int GroupLevel { get; private set; }
public override string ToString()
{
return GroupKey + ", GroupLevel: " + GroupLevel + ", Children: " +
ChildGroups.Length + (Leaf ? " (Leaf)" : "");
}
}
public class PivotGroupKey
{
public PivotGroupKey()
{
}
public PivotGroupKey(string keyGroup, string keyValue)
{
if(keyGroup != null)
KeyGroup = string.Intern(keyGroup);
if (keyValue != null)
KeyValue = string.Intern(keyValue);
}
public string KeyGroup { get; private set; }
public string KeyValue { get; private set; }
public override string ToString()
{
return KeyGroup + ": " + KeyValue;
}
}
public class PivotRow
{
public PivotRow()
{
}
public PivotRow(string key, params object[] data) : this(key, true, data) { }
public PivotRow(string key, bool entitled, params object[] data)
{
Data = data;
Key = null;
Entitled = entitled;
}
public object[] Data { get; private set; }
public bool Entitled { get; private set; }
public string Key { get { return null; } set { } }
}
Main program I tried:
public class BookLevels
{
public string Root { get; set; }
public string BookLevel2 { get; set; }
public string BookLevel3 { get; set; }
public string BookLevel4 { get; set; }
public string BookLevel5 { get; set; }
}
class Program
{
static void BuildTree(string[] paths)
{
var BookPaths = paths.Select(x => x.Split('!'))
.Select(x => new BookLevels
{
Root = x[0],
BookLevel2 = x[1],
BookLevel3 = x[2],
BookLevel4 = x[3],
BookLevel5 = x[4]
}).GroupBy(z => new { z.BookLevel3, z.BookLevel4 }).ToArray();
var BookLevel3Cnt = BookPaths.Select(q => q.Key.BookLevel3).Count();
PivotGroup root = new PivotGroup(
new PivotGroupKey("Total", ""),
new PivotRow(null, new string[8]),
new PivotGroup[BookLevel3Cnt], false, 0);
foreach (var booklevel3 in BookPaths)
{
AddChildren(root, booklevel3);
}
}
private static void AddChildren(PivotGroup root, IGrouping<object, BookLevels> booklevel, int index = 0)
{
root.ChildGroups[index] = new PivotGroup(
new PivotGroupKey("Book Level " + (index + 3).ToString(), booklevel.Key.ToString()),
new PivotRow(null, new string[8]),
AddChildren(root, booklevel[index], index + 1), false, 0);
}
static void Main(string[] args)
{
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891",
"ROOT!ZZZ!AAA!DDD!15712",
"ROOT!ZZZ!AAA!DDD!15722",
"ROOT!ZZZ!AAA!DDD!13891",
"ROOT!ZZZ!BBB!DDD!15812",
"ROOT!ZZZ!BBB!DDD!15822",
"ROOT!ZZZ!BBB!DDD!13891",
};
BuildTree(paths);
Console.WriteLine();
Console.ReadLine();
}
I think my issue might be the way I am creating the Linq statement that breaks up the string, since I'm not sure how to progress thru it recursively.
I'm not sure what goes into which property. Also, for sake of simplicity and to be able to concentrate on the recursive algorithm, I redefine the group class like this (it does not mean that you have to change your class, instead, adapt my algorithm):
public class PivotGroup
{
public string Key { get; set; }
public List<PivotGroup> ChildGroups { get; } = new List<PivotGroup>();
public override string ToString() => Key; // Makes debugging easier.
}
The idea is that the values of the path go into the key. I made ChildGroups a list to be able to add children successively. My BuildTree returns the root
static PivotGroup BuildTree(string[] paths)
{
var root = new PivotGroup { Key = "ROOT" };
foreach (string path in paths) {
AddChildren(root, path.Split('!').Skip(1).ToList());
}
return root;
}
The recursive part goes into AddChildren. I convert the path into a List<string> to be able to remove the added part. AddChildren assumes that the first item in path is the first child to be added.
static void AddChildren(PivotGroup group, List<string> path)
{
string key = path[0];
int index = group.ChildGroups.FindIndex(g => g.Key == key);
PivotGroup child;
if (index >= 0) { // A child with this key exists.
child = group.ChildGroups[index]; // Select this existing child.
} else { // This key is missing. Add a new child.
child = new PivotGroup { Key = key };
group.ChildGroups.Add(child);
}
if (path.Count > 1) {
path.RemoveAt(0); // Remove the added child key and add the rest recursively.
AddChildren(child, path);
}
}
We add children by walking down the tree and adding new children if necessary.
This prints the tree recursively:
private static void PrintTree(PivotGroup group, int level)
{
Console.WriteLine(new String(' ', 2 * level) + group.Key);
foreach (PivotGroup child in group.ChildGroups) {
PrintTree(child, level + 1);
}
}
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
...
};
PivotGroup root = BuildTree(paths);
PrintTree(root, 0);
Console.ReadKey();
We could also use a loop instead of doing a recursion, since we add one branch at a time:
static PivotGroup BuildTree(string[] paths)
{
var root = new PivotGroup { Key = "ROOT" };
foreach (string path in paths) {
PivotGroup group = root;
string[] pathElements = path.Split('!');
for (int i = 1; i < pathElements.Length; i++) { // Element [0] is ROOT, we skip it.
string key = pathElements[i];
int index = group.ChildGroups.FindIndex(g => g.Key == key);
PivotGroup child;
if (index >= 0) { // A child with this key exists.
child = group.ChildGroups[index]; // Select this existing child.
} else { // This key is missing. Add a new child.
child = new PivotGroup { Key = key };
group.ChildGroups.Add(child);
}
group = child;
}
}
return root;
}
List<T>.FindIndex is inefficient for large lists. If you have large data sets and the order does not matter, switch to Dictionary<string, PivotGroup>. If you need the data to be sorted, use SortedDictionary<string, PivotGroup>.
Here is some simple recursive code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891"};
List<List<string>> inputData = paths.Select(x => x.Split(new char[] {'!'}).ToList()).ToList();
Node root = new Node();
Node.ParseTree(root, inputData);
}
}
public class Node
{
public string name { get; set; }
public List<Node> children { get; set; }
public static void ParseTree(Node parent, List<List<string>> inputData)
{
parent.name = inputData.First().FirstOrDefault();
var groups = inputData.Select(x => x.Skip(1)).GroupBy(x => x.Take(1).FirstOrDefault());
foreach (var group in groups)
{
if (group.Key != null)
{
if (parent.children == null) parent.children = new List<Node>();
Node newNode = new Node();
parent.children.Add(newNode);
ParseTree(newNode, group.Select(x => x.Select(y => y).ToList()).ToList());
}
}
}
}
}

Search in hierarchy tree to get tree with only result node

I have hierarchy tree, each element has a list of children and so on.
This is the class:
public class HierarchyItem
{
public int? HierarchyID { get; set; }
public string label { get; set; }
public List<HierarchyItem> children { get; set; }
public int Level { get; set; }
public int ParentID { get; set; }
}
I am trying to search for all the nodes that their label contains my search term.
I already managed to get the nodes that match my search term.
In addition i created a recursion to get all the parents (until the top node) for every node that match my search term.
The problem is that in this way i get a reverse tree:
Every children has parent that has parent and so on instead of getting parent with children that has a children and so on.
Any Idea? How to act?
I am not sure it will help, but this is the relevant code.
HierarchyItem result = new HierarchyItem();
var fullHierarchies = _entities.Hierarchies.Where(p => !p.Deleted).ToList();
var hierarchiesResult = _entities.Hierarchies.Where(p => !p.Deleted && p.Name.Contains(searchTerm)).ToList();
List<HierarchyItemWithParentAsHierarchyItem> itemsWithTrees = new List<HierarchyItemWithParentAsHierarchyItem>();
var hierarchyTop = new HierarchyItemWithParentAsHierarchyItem
{
HierarchyID = null,
label = "Organization",
Code = "0",
Path = string.Empty,
Level = 0,
Parent = null
};
foreach (var item in hierarchiesResult)
{
var hierarchy = new HierarchyItemWithParentAsHierarchyItem
{
HierarchyID = item.HierarchyID,
label = item.Name,
Code = item.Code,
Path = string.Empty,
Level = item.Level.Value,
};
hierarchy.Parent.Add(GetParent(fullHierarchies, item, hierarchyTop));
itemsWithTrees.Add(hierarchy);
}
private HierarchyItemWithParentAsHierarchyItem GetParent(List<CloudEntities.Hierarchy> fullHierarchies, CloudEntities.Hierarchy item, HierarchyItemWithParentAsHierarchyItem hierarchyTop)
{
try
{
HierarchyItemWithParentAsHierarchyItem tempHierarchyItem;
CloudEntities.Hierarchy tempHierarchy = fullHierarchies.FirstOrDefault(x => x.HierarchyID == item.ParentID);
if (tempHierarchy == null)
return hierarchyTop;
else
{
tempHierarchyItem = new HierarchyItemWithParentAsHierarchyItem()
{
HierarchyID = tempHierarchy.HierarchyID,
label = tempHierarchy.Name,
Code = tempHierarchy.Code,
Path = string.Empty,
Level = tempHierarchy.Level.Value
};
tempHierarchyItem.Parent.Add(GetParent(fullHierarchies, tempHierarchy, hierarchyTop));
}
return tempHierarchyItem;
}
catch (Exception ex)
{
}
}

how to transform the following in C#?

I have the following model
public class Node
{
public int AutoIncrementId { get; set; }
public string Text { get; set; }
public List<Node> Nodes { get; set; }
...//other propeties
}
I want to transform the data into the following model,
public class TreeView
{
public int Id {get; set;}
public string Text {get; set;}
public List<TreeView> Items {get; set;}
}
I started with the following, but then realised how am I going to know when to stop?
the variable test holds the node data
var items = test.Data.Select(x => new TreeViewItemModel
{
Id = x.AutoIncrementId.ToString(),
Text = x.Text,
Items = x.Nodes.Select(y=> new TreeViewItemModel(
{
Id = y.AutoIncrementId.ToString(),
Text = y.Text,
Items = //do I keep going?
}));
}
);
You can use recursion to do that:
public TreeView ConvertToTreeView(Node node)
{
TreeView tv = new TreeView();
tv.Id = node.AutoIncrementId;
tv.Text = node.Text;
if (node.Nodes != null && node.Nodes.Count > 0)
{
tv.Items = new List<TreeView>();
node.Nodes.ForEach(x => tv.Items.Add(ConvertToTreeView(x)));
}
return tv;
}
For clarity and simplicity, this works nicely:
public TreeView ConvertNode(Node rootNode)
{
var tree = new TreeView
{
Id = rootNode.AutoIncrementId,
Text = rootNode.Text,
Items = new List<TreeView>()
};
if (rootNode.Nodes != null)
{
foreach (var node in rootNode.Nodes)
{
tree.Items.Add(ConvertNode(node));
}
}
return tree;
}
I prefer this form.
public TreeView ConvertToTreeView(Node node)
{
return new TreeView
{
Id = node.AutoIncrementId;
Text = node.Text;
Items = node.Nodes.Select(ConvertToTreeView).ToList()
};
}
Edit: Yes Baldrick, I did :P and
public TreeView ConvertToTreeView(Node node)
{
return new TreeView
{
Id = node.AutoIncrementId;
Text = node.Text;
Items = node.Nodes != null
? node.Nodes.Select(ConvertToTreeView).ToList()
: new List<TreeView>()
};
}
Just doesn't look as nice.

Select multiple elements with Linq to XML

I have a C# application and need to extract multiple elements from a Linq to XML collection.
I have the following extract from an XML file
<SNS>
<uniqueSystem><system>49</system><label>Engines</label>
<uniqueSubsystem><subsystem>30</subsystem><label>APU</label>
<uniqueUnit><unit>00</unit><label>Starter</label>
</uniqueUnit>
</uniqueSubsystem>
</uniqueSystem>
<uniqueSystem><system>50</system><label>Hydraulics</label>
<uniqueSubsystem><subsystem>30</subsystem><label>Reservoir</label>
<uniqueUnit><unit>00</unit><label>Pump</label>
</uniqueUnit>
</uniqueSubsystem>
</uniqueSystem></SNS>
I need to extract the values from within each 'uniqueSystem' element. So in the example above, under the 'SNS' element there are 2 'uniqueSystem' elements, and within each of these there are 'uniqueSubsystem' elements and 'uniqueUnit' elements each with 'label' elements. I need to extract this data to build a TreeView.
My problem is extracting multiple elements using Linq. How do i do this?
At the moment i have
var item = from items in doc.Descendants("SNS").Descendants("uniqueSystem").Descendants("system")
orderby items.Value
select items.Descendants("uniqueSystem");
I think this will give me a collection of the 'uniqueSystem' elements, from which i now need to extract the values of the multiple elements within. Can anybody please help?
My next attmpt is as follows, but this is giving me a null reference exception:
var item = from items in doc.Descendants("SNS").Descendants("uniqueSystem").Descendants("system")
orderby items.Value
select items.Descendants("uniqueSystem");
foreach (var e in item)
{
string sys = e.Descendants("system").FirstOrDefault().Value;
string sysLabel = e.Descendants("system").Descendants("label").FirstOrDefault().Value;
string subsys = e.Descendants("subsystem").FirstOrDefault().Value;
string subsysLabel = e.Descendants("subsystem").Descendants("label").FirstOrDefault().Value;
string unit = e.Descendants("unit").FirstOrDefault().Value;
string unitLabel = e.Descendants("unit").Descendants("label").FirstOrDefault().Value;
buildSystemNodes(sys, sysLabel);
//getSubSystems(myitem);
}
So the code I have written below will generate the tree view. The treeview class that I am using is in the namespace System.Web.UI.WebControls. The function is however O(n^3).
public void ProcessXml(string document)
{
var doc = XDocument.Parse(document, LoadOptions.None);
var uniqueSystemList = doc.Element("SNS").Elements();
var treeView = new TreeView();
string value = string.Empty;
string text = string.Empty;
foreach (var uniqueSystem in uniqueSystemList)
{
value = uniqueSystem.Element("label").Value.ToString();
text = uniqueSystem.Element("system").Value.ToString();
var uniqueSystemNode = new TreeNode(text, value);
var uniqueSubsystemList = uniqueSystem.Elements("uniqueSubsystem");
foreach (var uniqueSubSystem in uniqueSubsystemList)
{
value = uniqueSubSystem.Element("label").Value.ToString();
text = uniqueSubSystem.Element("subsystem").Value.ToString();
var uniqueSubSystemNode = new TreeNode(text, value);
var uniqueUnitList = uniqueSubSystem.Elements("uniqueUnit");
foreach (var uniqueUnit in uniqueUnitList)
{
value = uniqueUnit.Element("label").Value.ToString();
text = uniqueUnit.Element("unit").Value.ToString();
var uniqueUnitNode = new TreeNode(text, value);
uniqueSubSystemNode.ChildNodes.Add(uniqueUnitNode);
}
uniqueSystemNode.ChildNodes.Add(uniqueSubSystemNode);
}
treeView.Nodes.Add(uniqueSystemNode);
}
}
Check if this kind of code will work out for your requirement
var doc = XDocument.Load(#"..\XMLFile1.xml");
var res = doc.XPathSelectElements("/SNS/uniqueSystem");
foreach (var item in res)
{
var us = new UniqueSystem
{
System = int.Parse(item.Element("system").Value),
Label = item.Element("label").Value
};
if (item.Element("uniqueSubsystem") != null)
{
// process the logic here
}
}
Where you can have models like the ones shown below to bind to
class UniqueSystem
{
public int System { get; set; }
public string Label { get; set; }
public List<uniqueSubsystem> SubSystems { get; set; }
}
class uniqueSubsystem
{
public int subsystem { get; set; }
public string Label { get; set; }
public List<uniqueUnit> Units { get; set; }
}
class uniqueUnit
{
public int unit { get; set; }
public string label { get; set; }
}
By the following code you can get the uniqueSubsystem
XmlNodeList nodes = xml.SelectNodes("SNS/uniqueSystem/uniqueSubsystem");
foreach (XmlNode i in nodes)
{
Response.Write(i.InnerXml);
}
If you want the nodes:
XmlNodeList labels1 = xml.SelectNodes("SNS/uniqueSystem/uniqueSubsystem/label");
XmlNodeList labels2 = xml.SelectNodes("SNS/uniqueSystem/uniqueSubsystem/uniqueUnit/label");
Response.Write("SNS/uniqueSystem/uniqueSubsystem/label<br />");
foreach (XmlNode i in labels1)
{
Response.Write(i.InnerText+"<br />");
}
Response.Write("<br />SNS/uniqueSystem/uniqueSubsystem/uniqueUnit/label<br />");
foreach (XmlNode i in labels2)
{
Response.Write(i.InnerText+"<br />");
}
Let me know if this helped you.

Categories