Make function dynamic / use Iteration / Make DRY - c#

I want to make this function dynamic/Dry
rather than having to do multiple loops, do one loop /recursively
step1: The function receives a path
from that path it creates a Dir Model for the parent directory,
if the parent has sub directories, it adds them as models, to the parent subdirectory list,
step2: for each of the sub directories it repeats what it did in step 1, with the child being the parent to its sub directories if it has them,
this should repeat as long as a subdirectory to a parent has subdirectories,
finally, it returns the main Directory with its sub directories, as models with their own subdirectory models
public string RepoAsJson(string repoPath)
{
string path = #"C:\parentDir\subDir\subSubDir";
string[] parentDir = Directory.GetDirectories(path);
RepoDirModel model = new RepoDirModel();
foreach (string dir in parentDir){
RepoDirModel model1 = new RepoDirModel();
model1.Name = rket.Split(Path.DirectorySeparatorChar).Last();
model.SubDirectories.Add(model1);
foreach (string sub in Directory.GetDirectories(rket))
{
RepoDirModel model2 = new RepoDirModel();
model2.Name = sub.Split(Path.DirectorySeparatorChar).Last();
model1.SubDirectories.Add(model2);
foreach (string subSub in Directory.GetDirectories(sub))
{
RepoDirModel model3 = new RepoDirModel();
model3.Name = subSub.Split(Path.DirectorySeparatorChar).Last();
model2.SubDirectories.Add(model3);
}
}
}
return JsonSerializer.Serialize(model);
}
Remove nested loops / use Iteration / Make function dynamic

Since this seems to be some sort of course work / homework / training (maybe?), I am not going to post a working solution, but rather give you some hints.
First of all: What is the common "thing" your existing code does?
It gets the list of subdirectories from a specific path string and adds those into a tree structure.
That's it. So the first thing you might want to do is "extract" this functionality into a function or method, that only works on its parameters (no outside fields, "global vars" or something like that). You pass it the node to expand and the path to analyse and it will take care of one step.
Now, if you want to go into recursion, you need some additional things:
Do you want to go deep first or wide first?
When do you stop?
The second question is rather easy here: If there are no children (i.e. subdirectories) of the current subject, we are done - no further recursion.
The answer to the first one may not be so relevant in this setting, but think about it anyway. If for example you are looking for a specific leaf or node in the tree it might be relevant if it is more probable that you will find it quicker going deep first or go level by level.
But anyway: If you want to go deep first, you'd do something like this (Pseudo code)
RecursiveMethod( Element parent, ModelNode parentNode )
{
foreach (child of parent)
{
newNode = AddToModel(parentNode, child)
RecursiveMethod( child, newNode ) // <-- Note: Recursion IN the loop
}
}
And if you want to go wide something like
RecursiveMethod( List<Element> parents, Model m )
{
newParents = new List<Element>();
foreach( parent in parents )
{
foreach (child of parent)
{
AddToModel(m, parent, child)
newParents.Add(child);
}
}
// Note: Recursion AFTER the loop - in some cases it may even happen _before_
if( newParents.Length > 0 ) RecursiveMethod( newParents, m );
}
If you want an iterative solution, you have again those two questions, you just do a little different:
// you need some sort of datastructure, for "width first" we can use a FIFO queue
queue = new Queue(); // Not _the_ .NET Queue, just a FIFO for demo
// we start with the root...
queue.Enqueue( path );
// Then you do that one step as long as there is something in the queue
while( queue.Length > 0 )
{
current = queue.Dequeue(); // Get the first element in queue
foreach( child of current ) // No child -> no new elements in queue, length decreases
{
// If there are children =>
// Do what you need to do
InsertIntoModel(child); // Whatever that means in particular
// and put them into the queue for later processing
// queue length increases
queue.Enqueue(child);
}
}
If you want to go deep first, you can chose another mode of insertion and/or a different data structure. (For example a Stack, that is LIFO, creating width-first behavior)

Related

TreeView - Node not expanding

