I want to create a treeview in c# which will group file by prefix (here the prefix is a marked by the separator _). The following files should give this tree:
Files list :
p_a
p_a_test
p_LIG
p_p
p_p_c
p_p_c2
p_p_ccc
p_p_test
p_tres
TestLineGraph1
TestLineGrpah
Corresponding tree:
|--p_
|--p_a
|--p_a_test
|--p_LIG
|--p_p
|--p_p_
|--p_p_c
|--p_p_c2
|--p_p_ccc
|--p_p_test
|--p_tres
TestLineGraph1
TestLineGrpah
Here's my attempt of code:
private GraphUINode(List<string> subNodes, GraphUINode parent, string name, int lvl = 0)
: base(parent.m_viewDataSubControl)
{
parent.Nodes.Add(this);
this.Name = name;
this.Text = name;
string currentPrefix = "";
int pertinentSubNodes = 0;
while (pertinentSubNodes < subNodes.Count -1 && subNodes[pertinentSubNodes].Split('_').Length < 2+ lvl)
pertinentSubNodes++;
for (int i = 0; i <= lvl; i++)
{
currentPrefix += subNodes[pertinentSubNodes].Split('_')[i] + "_";
}
List<String> children = new List<string>();
foreach (string child in subNodes)
{
// The child is in the same group than the previous one
if (child.StartsWith(currentPrefix))
{
children.Add(child);
}
else
{
// Create a node only if needed
if (children.Count > 1)
{
// Create the new node
new GraphUINode(children, this, currentPrefix, lvl + 1);
children.Clear();
children.Add(child);
}
else
{
new GraphTemplateNode(this, m_viewDataSubControl, child);
}
currentPrefix = "";
for (int i = 0; i <= lvl; i++)
{
currentPrefix += child.Split('_')[i] + "_";
}
}
}
}
But I miss a few ones in the final result:
How can I get its back? Even when I debug step by step I can't find the logical way to do it.
So the first thing that we'll want to do here is take our strings and turn them into a tree. Once we have a tree then mapping those nodes to a TreeView is quite easy.
We'll start out with the definition for the tree itself:
public class Node<T>
{
public Node(T value, IEnumerable<Node<T>> children)
{
Value = value;
Children = children;
}
public T Value { get; private set; }
public IEnumerable<Node<T>> Children { get; private set; }
}
Nice and easy, each node is just a value and a collection of children.
Next we'll write a method to take a sequence of sequences, and build a tree from it. The idea here is that we'll group all of the items based on the first value in their sequence, build a node for each group, and then recursively call the method on the group to get the children for that node.
public static IList<Node<T>> GroupToTree<T>(this IEnumerable<IEnumerable<T>> source)
{
return GroupToTree(source.Select(sequence => sequence.GetEnumerator()));
}
private static IList<Node<T>> GroupToTree<T>(IEnumerable<IEnumerator<T>> source)
{
return source.WhereHasNext()
.GroupBy(iterator => iterator.Current)
.Select(group => new Node<T>(group.Key, GroupToTree(group)))
.ToList();
}
//This ensures that the iterators all get disposed
private static IEnumerable<IEnumerator<T>> WhereHasNext<T>(
this IEnumerable<IEnumerator<T>> source)
{
foreach (var iterator in source)
{
if (iterator.MoveNext())
yield return iterator;
else
iterator.Dispose();
}
}
Now we can take the raw data, split each of the strings into sequences of strings, and then map each of the nodes that we have here into UI-based nodes for presentation:
List<string> rawData = new List<string>();
//TODO populate raw data
Func<Node<string>, TreeNode> selector = null;
selector = node => new TreeNode(node.Value, node.Children.Select(selector).ToArray());
var nodes = rawData.Select(line => line.Split('_').AsEnumerable())
.GroupToTree()
.Select(selector);
Related
I have a list of objects with properties. The object class shown below:
public class ElementImpression
{
public int ElementId { get; private set; }
public string FamilyAndTypeName { get; private set; }
public string CategoryName { get; private set; }
public int CategoryNumber { get; private set; }
public string SystemAbbreviation { get; private set; }
public ElementImpression(Element e)
{
ElementId = e.Id.IntegerValue;
FamilyAndTypeName = e.get_Parameter(BuiltInParameter.ELEM_FAMILY_AND_TYPE_PARAM).AsValueString();
CategoryName = e.Category.Name;
CategoryNumber = e.Category.Id.IntegerValue;
SystemAbbreviation = e.get_Parameter(BuiltInParameter.RBS_DUCT_PIPE_SYSTEM_ABBREVIATION_PARAM).AsString();
}
}
The goal is to parse the list and create a structured, hierarchical presentation in a TreeView control. The number of levels in the hierarchy and which properties to use as nodes is defined at runtime by the user.
I have been succesful in creating the following treeview:
Treeview by using the following code:
private void UpdateTreeView(object sender, MyEventArgs e)
{
//Level 0: All
//Level 1: System Abbreviation
//Level 2: Category Name
//Level 3: Family and Type Name
treeView1.BeginUpdate();
treeView1.Nodes.Clear();
//Payload is a container object holding the list to be parsed. It is cached as a property in the form.
//Payload.ElementsInSelection is the list of objects to parse.
var lv1Group = Payload.ElementsInSelection.GroupBy(x => x.SystemAbbreviation);
treeView1.Nodes.Add("All");
int i = -1;
foreach (IGrouping<string, ElementImpression> group1 in lv1Group)
{
treeView1.Nodes[0].Nodes.Add(group1.Key);
var lv2Group = group1.ToList().GroupBy(x => x.CategoryName);
i++;
int j = -1;
foreach (IGrouping<string,ElementImpression> group2 in lv2Group)
{
treeView1.Nodes[0].Nodes[i].Nodes.Add(group2.Key);
var lv3Group = group2.ToList();
j++;
int k = -1;
foreach (ElementImpression ei in lv3Group)
{
k++;
treeView1.Nodes[0].Nodes[i].Nodes[j].Nodes.Add(ei.FamilyAndTypeName);
treeView1.Nodes[0].Nodes[i].Nodes[j].Nodes[k].Nodes.Add(ei.ElementId.ToString());
treeView1.Nodes[0].Nodes[i].Nodes[j].Nodes[k].Nodes.Add(ei.CategoryNumber.ToString());
}
}
}
treeView1.EndUpdate();
}
Is it possible to rewrite the UpdateTreeView() method, so that it accepts some kind of object, which tells the method how many levels and what properties to use, and then parses the data and creates the treeview dynamically at runtime? Is it possible to do it using recursion?
Well, I managed to arrive at a solution. It is not recursive, but it is dynamic. Properties can be added or removed at runtime and the following code should parse it and populate the tree.
The general idea is to loop through all the objects which need to be organized to determine the FullPath of the deepest level node. Because I am using property values as nodes, each object already holds its' FullPath subparts. They just need to be combined in the correct order.
Then, starting from the root node at the top level, which is added manually, walk every step of the path checking if the node exists and if not creating it. This method successfully renders my data as needed in a treeview.
The FindTreeNodeByFullPath() method is from here.
public void PopulateTreeview()
{
//Manually add root node
treeView1.Nodes.Add("All");
//Loop all the objects
foreach (ElementImpression e in Elements)
{
//Declare array to hold the names of all nodes in path to the element
//PropertiesList is an object containing information about what properties to consider and how many.
string[] pathParts = new string[PropertiesList.Length + 2];
//The name of root node
pathParts[0] = "All";
//Populate the path parts with values from elements
for (int i = 0; i < PropertiesList.Length; i++)
{
pathParts[i + 1] = PropertiesList[i].getPropertyValue(e);
}
//Finish the list with the name of the element (id currently)
pathParts[pathParts.Length - 1] = e.Id.IntegerValue.ToString();
//Create an array of all full paths from root node to the element
string[] fullPaths = new string[PropertiesList.Length + 2];
for (int i = 0; i < fullPaths.Length; i++)
{
if (i == 0) fullPaths[i] = pathParts[i];
else fullPaths[i] = fullPaths[i - 1] + "." + pathParts[i];
}
//Iterate through the fullPaths to determine, if node exists, if not -> create it
TreeNode previousNode = null;
for (int i = 0; i < fullPaths.Length; i++)
{
TreeNode foundNode = treeView1.Nodes.FindTreeNodeByFullPath(fullPaths[i]);
if (foundNode == null)
{
if (previousNode != null) previousNode = previousNode.Nodes.Add(pathParts[i]);
}
else
{
previousNode = foundNode;
continue;
}
}
}
}
I have to create an indented navigation menu using below data from a .csv file:
ID;MenuName;ParentID;isHidden;LinkURL1;Company;NULL;False;/company2;About Us;1;False;/company/aboutus3;Mission;1;False;/company/mission4;Team;2;False;/company/aboutus/team5;Client 2;10;False;/references/client26;Client 1;10;False;/references/client17;Client 4;10;True;/references/client48;Client 5;10;True;/references/client510;References;NULL;False;/references
Using this data I have to develop an application that will parse the file and present the content in a console as the example below:
. Company.... About Us....... Team.... Mission. References.... Client 1.... Client 2
Menu items should be indented (depending on the parent), hidden items (isHidden==true) shouldn't be presented and items should be ordered alphabetically. So far I tried:
using (StreamReader sr = new StreamReader(#"file.csv"))
{
// Read the stream to a string, and write the string to the console.
string [] lines = sr.ReadToEnd().Split(/*';', */'\n');
for (int i = 1; i < lines.Length; i++)
{
Console.WriteLine($"String no {i} is : {lines[i-1]}");
}
}
With this i'm getting the lines but I'm stuck after that. I'm new in coding so any help will be appreciated :)
heres some code that should help you get off.
Working sample:
https://dotnetfiddle.net/L37Gjr
It first parses the data to a seperate object. This then gets used to build a m-ary tree, or a hierachical structure of connected nodes. (a node has a reference to 0 or more children).
https://en.wikipedia.org/wiki/M-ary_tree
Then tree traversal (use google if you need to know more) is used to insert and print the output, There is still something wrong however. it now uses level order traversal to print, this however comes up with an error:
Found root:1 - Company
Found root:10 - References
-------------------
1 - Company
2 - About Us
3 - Mission
4 - Team
10 - References
6 - Client 1
5 - Client 2
As you can see, it prints 4 - Team on the wrong level. I'll leave it to you to fix it (because i ran out of time), and if not i hope i gave you plenty ideas to go off and research on your own.
// sample for https://stackoverflow.com/questions/61395486/read-csv-file-and-return-indented-menu-c-sharp by sommmen
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public class Node<T>
{
public T Data {get;set;}
public List<Node<T>> Children { get; set;}
public Node()
{
Children = new List<Node<T>>();
}
// Tree traversal in level order
public List<Node<T>> LevelOrder()
{
List<Node<T>> list = new List<Node<T>>();
Queue<Node<T>> queue = new Queue<Node<T>>();
queue.Enqueue(this);
while(queue.Count != 0)
{
Node<T> temp = queue.Dequeue();
foreach (Node<T> child in temp.Children)
queue.Enqueue(child);
list.Add(temp);
}
return list;
}
public List<Node<T>> PreOrder()
{
List<Node<T>> list = new List<Node<T>>();
list.Add(this);
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
return list;
}
public List<Node<T>> PostOrder()
{
List<Node<T>> list = new List<Node<T>>();
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
list.Add(this);
return list;
}
}
public class Entity
{
public int id {get;set;}
public string menuName {get;set;}
public int? parentID {get;set;}
public bool isHidden {get;set;}
public string linkURL {get;set;}
}
public static void Main()
{
var data = #"ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False;/company
2;About Us;1;False;/company/aboutus
3;Mission;1;False;/company/mission
4;Team;2;False;/company/aboutus/team
5;Client 2;10;False;/references/client2
6;Client 1;10;False;/references/client1
7;Client 4;10;True;/references/client4
8;Client 5;10;True;/references/client5
10;References;NULL;False;/references";
var lines = data.Split('\n');
var rootNodes = new List<Node<Entity>>();
var childItems = new List<Entity>();
// Parse the data to entities
// Items without a parent are used as rootnodes to build a tree
foreach(var row in lines.Skip(1))
{
var columns = row.Split(';');
var id = Convert.ToInt32(columns[0]);
var menuName = columns[1];
var parentID = ToNullableInt(columns[2]);
var isHidden = Convert.ToBoolean(columns[3]);
var linkURL = columns[4];
var entity = new Entity()
{
id = id,
menuName = menuName,
parentID = parentID,
isHidden = isHidden,
linkURL = linkURL
};
if(parentID == null)
{
Console.WriteLine("Found root:" + entity.id + " - " + entity.menuName);
rootNodes.Add(new Node<Entity>()
{
Data = entity
});
}
else
{
childItems.Add(entity);
}
}
// Add the childElements to their appropriate rootnode
foreach(var rootNode in rootNodes)
{
foreach(var childItem in childItems.OrderBy(a=>a.parentID).ThenBy(b=>b.menuName))
{
var newNode = new Node<Entity>()
{
Data = childItem
};
Insert(rootNode, newNode);
}
}
Console.WriteLine("-------------------");
foreach(var rootNode in rootNodes)
{
var indent = 0;
var previous = rootNode;
foreach(var node in rootNode.LevelOrder())
{
if(node.Data.isHidden) continue;
if(previous.Data.parentID != node.Data.parentID)
indent++;
for(var i = 0; i < indent; i++)
Console.Write("\t");
Console.WriteLine(node.Data.id + " - " + node.Data.menuName);
previous = node;
}
}
}
public static void Insert(Node<Entity> rootNode, Node<Entity> targetNode)
{
foreach(var current in rootNode.LevelOrder())
{
if(current.Data.id == targetNode.Data.parentID)
{
current.Children.Add(targetNode);
return;
}
}
}
public static int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i)) return i;
return null;
}
}
Im trying to do a Depth first traversal on graph node but I cant get my for each loop to loop correctly
Here is the code for doing the search
The foreach line is the problem
public void DepthFirstTraverse(T startID, ref List<T> visited)
{
LinkedList<T> adj;
Stack<T> toVisit = new Stack<T>();
GraphNode<T> current = new GraphNode<T>(startID);
toVisit.Push(startID);
while (toVisit.Count != 0)
{
current.ID = toVisit.Peek();
toVisit.Pop();
visited.Add(current.ID);
Console.WriteLine(current.GetAdjList());
foreach(T n in current.GetAdjList())
{
Console.WriteLine("Loop working");
if(toVisit.Contains(n)==false && visited.Contains(n)==true)
{
toVisit.Push(n);
}
}
}
}
Here is the graphnode code, it calls the getadjlist and should loop through for each element in that list, currently it doesnt loop at all
public class GraphNode
{
private T id; // data stored in the node (“id” of the node).
private LinkedList adjList; // adjacent list of the node
// constructor
public GraphNode(T id)
{
this.id = id;
adjList = new LinkedList<T>();
}
// set and get the data stored in the node
public T ID
{
set { id = value; }
get { return id; }
}
//add a directed edge from “this” node to the node "to”
public void AddEdge(GraphNode<T> to)
{
adjList.AddFirst(to.ID);
}
// returns the adjacent list of the node
public LinkedList<T> GetAdjList()
{
return adjList;
}
}
I have created a simple list class from scratch. This is for a class assignment that I have been working on for about a week - very new to lists. We can not use generics so trying to research my question below has not been fruitful. Although I did get to watch 7 tutorials on youtube by BetterCoder and I found some stuff in my book but nothing with an example of "merging".
I have three classes - my node, my list, and my program. In my list class, I am working on building a Merge() method which eventually will compare the data in the two lists and merge them into an ordered list.
Right now for some reason my Merge method - which is very basic to help me understand what is happening - is not working correctly. It has both lists passed to it, and is adding the data from listTwo to listOne BUT for some reason when it's printing to the console the second Node's Data shows twice :
EX: 1 -> 2 -> 2
instead of printing the head (1), the next (2) and then the next (3) which it should be.
EX: 1 -> 2 -> 3
In the program class I have proven with a write line that (listOne.firstNode.Next.Next.Data) = 3 . Which it should be.
Can someone help me figure out if the nodes in list one aren't pointing to each other correctly or whatever is going on?
My Merge Method must be passed both list objects (listOne and listTwo) and eventually I need to make those passed as references but I haven't figured that out quite yet and will focus on that later I suppose.
namespace LinkedList
{
//This is my Node Class
class Node
{
public object Data { get; set; }
public Node Next { get; set; }
public Node(object dataValue) : this(dataValue, null) { }
public Node(object dataValue, Node nextNode)
{
Data = dataValue;
Next = nextNode;
}
}
//This is my List Class
class List
{
public Node firstNode;
public int count;
public List()
{
firstNode = null;
}
public bool Empty
{
get { return this.count == 0; }
}
public int Count
{
get { return this.count; }
}
public object Add(int index, object o)
{
if (index < 0)
throw new ArgumentOutOfRangeException("Index: " + index);
if (index > count)
index = count;
Node current = this.firstNode;
if (this.Empty || index == 0)
{
this.firstNode = new Node(o, this.firstNode);
}
else
{
for (int i = 0; i < index - 1; i++)
current = current.Next;
current.Next = new Node(o, current.Next);
}
count++;
return o;
}
public object Add(object o)
{
return this.Add(count, o);
}
public object Merge(List a, List b)
{
a.Add(b.firstNode.Data);
return a;
}
public void Print()
{
while (this.count > 0)
{
Console.Write(firstNode.Data + "->");
if(firstNode.Next != null)
firstNode.Data = firstNode.Next.Data;
count--;
}
}
}
//And here is my Program
class Program
{
static void Main(string[] args)
{
List listOne = new List();
List listTwo = new List();
listOne.Add(1);
listOne.Add(2);
listTwo.Add(3);
listTwo.Print();
Console.WriteLine("");
listOne.Merge(listOne, listTwo);
Console.WriteLine("");
listOne.Print();
//This line below shows that the data "3" from listTwo is being added to listOne in the list Merge Method
//Console.WriteLine(listOne.firstNode.Next.Next.Data);
Console.ReadKey();
}
}
}
Actual problem in your print method
public void Print()
{
Node node = firstNode;
for (int i = 0; i < this.count; i++)
{
Console.Write(node.Data + "->");
if (node.Next != null)
node = node.Next;
}
}
Alex Sikilinda , you are right the merge method is incomplete.
public object Merge(List a, List b)
{
Node bNode = b.firstNode;
while (bNode != null)
{
a.Add(bNode.Data);
bNode = bNode.Next;
}
return a;
}
I would write it this way:
public void Merge(List b)
{
Node lastNode = GetLastNode();
if (lastNode != null)
{
lastNode.Next = b.firstNode;
}
else
{
this.firstNode = b.firstNode;
}
}
// this method is used to find the last node in current list
private Node GetLastNode()
{
if (this.firstNode == null)
{
return null;
}
Node current = this.firstNode;
while (current.Next != null)
{
current = current.Next;
}
return current;
}
First of all, I changed signature of Merge from public object Merge(List a, List b) to public void Merge(List b). Now we can use it like this:
listOne.Merge(listTwo);
This will link listOne's last element with the first element of listTwo and thus they are merged.
Now we need to change Print method since current version modifies the list, which shouldn't happen:
public void Print()
{
Node currentNode = this.firstNode;
while(currentNode != null)
{
Console.Write(currentNode.Data + ' ');
currentNode = currentNode.Next;
}
}
Instead of assigning the data back to first node I assign the
firstNode = firstNode.Next;
Please check the below Print Code
public void Print()
{
while (this.count > 0)
{
Console.Write(firstNode.Data + "->");
if (firstNode.Next != null)
firstNode = firstNode.Next;
count--;
}
}
Classes:
public class Tree
{
public Node RootNode { get; set; }
}
public class Node
{
public int Key { get; set; }
public object Value { get; set; }
public Node ParentNode { get; set; }
public List<Node> Nodes { get; set; }
}
Methods:
This method generates a tree.
private static int totalNodes = 0;
static Tree GenerateTree()
{
Tree t = new Tree();
t.RootNode = new Node();
t.RootNode.Key = 0;
t.RootNode.Nodes = new List<Node>();
Console.WriteLine(t.RootNode.Key);
List<Node> rootNodes = new List<Node>();
rootNodes.Add(t.RootNode);
while (totalNodes <= 100000)
{
List<Node> newRootNodes = new List<Node>();
foreach (var rootNode in rootNodes)
{
for (int j = 0; j < 3; j++)
{
totalNodes++;
Console.Write(string.Format(" {0}({1}) ", totalNodes, rootNode.Key));
Node childNode = new Node() {Key = totalNodes, Nodes = new List<Node>(), ParentNode = t.RootNode};
rootNode.Nodes.Add(childNode);
newRootNodes.Add(childNode);
}
Console.Write(" ");
}
Console.WriteLine();
rootNodes = newRootNodes;
}
return t;
}
This method is supposed to print a tree, but node is null in some case:
static void PrintTreeParallel(Node rootNode)
{
List<Node> rootNodes = new List<Node>();
List<Node> newRootNodes = new List<Node>();
rootNodes.Add(rootNode);
Console.WriteLine(rootNode.Key);
while (rootNodes.Count > 0)
{
newRootNodes = new List<Node>();
Parallel.ForEach(rootNodes, node =>
{
if (node != null)
{
Console.Write(string.Format(" {0} ", node.Key));
if (node.Nodes != null)
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
}
else
{
//HOW CAN WE GET HERE?????
Debugger.Break();
Console.WriteLine(rootNodes.Count);
}
});
Console.WriteLine();
rootNodes = newRootNodes;
}
}
Execute:
static void Main(string[] args)
{
var t = GenerateTree();
Console.WriteLine("Tree generated");
PrintTreeParallel(t.RootNode);
Console.WriteLine("Tree printed paral");
Console.ReadLine();
}
Question:
What's wrong here?
Why node is null in some case?
And it happens only when there are a lot of generated nodes. For ex if there would be only 10 nodes everything is OK.
The problem is that you have this code:
Parallel.ForEach(node.Nodes, newRoot => { newRootNodes.Add(newRoot); });
Which allows multiple threads to add items to the newRootNodes list concurrently. As a commenter pointed out, List<T> is not thread-safe. What's probably happening is that one thread's Add is being interrupted by another thread's call to Add, which causes an internal index in the list to be incremented. That leaves a null value in one of the list's items.
Then, later in the loop you have:
rootNodes = newRootNodes;
Which puts the corrupted list as the list that's going to be iterated by the while.
You have a data race here:
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
Adding to a list with multiple threads is not thread-safe and will cause undetermined behavior.
First try to run this part with a simple foreach and see if the problem goes away. Running two nested Parallel.ForEach statements is definitely a bizarre choice.
List<T> is indeed not thread safe, so rootNode.Nodes.Add(childNode); is dropping data in unpredictable ways.
Instead of using List<> use ConcurrentBag<> and it will all work. Note that ConcurrentBag<T> is unordered, but that is fine because you have no way of predicting the order from the threads anyway.