I'm creating a special tree algorithm and I need a bit of help with the code that I currently have, but before you take a look on it please let me explain what it really is meant to do.
I have a tree structure and I'm interacting with a node (any of the nodes in the tree(these nodes are Umbraco CMS classes)) so upon interaction I render the tree up to the top (to the root) and obtain these values in a global collection (List<Node> in this particular case). So far, it's ok, but then upon other interaction with another node I must check the list if it already contains the parents of the clicked node if it does contain every parent and it doesn't contain this node then the interaction is on the lowest level (I hope you are still with me?).
Unfortunately calling the Contains() function in Umbraco CMS doesn't check if the list already contains the values which makes the list add the same values all over again even through I added the Contains() function for the check.
Can anyone give me hand here if he has already met such a problem? I exchanged the Contains() function for the Except and Union functions, and they yield the same result - they do contain duplicates.
var currentValue = (string)CurrentPage.technologies;
List<Node> globalNodeList = new List<Node>();
string[] result = currentValue.Split(',');
foreach (var item in result)
{
var node = new Node(int.Parse(item));
if (globalNodeList.Count > 0)
{
List<Node> nodeParents = new List<Node>();
if (node.Parent != null)
{
while (node != null)
{
if (!nodeParents.Contains(node))
{
nodeParents.Add(node);
}
node = (Node)node.Parent;
}
}
else { globalNodeList.Add(node); }
if (nodeParents.Count > 0)
{
var differences = globalNodeList.Except<Node>(globalNodeList);
globalNodeList = globalNodeList.Union<Node>(differences).ToList<Node>();
}
}
else
{
if (node.Parent != null)
{
while (node != null)
{
globalNodeList.Add(node);
node = (Node)node.Parent;
}
}
else
{
globalNodeList.Add(node);
}
}
}
}
If I understand your question, you only want to see if a particular node is an ancestor of an other node. If so, just (string) check the Path property of the node. The path property is a comma separated string. No need to build the list yourself.
Just myNode.Path.Contains(",1001") will work.
Small remarks.
If you are using Umbraco 6, use the IPublishedContent instead of Node.
If you would build a list like you do, I would rather take you can provide the Umbraco helper with multiple Id's and let umbraco build the list (from cache).
For the second remark, you are able to do this:
var myList = Umbraco.Content(1001,1002,1003);
or with a array/list
var myList = Umbraco.Content(someNode.Path.Split(','));
and because you are crawling up to the root, you might need to add a .Reverse()
More information about the UmbracoHelper can be found in the documentation: http://our.umbraco.org/documentation/Reference/Querying/UmbracoHelper/
If you are using Umbraco 4 you can use #Library.NodesById(...)
Related
I have implemented binary search tree in C# using standard approach.
The complete code is here
I'm not able to figure out how can I do this using custom approach. How can this be done using C# manually?
I don't see why you wouldn't use some of standard (de)serialization techniques (BinaryFormatter, XmlSerializer, data contracts, protocol buffers)?
But if you really want to use the approach given in the link, the point of the article can be summarized into:
A simple solution is to store both Inorder and Preorder traversals. This solution requires requires space twice the size of Binary Tree.
When represented this way, you have to use a "dummy" value for empty nodes. And since the author of the linked article used the tree to store integers, (s)he chose to use the "special" -1 value for empty nodes.
But if you are not storing the tree this way internally (I presume you are using a linked list), then there is not point in adding these dummy values. If you are storing plain C# objects, than a null value clearly describes an empty node.
If your intention is to port the C++ to C# completely, then the serialization method would look like this:
// This function stores a tree in a file pointed by fp
void Serialize(Node root, StreamWriter writer)
{
// If current node is NULL, store marker
if (root == null)
{
writer.Write("{0} ", MARKER);
return;
}
// Else, store current node and recur for its children
writer.Write("{0} ", root.key);
Serialize(root.leftc, writer);
Serialize(root.rightc, writer);
}
But this is very specific to your tree, as it only works for simple keys (like integers in your case), and it's not very space/speed efficient.
When writing a binary data to a file (or stream), you need to put some "marker" (indicator) for null (in contrast with XML where you have a natural "missing" element/attribute). It could be anything, the most natural would be a bool representing something similar to Nullable<T>.HasValue, but for Node reference, like this
class ObjectPersistence
{
public void StoreBSTToFile(BST bst, string TreeStoreFile)
{
using (var writer = new BinaryWriter(File.Create(TreeStoreFile)))
WriteNode(writer, bst.root);
}
public BST ReadBSTFromFile(string TreeStoreFile)
{
using (var reader = new BinaryReader(File.OpenRead(TreeStoreFile)))
return new BST { root = ReadNode(reader) };
}
private static void WriteNode(BinaryWriter output, Node node)
{
if (node == null)
output.Write(false);
else
{
output.Write(true);
output.Write(node.key);
WriteNode(output, node.leftc);
WriteNode(output, node.rightc);
}
}
private static Node ReadNode(BinaryReader input)
{
if (!input.ReadBoolean()) return null;
var node = new Node();
node.key = input.ReadInt32();
node.leftc = ReadNode(input);
node.rightc = ReadNode(input);
return node;
}
}
I am creating a program that cursively finds all the files and directories in the specified path. So one node may have other nodes if that node happens to be a directory.
Here is my Node class:
class Node
{
public List<Node> Children = new List<Node>(); // if node is directory then children will be the files and directories in this direcotry
public FileSystemInfo Value { get; set; } // can eather be a FileInfo or DirectoryInfo
public bool IsDirectory
{
get{ return Value is DirectoryInfo;}
}
public long Size // HERE IS WHERE I AM HAVING PROBLEMS! I NEED TO RETRIEVE THE
{ // SIZE OF DIRECTORIES AS WELL AS FOR FILES.
get
{
long sum = 0;
if (Value is FileInfo)
sum += ((FileInfo)Value).Length;
else
sum += Children.Sum(x => x.Size);
return sum;
}
}
// this is the method I use to filter results in the tree
public Node Search(Func<Node, bool> predicate)
{
// if node is a leaf
if(this.Children.Count==0)
{
if (predicate(this))
return this;
else
return null;
}
else // Otherwise if node is not a leaf
{
var results = Children.Select(i => i.Search(predicate)).Where(i => i != null).ToList();
if (results.Any()) // THIS IS HOW REMOVE AND RECUNSTRUCT THE TREE WITH A FILTER
{
var result = (Node)MemberwiseClone();
result.Children = results;
return result;
}
return null;
}
}
}
and thanks to that node class I am able to display the tree as:
In one column I display the name of the directory or file and on the right the size. The size is formated as currency just because the commas help visualize it more clearly.
So now my problem is The reason why I have this program was to perform some advance searches. So I may only want to search for files that have the ".txt" extension for example. If I perform that filter on my tree I will get:
(note that I compile the text to a function that takes a Node and returns a bool and I pass that method to the Search method on my Node class in order to filter results. More information on how to dynamically compile code can be found at: http://www.codeproject.com/Articles/10324/Compiling-code-during-runtime) Anyways that has nothing to do with this question. The important part was that I removed all the nodes that did not matched that criteria and because I removed those nodes now the sizes of the directories changed!!!
So my question is how will I be able to filter results maintaining the real size of the directory. I guess I will have to remove the property Size and replace it with a field. The problem with that is that every time I add to the tree I will have to update the size of all the parent directories and that gets complex. Before starting coding it that way I will appreciate your opinion on how I should start implementing the class.
Since you're using recursion and your weight is a node-level property you can't expect that will continue to sum even after you remove the node. You either promote it to a upper level (collection) or use an external counter within the recursion (which counts but not depending on filter, you'll need to carry this through the recuersion).
Anyway, why are you implementing a core .NET functionality again? any reason beyond filtering or recursive search? both are pretty well implemented in the BCL.
I want to store an organisation chart in a collection. I think a tree data structure will be best suited to my needs, as I need to add multiple nodes to one node.
LinkedList only provides adding one node to another node, if I understand it correctly.
I have looked at C5 treeset collection, but it doesn't seem to have Add() method to add more than 2 nodes to one node.
I have also looked at Treeview class from Windows Forms library, but I do not want to add Windows forms dll to my project, since I am building a service layer application. (or is it fine?)
I do not want to write my own tree collection class, if there is already one provided by 3rd party?
Any suggestion please?
Thanks
Something like this can be a starting point. By using generics this one can hold a tree of anything
class TreeNode<T>
{
List<TreeNode<T>> Children = new List<TreeNode<T>>();
T Item {get;set;}
public TreeNode (T item)
{
Item = item;
}
public TreeNode<T> AddChild(T item)
{
TreeNode<T> nodeItem = new TreeNode<T>(item);
Children.Add(nodeItem);
return nodeItem;
}
}
A sample which holds a tree of strings
string root = "root";
TreeNode<string> myTreeRoot = new TreeNode<string>(root);
var first = myTreeRoot.AddChild("first child");
var second = myTreeRoot.AddChild("second child");
var grandChild = first.AddChild("first child's child");
I have an XElement (myParent) containing multiple levels of children that I wish to extract data from. The elements of interest are at known locations in the parent.
I understand that I am able to get a child element by:
myParent.Element(childName);
or
myParent.Element(level1).Element(childName);
I am having trouble figuring out how to do this if I want to loop through an array offor a list of elements that are at different levels, and looping through the list. For instance, I am interested in getting the following set of elements:
myParent.Element("FieldOutputs").Element("Capacity");
myParent.Element("EngOutputs").Element("Performance")
myParent.Element("EngOutputs").Element("Unit").Element("Efficiency")
How can I define these locations in an array so that I can simply loop through the array?
i.e.
string[] myStringArray = {"FieldOutputs.Capacity", "EngOutputs.Performance", "EngOutputs.Unit.Efficiency"};
for (int i=0; i< myArray.Count(); i++)
{
XElement myElement = myParent.Element(myStringArray);
}
I understand that the method above does not work, but just wanted to show effectively what I am trying to achieve.
Any feedback is appreciated.
Thank you,
Justin
While normally I'm reluctant to suggest using XPath, it's probably the most appropriate approach here, using XPathSelectElement:
string[] paths = { "FieldOutputs/Capacity", "EngOutputs/Performance",
"EngOutputs/Unit/Efficiency"};
foreach (string path in paths)
{
XElement element = parent.XPathSelectElement(path);
if (element != null)
{
// ...
}
}
The Descendants() method is what you're looking for, I believe. For example:
var descendants = myParent.Descendants();
foreach (var e in descendants) {
...
}
http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.descendants.aspx
Edit:
Looking at your question more closely, it looks like you may want to use XPathSelectElements()
var descendants = myParent.XPathSelectElements("./FieldOutputs/Capacity | ./EngOutputs/Performance | ./EngOutputs/Units/Efficency");
http://msdn.microsoft.com/en-us/library/bb351355.aspx
First off we have the almighty code!
List nodes = new List();
TreeNode Temp = new TreeNode();
TreeNodeCollection nodeList = treeViewTab4DirectoryTree.Nodes;
while (nodeList.Count != 0)
{
Temp = nodeList[0];
while (Temp.FirstNode != null)
{
Temp = Temp.FirstNode;
}
if (!nodes.Contains(Temp.FullPath))
{
nodes.Add(Temp.Text);
}
nodeList.Remove(Temp);
}
Now the problem: I have written the code above with the intent of creating a List containing the text from all the nodes in the tree. That works perfectly. The problem I am having is when I remove the nodes from my variable they are also being removed from the actual list. The question would be how can I make a copy of the list of nodes so I can play with them without messing with the ACTUAL list. How do I make a copy of it without just making a reference to it? Any help will be greatly appreciated!
Your problem arises because "nodeList" is a reference to treeViewTab4DirectoryTree.Nodes, rather than a copy of it.
The solution depends entirely on the what type of TreeNodeCollection you're using (WinForms, ASP.net, something else?), as you'll need to look for a .Copy(), .Clone(), .ToArray() method or similar to take a copy of the contents of the collection, rather than a reference to the existing collection.
If, for example, you're using asp.net and thus the System.Web.UI.WebControls.TreeNodeCollection, you could use the .CopyTo method in a way similar to this:
TreeNode[] x = null;
treeViewTab4DirectoryTree.Nodes.CopyTo(x, 0);
Updated to show stack based approach:
List<String> result = new List<String>();
Stack<IEnumerator> nodeColls = new Stack<IEnumerator>();
IEnumerator nodes = treeViewTab4DirectoryTree.Nodes.GetEnumerator();
nodeColls.Push(null);
while (nodes != null)
{
while (nodes.MoveNext())
{
result.add(nodes.Current.FullPath);
if (nodes.Current.FirstNode != null)
{
nodeColls.Push(nodes);
nodes = nodes.Current.Nodes.GetEnumerator();
}
}
nodes = nodeColls.Pop();
}
The code below does not work as was mentioned in comments, because it doesn't traverse the entire tree, but only takes the first leaf node of each top-level branch.
I actually thought the original code (in the question) did so too, because I thought the Remove would actually remove the top-level node after finding the first leaf node under it; but instead, it tries to remove the leaf node from the collection of top-level nodes, and just ignores it if it can't find it.
Original post, non-functioning code
First of all, why do you need to remove the items from your list?
List<string> nodes = new List<string>();
foreach (TreeNode tn in treeViewTab4DirectoryTree.Nodes)
{
TreeNode temp = tn;
while (Temp.FirstNode != null)
{
Temp = Temp.FirstNode;
}
if (!nodes.Contains(Temp.FullPath))
{
nodes.Add(Temp.Text);
}
}
To answer your concrete question, assuming the Nodes collection implements IEnumerable, use:
List<TreeNode> nodeList = new List<TreeNode>(treeViewTab4DirectoryTree.Nodes);
If you do decide to stick with your while loop, you can save an instatiation by changing
TreeNode Temp = new TreeNode();
to
TreeNode Temp = null;
... you're never actually using the object you create, at least in the part of the code you've shown.