I have a TreeView with several nodes and if a special node (you will see in code) is deleted, the parent node should be expanded after updating the TreeView.
Here's how I tried it:
public void Remove(){
...
...
else if ((NodeType)n.Tag == NodeType.Attribute) //Here I simply check if it's the "special" parent
{
Commands.CommandAttributeRemove cmd = (Commands.CommandAttributeRemove)mAppData.CommandFactory.Create("AttributeRemove");
cmd.Data = n.Text;
cmd.ObjectClass = mObjectClass;
cmd.ObjectTypeName = n.Parent.Parent.Text;
list.Add(cmd);
mNodeToExpand = mTreeView.SelectedNode.Parent; //THIS LINE IS IMPORTANT... mNodeToExpand is a member variable which I use in UpdateData()
}
...
...
UpdateData();
}
public void UpdateData()
{
… //A lot of not so important stuff happening here (at least not important for what I want, I think)
...
//Update Selected Items (for the case that objects were deleted) and UpdateSelection
OnSelect();
//UpdateSelection();
this.Update();
Now that's interesting stuff:
if (mNodeToExpand != null)
{
mNodeToExpand.Expand();
mNodeToExpand = null;
}
}
This is how I tried to achieve what I want, but the node doesn't expand (it still has other children).
In the Remove() I also tried mTreeView.SelectedNode.Parent.Nodes.Add(new Node("Blabla")); but it doesn't even add a node.
And in the if(mNodeToExpand!=null) I also try to set the selectedNode to mNodeToExpand, but it gives me a NullReferenceException EVEN THOUGH I CHECK IF IT'S NULL IN THE IF. WHY?
This can't freaking be it. FullPath is apparently the path of the node in the treeview, so for example if you have Node2 which is a sub-node of Node1, then the FullPath of Node1 is "Node1" and of Node2 "Node1//Node2" … Now the thing is that FOR SOME WEIRD REASON my mNodeToExpand loses the Information of FullPath and suddenly it's only "Node2" instead of "Node1//Node2"... Because of that it isn't expanded cause the TreeView doesn't find a node with this FullPath… Now what I did is that I stored the FullPath in a string and when I want to expand it I search for the node with this FullPath and then expand what is returned. This is so stupid, cause imagine a list of 1 000 000 nodes. This could take very long. I'm starting to really dislike this Crownwoods.DotNetMagic library but it's already existing Code I'm working on.

Traversing all descendants of an object

I'm having problems with traversing all descendants of an object.
The 'unit' in the code below is of type Unit in my program. It has a property ChildUnits which returns a List<Unit> of the children of the unit.
I can successfully perform operations on the children. Then I check if those children have children, and if they do I can perform operations on them as well.
However, I need to check all descendants in case there is more depth than just grandchildren. I had a go with while loops in addition to the code below but it got really messy so I left it out.
This is the code I have reverted back to:
foreach (var child in unit.ChildUnits)
{
//do something here with the child (I know it sounds dodgy).
bool hasMoreChildren = child.ChildUnits.Count != 0;
if(hasMoreChildren)
{
foreach (var descendant in child.ChildUnits)
{
//do something here with the descendant.
}
}
}
I could just go another level deep as it's relatively rare for a unit to have more depth than that. But that's not a clean solution.
I think I might need to use a graph traversal algorithm and/or recursion perhaps, but I would like some advice on how to solve this problem most efficiently.
Edit: Is it possible to do this without defining a new function/method?
Edit: Is it possible to do this without defining a new function/method?
You could use an anonymous method...which is not exactly "not defining a new method", I know :)
However, there's another issue you should take care of: Circular references... even if you dont think there will be any
Here's an implementation, without defining any new method
Action<IEnumerable<Unit>> process = null;
var processed = new HashSet<Unit>();
process = list => {
foreach(var u in list.Where (processed.Add))
{
// do something here with u
//... and then process children
process(u.ChildUnits);
}
};
process(myList); // do the actual processing
Algorithm like this:
def traverse(Unit i):
for (Unit child : i.childList):
// Perform your logic for child
traverse(child)
This will perform the same function for each child for the first node , and when applying it for i.child[j] it will perform the same function for all i.child[j].child[k] so it will perform what you want for each node and all its childs.
instead you can use stack :
stack s;
s.push(firstNode);
while(!stack.empty()):
t = stack.pop()
foreach(Unit child : t):
s.push(child)
// Perform logic for child
You can use recursion:
void processChildren(List<Unit> children)
{
foreach (var child in children)
{
//do something here with the child (I know it sounds dodgy).
processChildren(child.Children); // recursive call here
}
}
If you don't want to define a new method, you could also roll your own stack:
var stack = new Stack<Unit>();
stack.push(firstUnit);
while( !stack.Any() ) {
var item = stack.pop();
//do something here with the item
foreach(var child in item.Children)
{
stack.push(child);
}
}
Another way to do this without recursion or an action/lambda, is to use a list with items to handle.
Like this.
var toDoList = new List<Unit> { unit };
while (toDoList.Any()) {
// Get current child, and remove it from the to-do-list
var currentChild = toDoList.First();
toDoList.RemoveAt(0);
// Do something with the current child.
// ...
// Now see, if the child has any children to handle
if (currentChild.ChildUnits != null && currentChild.ChildUnits.Any()) {
toDoList.AddRange(currentChild.ChildUnits);
}
}

