I have two trees like these:
I want to insert the second tree into the first tree in a node that has the same name of its root and attach the children of this node to the left most child of the second tree.
I tried:
PTree attachPoint = chain.Find(x => x.Val == RTree.Val);
if (attachPoint != null)
{
foreach (var c in attachPoint.Childs)
{
RTree.Left.Childs.Add(c);
}
attachPoint = RTree;
}
else
{
RTree.Left.Childs.Add(root);
root = RTree;
}
Here, RTree is the second tree and root points to the root of first tree and chain holds the branch from root "A" to "D". But it seems the desired tree is not built. Did i do it correctly?
It would have been easier to help if you have included the essential parts of your PTree class. Here is what I could suggest you based on the posted code:
PTree attachPoint = chain.Find(x => x.Val == RTree.Val);
if (attachPoint != null)
{
foreach (var c in attachPoint.Childs)
{
RTree.Left.Childs.Add(c);
}
// Here you either have to find the attachPoint parent and
// replace attachPoint with RTree in parent.Childs,
// or make attachPoint.Childs to be RTree.Childs as below
attachPoint.Childs.Clear();
foreach (var c in RTree.Childs)
{
attachPoint.Childs.Add(c);
}
}
else
{
RTree.Left.Childs.Add(root);
root = RTree;
}
attachPoint (and root?) are just local variables so attachPoint = RTree; will not affect the structure of the tree. You need to search the left tree to find the parent node of the insert point and then modify the parent node so that parent.Right = attachPoint;
Related
I have been running into an interesting error while building a "search optimized" BST (each node has an access count identifier). I can successfully move nodes up the tree however, some nodes do not move up the tree as many times as expected.
For example:
When I call my find method on value 1 the tree successfully adjusts and 1 becomes the root. Subsequently when I call the method on 4 the node moves up to be the right child of the root 1.
However, when I call on 5 or 6 the node only rotates upwards once, then the optimization ends.
Here is my code that optimizes the tree, it is called on the root of the tree after a node is found using the find method:
private void RecOptimize(TreeNode<T> currentNode, TreeNode<T> prevNode)
{
if (currentNode == null) return;
RecOptimize(currentNode.left, currentNode);
RecOptimize(currentNode.right, currentNode);
var left = currentNode.left;
var right = currentNode.right;
var oldNode = currentNode;
if(right != null)
{
if (right.iAccessCount > currentNode.iAccessCount)
{
currentNode = MakeRightRoot(currentNode);
}
}
if(left != null)
{
if (left.iAccessCount > currentNode.iAccessCount)
{
currentNode = MakeLeftRoot(currentNode);
}
}
if(prevNode != null)
{
if(prevNode.left != null && prevNode.left.Equals(oldNode))
{
prevNode.left = currentNode;
}
if(prevNode.right != null && prevNode.right.Equals(oldNode))
{
prevNode.right = currentNode;
}
}
else
{
this.rootNode = currentNode;
}
}
Edit Thought I would clear something up.
MakeLeftRoot(node) and MakeRightRoot(node) simply perform a rotation about the node, MakeLeftRoot rotates the nodes left node and returns it. While MakeRightRoot does the same but to the right side.
Edit 2 Heres the code for the rotation methods
protected TreeNode<T> MakeRightRoot(TreeNode<T> oldRoot)
{
var newRoot = oldRoot.right;
oldRoot.right = newRoot.left;
newRoot.left = oldRoot;
return newRoot;
}
protected TreeNode<T> MakeLeftRoot(TreeNode<T> oldRoot)
{
var newRoot = oldRoot.left;
oldRoot.left = newRoot.right;
newRoot.right = oldRoot;
return newRoot;
}
I will assume all your other code is correct, including the functions that perform rotations. Then there is still this issue in RecOptimize:
If currentNode changes by the call of MakeRightRoot(currentNode), then the left and right variables no longer represent the left and right nodes of currentNode and so the next if block will not be doing what is intended, as at that moment left is a grandchild of currentNode.
I understand why you also pass prevNode to this function, and how the if(prevNode != null) block is dealing with setting the link with prevNode and the potentially changed reference for currentNode, but it will be much easier if you use the same coding pattern as for your rotation functions: let the function return the potentially changed reference for currentNode, and let the caller be responsible for attaching that node back to its parent node.
Also, you should determine whether it is possible that a higher access count occurs in both subtrees of a node or not. If it cannot occur (because after each access you call this function), then you may want to use an if ...else if construct for the two first blocks. If however it could occur, then think how you want the tree to get rotated. I believe it is better to then to deal with each subtree separately: first finish the work for one subtree completely (recursive call + potential rotation), and then only deal with the other subtree (recursive call + potential rotation).
Here is the suggested code:
private TreeNode<T> RecOptimize(TreeNode<T> node) {
if (node == null) return null;
if (node.right != null) {
node = RecOptimize(node.right);
if (node.right.iAccessCount > node.iAccessCount) {
node = MakeRightRoot(node);
}
}
if (node.left != null) {
node = RecOptimize(node.left);
if (node.left.iAccessCount > node.iAccessCount) {
node = MakeLeftRoot(node);
}
}
return node;
}
Main call:
this.rootNode = RecOptimize(this.rootNode);
One more comment: RecOptimize is going to visit every node in the tree. This is not optimal. If you access a node through binary search, then you should only have to check for rotations along the path to that found node, as a "bubble up" phase.
I have this list of 2000+ categories that need to be organized in a tree before being sent to the controller and the View so the javascript plugin can render them correctly.
I am already doing this but the performance is terrible. It is taking like 30 seconds to assemble the tree.
I can't see what is dropping performance here. Can you guys help me to improve this code?
var allCategories = dal.Listar();
List<Model.Entity.CategoriaCursoEADVO> nestedCategories = new List<Model.Entity.CategoriaCursoEADVO>();
foreach (Model.Entity.CategoriaCursoEAD item in allCategories)
{
if (item.IdCategoriaPai == null)
{
CategoriaCursoEADVO child = new CategoriaCursoEADVO();
child.id = item.Id;
child.text = item.Descricao;
nestedCategories.Add(child);
FillChild(allCategories, child, item.Id);
}
}
And here is the FillChild method:
public int FillChild(IEnumerable<CategoriaCursoEAD> categorias, CategoriaCursoEADVO parent, int IID)
{
var childCategories = categorias.Where(w => w.IdCategoriaPai.Equals(IID));
parent.children = new List<CategoriaCursoEADVO>();
if (childCategories.Count() > 0)
{
foreach (CategoriaCursoEAD cat in childCategories)
{
CategoriaCursoEADVO child = new CategoriaCursoEADVO();
child.id = cat.Id;
child.text = cat.Descricao;
parent.children.Add(child);
FillChild(categorias, child, cat.Id);
}
return 0;
}
else
{
return 0;
}
}
I think the problem is with the new instances and tried using Parallel loops with no satisfatory level of improvement.
This is a pretty good time to use a HashTable (Dictionary). Something like the below code should help.
// Convert the flat list into a hash table with the ID
// of the element as the key
var dict = allCategories.ToDictionary (i => i.Id);
// Group categories by the parent id
var parentGrouping = allCategories.Where(c => c.IdCategoriaPai != null).GroupBy(c => c.ParentId);
// Since we group the items by parent id, we can find
// the parent by id in the dictionary and add the children
// that have that particular id.
foreach(var groupItem in parentGrouping)
if(groupItem.Key != null)
dict[(int)groupItem.Key].children.AddRange(groupItem);
// Get the root elements.
var hierarchicalCategories = allCategories.Where(item => item.IdCategoriaPai == null);
// Do what you need to do here.
This code will create a tree of categories. hierarchicalCategories will contain direct references to the root elements (categories that do not have a parent), assuming that your data is structured that way.
I'm trying to query the following XML to build some objects which copy the folder hierarchy outlined in the XML.
<ShareList>
<Title>Documantis</Title>
<Url>/sites/dev/Documantis/Forms/AllItems.aspx</Url>
<Guid>fed8f456-efa9-4fe5-8b97-46734a3040b6</Guid>
<HasUniqueScopes>False</HasUniqueScopes>
<RootFolder>/sites/dev</RootFolder>
<Children>
<ShareListItem>
<Title>First</Title>
<Url>Documantis/First</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
<Children>
<ShareListItem>
<Title>Second</Title>
<Url>Documantis/First/Second</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<ParentGuid>22b2a7e9-a42e-497f-aad3-8caa85f6ac6d</ParentGuid>
</ShareListItem>
</Children>
</ShareListItem>
<ShareListItem>
<Title>Folda</Title>
<Url>Documantis/Folda</Url>
<HasUniqueRole>False</HasUniqueRole>
<IsSubFolder>False</IsSubFolder>
<PermissionMask>FullMask</PermissionMask>
</ShareListItem>
</Children>
</ShareList>
I'm having trouble finding a way to return one level of the <ShareListItem> elements at a time, with my current code it returns all the ShareListItems in one List which doesn't represent the hierarchy accurately.
XmlDocument doc = new XmlDocument();
doc.LoadXml(sharepointXml);
XElement root;
using (XmlReader xr = new XmlNodeReader(doc)) { root = XElement.Load(xr); }
var result = from child in root.DescendantsAndSelf("ShareList") //.Elements("ShareList") // Descendants("ShareList")
select child;
foreach (XElement xml in result)
{
// Build ListItem from results
ShareList list = new ShareList()
{
Title = xml.Element("Title").Value,
Url = xml.Element("Url").Value,
Guid = xml.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(xml.Element("HasUniqueScopes").Value),
RootFolder = xml.Element("RootFolder").Value,
};
if (xml.Element("Children") != null)
{
var subResult = from child in xml.Element("Children").Descendants("ShareListItem")
select child;
foreach (XElement subXml in subResult)
{
// results here are flat and don't show depth of nodes
}
//list.Children =
}
I could recursively infer the hierarchy's structure from the URL Element, however I already have it represented in XML so I would rather learn how to return this through a query.
Edit:
Here's what I ended up using
public List<ShareList> HandleLists(XElement levelRoot)
{
List<ShareList> lists = new List<ShareList>();
var results = from list in levelRoot.DescendantsAndSelf("ShareList")
select list;
foreach (var list in results)
{
var children = list.Element("Children");
if (children == null)
return null;
ShareList shareList = new ShareList()
{
Title = list.Element("Title").Value,
Url = list.Element("Url").Value,
Guid = list.Element("Guid").Value,
HasUniqueScopes = Convert.ToBoolean(list.Element("HasUniqueScopes").Value),
RootFolder = list.Element("RootFolder").Value,
// Recursively find ListItem folders
Children = HandleSubfolders(list)
};
lists.Add(shareList);
}
return lists;
}
public List<ShareListItem> HandleSubfolders(XElement levelRoot)
{
List<ShareListItem> subfolders = new List<ShareListItem>();
// All nodes deeper than current
var children = levelRoot.Element("Children");
if (children == null)
return null;
// Subfolders
var items = children.Elements("ShareListItem");
foreach (var item in items)
{
ShareListItem listItem = new ShareListItem()
{
Title = item.Element("Title").Value,
Url = item.Element("Url").Value,
HasUniqueRole = Convert.ToBoolean(item.Element("HasUniqueRole").Value),
IsSubfolder = Convert.ToBoolean(item.Element("IsSubFolder").Value),
PermissionMask = item.Element("PermissionMask").Value,
PermissionMaskName = item.Element("PermissionMaskName").Value,
// Recursively find ListItem subfolders
Children = HandleSubfolders(item)
};
// Add subfolder to Children collection
subfolders.Add(listItem);
}
return subfolders;
}
You would want to use recursion here.
Create a method that handles one level of the hierarchy and calls itself with the next level.
public void HandleLevel(XElement levelRoot)
{
PerformAction(levelRoot);
var children = levelRoot.Element("Children");
if(children == null)
return;
var items = children.Elements("ShareListItem");
foreach(var item in item)
{
// Handle child's children:
HandleLevel(item);
}
}
PerformAction is the code that actually does, whatever you want to do for each document.
The way the code is currently structured, this action is also executed for the root document /sites/dev/Documantis/Forms/AllItems.aspx.
If you don't want this simply move PerformAction into the foreach loop and pass item instead of levelRoot.
BTW: Your initialization of the root element is very strange.
You can simply use this:
var root = XDocument.Parse(sharepointXml).Root;
The initial call to HandleLevel would simply look like this:
HandleLevel(root);
A good way of producing the results your after is by using XPath (here's a good primer if you need it).
Once you've got your XML into an XmlDocument you can return different bits of it by using an XPathNavigator, like this:
var xmlNavigator = xmlDocument.CreateNavigator();
var outerQuery = xmlNavigator.Select("ShareList/Children/ShareListItem");
while (outerQuery.MoveNext()) {
Console.WriteLine(outerQuery.Current.SelectSingleNode("Title").Value);
var innerQuery = outerQuery.Current.Select("Children/ShareListItem");
while (innerQuery.MoveNext()) {
Console.WriteLine(" - " + innerQuery.Current.SelectSingleNode("Title").Value);
}
}
In the code above, we query the XML for all ShareListItem nodes within Children nodes of the root ShareList node, and store the resultant XPathNodeIterator in the variable outerQuery. We then iterate over all the nodes found, and run an operation as well as another XPath query on each to retrieve child nodes to process. The code above produces the following output:
First
- Second
Folda
Which I think it what you're after. Obviously, you can use recursion if necessary if your XML can be nested more deeply than this.
One way of doing this is by creating Classes to represent the hierarchy like so:
public class ShareList {
...
public List<ShareList> Children { get; set; }
}
In your code, refactor the traversing part into a method that accepts a Sharelist node and traverse it, calling itself for each Children node:
private Sharelist RecurseHierarchy(XElement sharelistNode, ShareList parent)
{
// your traversing code goes here
// get your data and create a new Sharelist object
// if it has a children node, traverse it and call this same method on the child
// Sharelist nodes
parent.Title = sharelistNode.Element("Title").Value;
var children = sharelistNode.Element("Children");
if (children != null)
{
var items = children.Elements("ShareListItem");
foreach(var listItem in items)
{
ShareList childShareList = new ShareList();
parent.Children.Add(childShareList);
RecurseHierarchy(listItem, childShareList);
}
}
// Just in case we want to chain some method
return parent;
}
To call it initially, you will have to pass in the root node and a new ShareList object.
I have a program that contains a TreeView. All of my nodes except for the root and two nodes under the root are loaded from a database.
When the user adds data to the database it must be automatically added to the TreeView. I can successfully do this by clearing all the nodes, add the default nodes and add all the data including the new one to my TreeView, but all nodes of the new TreeView are collapsed.
Our client wants to retain all expanded nodes but still add the new data he just added. Is there any way to know all expanded nodes and expand it again once collapsed or refreshed? Thank you for any response.
Hi As i understand you want to save you tree view map after you refresh tree view (by adding new data or even remove some) you want to expand all expanded nodes the other are collapsed by default.
Solution is:
1) save expanded tree view nodes before refresh
2) refresh tree view data (notice that if you are removing a node , remove it from saved list as well)
3) set tree view map which saved before
saving tree view map (only expanded nodes)->
This Code Look through a tree view node collection and saves expanded nodes Names in a string list
List<string> collectExpandedNodes(TreeNodeCollection Nodes)
{
List<string> _lst = new List<string>();
foreach (TreeNode checknode in Nodes)
{
if (checknode.IsExpanded)
_lst.Add(checknode.Name);
if (checknode.Nodes.Count > 0)
_lst.AddRange(collectExpandedNodes(checknode.Nodes));
}
return _lst;
}
Now you have collected expanded nodes name in a list and you want to gain back your tree view appearance you need 2 functions a function which retrieves node by a name and a function which expand selected node and it's parents the following codes does :
This function retrieves a pointer to selected node Name if node is exist in Tree Node Collection
TreeNode FindNodeByName(TreeNodeCollection NodesCollection , string Name)
{
TreeNode returnNode = null; // Default value to return
foreach (TreeNode checkNode in NodesCollection)
{
if (checkNode.Name == Name) //checks if this node name is correct
returnNode = checkNode;
else if (checkNode.Nodes.Count > 0 ) //node has child
{
returnNode = FindNodeByName(checkNode.Nodes , Name);
}
if (returnNode != null) //check if founded do not continue and break
{
return returnNode;
}
}
//not found
return returnNode;
}
and This Function Expand node and it's parents
void expandNodePath(TreeNode node)
{
if (node == null)
return;
if (node.Level != 0) //check if it is not root
{
node.Expand();
expandNodePath(node.Parent);
}
else
{
node.Expand(); // this is root
}
}
and the following show you the usage of the functions
private void button4_Click(object sender, EventArgs e)
{
//saving expanded nodes
List<string> ExpandedNodes = new List<string>();
ExpandedNodes = collectExpandedNodes(treeView1.Nodes);
//resetting tree view nodes status to colapsed
treeView1.CollapseAll();
//Restore it back
if (ExpandedNodes.Count > 0)
{
TreeNode IamExpandedNode;
for (int i = 0; i < ExpandedNodes.Count;i++ )
{
IamExpandedNode = FindNodeByName(treeView1.Nodes, ExpandedNodes[i]);
expandNodePath(IamExpandedNode);
}
}
}
For expanding all the nodes use below code
treeView1.ExpandAll();
For expanding selected node use below code
treeView1.SelectedNode.ExpandAll();
For expanding particular node use below code
treeView1.Nodes[Index].Expand();
Assume the Nodename is unique.
Using an Database the nodename can be the unique rowid of an table
The state of the tree (List) can simply saved with an Formatter (e.g. BinaryFormatter)
if User want to save status
Only save the expandedstate
private List<string> SaveTreeState(TreeNodeCollection nodes)
{
List<string> nodeStates = new List<string>();
foreach (TreeNode node in nodes)
{
if (node.IsExpanded) nodeStates.Add(node.Name);
nodeStates.AddRange(SaveTreeState(node.Nodes));
}
return (nodeStates);
}
Let the treeview do the work finding the nodes for restore
private void RestoreTreeState(TreeView tree, List<string> treeState)
{
foreach (string NodeName in treeState)
{
TreeNode[] NodeList = treeView1.Nodes.Find(NodeName, true);
if (NodeList.Length > 0) // only if node after reload is avail
NodeList[0].Expand();
}
}
Using:
List<string> StateList = SaveTreeState(treeView1.Nodes);
... // reload
RestoreTreeState(treeView1, StateList);
To simply expand nodes you can try this following code
private void button1_Click(object sender, EventArgs e)
{
treeView1.Nodes.Add(new TreeNode("New Node",
new TreeNode[2] { new TreeNode("Node1"), new TreeNode("Node2") }));
treeView1.Nodes[1].Expand();
}
Hope that helps
It's very simple. Below, you can see my recursive version:
//List of storage ids of expanded nodes
List<int> expandedNodeIds = new List<int>();
//call recursive fun for our tree
CollectExpandedNodes(tree.Nodes);
//recursive fun for collect expanded node ids
private void CollectExpandedNodes(TreeListNodes nodes)
{
foreach (TreeListNode node in nodes)
{
if (node.Expanded) expandedNodeIds.Add(node.Id);
if (node.HasChildren) CollectExpandedNodes(node.Nodes);
}
}
I know this post is old but if the tree is deep it might not be a good idea to use a recursive traversal of the tree. Since I have not seen any anwser using a non-recursive way here is a solution for doing getting expanded nodes without impacting performance.
public static IEnumerable<TreeNodeAdv> CollectExpandedNodes(this TreeNodeAdv root)
{
Stack<TreeNodeAdv> s = new Stack<TreeNodeAdv>();
s.Push(root);
while (s.Count > 0)
{
TreeNodeAdv n = s.Pop();
if (n.IsExpanded)
yield return n;
foreach (var child in n.Children.ToArray().Reverse())
{
s.Push(child);
}
}
}
To use this method, you can do the following:
foreach (TreeNodeAdv expandedNode in yourTreeView.Root.CollectExpandedNodes())
{
//Do processing on the expanded node or add in list.
}
This extension method uses Deep-First traversal in post-order along with the yield keyword to generate an IEnumerable collection.
I'm trying to populate a treeview from an XmlDocument.
The Root of the tree is set as 'Scripts' and from the root the next level should be 'Departments' which is within the XML script. I can get data from the XML document no problem. My question is when looping through the XmlDocument and adding nodes to the root node, I want to ensure that if a department is already within the treeview then it is not added again. I should also add that each Department also has a list of scripts that need to be child nodes of the department.
My code so far is:
XmlDocument xDoc = new XmlDocument();
xDoc.LoadXml(scriptInformation);
TreeNode t1;
TreeNode rootNode = new TreeNode("Script View");
treeView1.Nodes.Add(rootNode);
foreach (XmlNode node in xDoc.SelectNodes("//row"))
{
t1 = new TreeNode(node["DEPARTMENT"].InnerXml);
//How to check if node already exists in treeview?
}
Thanks.
if(treeView1.Nodes.ContainsKey("DEPARTMENT")){
//...
}
EDIT: Recursive method:
bool exists = false;
foreach (TreeNode node in treeView1.Nodes) {
if (NodeExists(node, "DEPARTMENT"))
exists = true;
}
private bool NodeExists(TreeNode node, string key) {
foreach (TreeNode subNode in node.Nodes) {
if (subNode.Text == key) {
return true;
}
if (node.Nodes.Count > 0) {
NodeExists(node, key);
}
}
return false;
}
Depending upon the size of your XML file, you could consider using an associated List for fast lookup. As you add each node to the TreeView also add it to the List.
If your XML document has a set structure where 'Departments' will always be indexed at 1;
ie:
index:[0] Scripts
index:[1] Department
index:[2] Script
index:[1] Department2
index:[2] Script
Then you could encapsulate the following code into a method where 'name' is a string parameter and the return type is boolean.
foreach (TreeNode node in uxTreeView.Nodes[0].Nodes) {
if (node.Name.ToLower() == name.ToLower()) {
return true;
}
}
return false;
The idea is you would call that function each time you encounter a 'Department' node in your Xml, before creating the TreeNode.
Full example:
private bool DepartmentNodeExists(string name) {
foreach (TreeNode node in uxTreeView.Nodes[0].Nodes) {
if (node.Name.ToLower() == name.ToLower()) {
return true;
}
}
return false;
}
Lastly, the easy way:
private bool DepartmentNodeExists(string name) {
if (uxTreeView.Nodes[0].ContainsKey(name)) {
return true;
}
else {
return false;
}
}
These are all just refactored and encapsulated into their own named methods, you of course could just call:
if (uxTreeView.Nodes[0].ContainsKey(name)) {
// do not create TreeNode
}
...during your parsing of your XML. PS. These examples all assume that you have the first root node in the TreeView already created and added to the TreeView.
http://www.vbdotnetforums.com/listviews-treeviews/13278-treeview-search.html#post39625
http://forums.asp.net/t/1645725.aspx/1?Check+if+child+Node+exists+on+treeview
You can do something like this:
TreeNode parentNode = t1.Parent;
if (parentNode != null}
{
if(parentNode.Nodes.Cast<TreeNode>().ToList().Find(t => t.Text.Equals(node["DEPARTMENT"].InnerXml) == null)
{
//Add node
}
}
else
{
bool isFound = true;
if (treeView1.Nodes.Cast<TreeNode>().ToList().Find(t => t.Text.Equals(node["DEPARTMENT"].InnerXml) == null)
{
isFound = false;
}
if(!isFound)
{
//Add node
}
}
Not sure about the document structure...
Couldn't you use Linq to Xml, load the document and get the distinct row ( row = department?) and consider only those elements to create a TreeNode? It is more efficient than trying to find if a node with such a text has already been added.
ex:
var rows = ( from row in XDocument.Load(document).Root.Elements("row")
select row
).Distinct(new SampleElementComparerOnNameAttribute());
Here the EqualityComparer is made on the "name" attribute value assuming the doc structure to be
<rows><row name='dep1'><script>script1</script><script>script2</script></row><row name='dep1'><script>script3</script><script>script4</script></row></rows>
I use,
string department = node["DEPARTMENT"].InnerXml;
TreeNode node = parentNode.Nodes[department] ?? parentNode.Nodes.Add(department, department);
That line guarantees that a lookup of the value department will be done first, if not found it creates it. You have to do the double entry in Add() so it will have a key value you can do the lookup with the .Nodes[department].
It depends on the structure of your input. Since you don't show how exactly you add your subnodes I can only point you towards either the Contains or the ContainsKey method of the Nodes property, either of the treeView1 itself, or of any subnodes you add. You should use an overload of the Add method to specify a key name to simplify lookup.