A special C# Tree algorithm in Umbraco CMS

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(...)

Recursive collection search

I have a collection (List<Element>) of objects as described below:
class Element
{
string Name;
string Value;
ICollection<Element> ChildCollection;
IDictionary<string, string> Attributes;
}
I build a List<Element> collection of Element objects based on some XML that I read in, this I am quite happy with. How to implement searching of these elements currently has me, not stumped, but wondering if there is a better solution.
The structure of the collection looks something like this:
- Element (A)
- Element (A1)
- Element (A1.1)
- Element (A2)
- Element (B)
- Element (B1)
- Element (B1.1)
- Element (B1.2)
- Element (C)
- Element (C1)
- Element (C2)
- Element (C3)
Currently I am using recursion to search the Attributes dictionary of each top level (A, B, C) Element for a particular KeyValuePair. If I do not find it in the top level Element I start searching its ChildElement collection (1, 1.1, 2, 2.1, n, etc.) in the same manner.
What I am curious about is if there is a better method of implementing a search on these objects or if recursion is the better answer in this instance, if I should implement the search as I am currently, top -> child -> child -> etc. or if I should search in some other manner such as all top levels first?
Could I, and would it be reasonable to use the TPL to search each top level (A, B, C) in parallel?
Recursion is one way of implementing a tree search where you visit elements in depth-first order. You can implement the same algorithm with a loop instead of recursion by using a stack data structure to store the nodes of your tree that you need to visit.
If you use the same algorithm with a queue instead of a stack, the search would proceed in breath-first order.
In both cases the general algorithm looks like this:
var nodes = ... // some collection of nodes
nodes.Add(root);
while (nodes.Count != 0) {
var current = nodes.Remove ... // Take the current node from the collection.
foreach (var child in current.ChildCollection) {
nodes.Add(child);
}
// Process the current node
if (current.Attributes ...) {
...
}
}
Note that the algorithm is not recursive: it uses an explicit collection of nodes to save the current state of the search, whereas a recursive implementation uses the call stack for the same purpose. If nodes is a Stack<Element>, the search proceeds in depth-first order; if nodes is a Queue<Element>, the search proceeds in breadth-first order.
I grabbed this bit from SO somewhere, Its not mine but I cant provide a link to it. This class Flattens out a treeview for a recursive search, looks like it should do the same for you.
public static class SOExtension
{
public static IEnumerable<TreeNode> FlattenTree(this TreeView tv)
{
return FlattenTree(tv.Nodes);
}
public static IEnumerable<TreeNode> FlattenTree(this TreeNodeCollection coll)
{
return coll.Cast<TreeNode>()
.Concat(coll.Cast<TreeNode>()
.SelectMany(x => FlattenTree(x.Nodes)));
}
}
I found the link I got this from - its very easy to use. have a look. Is there a method for searching for TreeNode.Text field in TreeView.Nodes collection?
You can re-use existing components designed specifically for traversing in different ways, such as NETFx IEnumerable.Traverse Extension Method. It allows you to depth or breadth first. It lets you traverse an enumerable tree, depth or breadth first.
Example to get a flattened enumerable of directories:
IEnumerable<DirectoryInfo> directories = ... ;
IEnumerable<DirectoryInfo> allDirsFlattened = directories.Traverse(TraverseKind.BreadthFirst, dir => dir.EnumerateDirectories());
foreach (DirectoryInfo directoryInfo in allDirsFlattened)
{
...
}
For BreadhFirst it uses Queue<T> internally and for DepthFirst it uses Stack<T> internally.
It is not traversing nodes parallell and unless the traversal is resource demanding it isn't appropriate to use parallellism at this level. But that depends on the context.

Most appropriate way to construct a File and Directory class in order to easily filter results when placing them on a tree

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.

Categories

Resources