Sometimes you get one of those days no matter how much you batter your head around a wall, even the simplest task alludes you (this is one of those days!).
So what I have is a list of categories
CategoryID, CategoryName, ParentID, Lineage
1 Root Category, NULL, /1/
2 Child Category, 1, /1/2/
3 Grandchild, 2, /1/2/3
4 Second Root, NULL, /4/
5 Second Child 2, /1/2/5/
I've created a class to hold this where it contains all the values above, plus
ICollection<Category> Children;
This should create the tree
Root Category
`-- Child category
| `-- Grandchild
`-- Second Child
Second Root
So I'm trying to add a new category to the tree given the Lineage and the element, I convert the lineage to a queue and throw it into this function.
public void AddToTree(ref Category parentCategory, Category newCategory, Queue<Guid>lineage)
{
Guid lastNode = lineage.Dequeue();
if(lastNode == newCategory.CategoryId)
{
parentCategory.Children.Add(newCategory);
return;
}
foreach (var category in parentCategory.Children)
{
if(category.CategoryId == lastNode)
{
this.AddToTree(ref category, newCategory, lineage);
}
}
}
Now two problems I'm getting
The self referencing isn't too worrying (its designed to be recursive) but since the category in the foreach loop is a locally instantiated variable I can't make it by reference and use it as a pointer.
I'm sure there has to be an easier way than this!
Any pointers would be greatly received.
This code seems to be what you are looking for, but without any self references and recursions - it goes through the tree along the given lineage and in the end of the lineage inserts the given category.
Several assumptions:
Tree is stored as a list of its roots
lineage is a string
void AddCategory(List<Category> roots, Category categoryToAdd, string lineage)
{
List<Guid> categoryIdList = lineage.Split('/').Select(id => new Guid(id)).ToList();
List<Category> currentNodes = roots;
Category parentNode = null;
foreach (Guid categoryId in categoryIdList)
{
parentNode = currentNodes.Where(category => category.CategoryId == categoryId).Single();
currentNodes = parentNode.Children;
}
parentNode.Children.Add(categoryToAdd);
}
You dont appear to need the "ref" at all. You are not modifying the object reference, just its state.
EDIT:
If you must use ref, then use a temporary variable, for example...
foreach (var temp in parentCategory.Children)
{
Category category = temp;
if (category.CategoryId == lastNode)
{
this.AddToTree(ref category, newCategory, lineage);
}
}
But even with this, the ref is about useless. AddToTree does not modify the reference value. It modifies the referenced objects state. Maybe you have more code involved that we need to see.
If your intent is to modify the child reference in the parent, you will have an issue with ICollection Children object. You cannot use "ref" on an element in the ICollection to in effect replace the reference. You would have to remove the child reference and add a new one.
Related
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)
I am working on a project in C# where I have an object with properties, one of which is called Children which is the same as the parent object. For example:
public class ObjectInformation
{
public string FullName {get; set;}
public string FriendlyName {get; set;}
public List<ObjectInformation> Children {get;}
}
And I already have a method which flattens a root object into a simple list:
public static IEnumerable<ObjectInformation> Flatten(List<ObjectInformation> objs)
{
var localCopy = Helpers.General.DeepCopy(objs);
var finalList = new List<ObjectInformation>();
foreach(var obj in localCopy)
{
if(obj.Children.Count > 0)
{
finalList.AddRange(Flatten(obj.Children));
obj.Children.Clear();
}
obj.Parent = null;
finalList.Add(obj);
}
return finalList;
}
I know the above method could probably be improved, but it works right now. However, what I'm trying to do now is traverse the nested list and output the data, while indenting lines depending on the nesting level.
So, as an example, say the root object has two objects, the first one which has one child, and the second which has a child, which also has a child. I would want the output to be something like this:
FullName of Root Object 1
FullName of Child 1 of Root Object 1
FullName of Root Object 2
FullName of Child 1 of Root Object 2
FullName of Child 1 of Child 1 of Root Object 2
To do the indenting, I need some kind of counter to determine how deep the level is nested. I keep running into an issue using a recursive method though because on each call, the variable is reset. I was thinking, maybe I need to use a static variable to track the nesting level. The issue I see with that though is that as it travels back up, the static variable will still have the value of the deepest level its reached.
I'm kind of at a lost of how to proceed with this, albeit I'm sure it is probably a simple solution I just can't picture at the moment; I generally don't use/need recursion so I don't have a lot of experience actually using it.
Any help/suggestions you an provide would be greatly apprecated.
Thank You
I would recommend a method that takes the object and prints each with a defined space. Recursive call would add x amount of spaces each time you would go in deeper within the objects.
public static void Print(this ObjectInformation parent, int spacing = 2)
{
Console.WriteLine($"{new string(' ', spacing)}{parent.FullName}");
foreach (ObjectInformation child in parent.Children)
{
child.Print(spacing + 2);
}
}
I tried this same as an exercise and got a working result.
public void PrintInfoList(IEnumerable<ObjectInformation> list)
{
var result = string.Join("\n", list.Select(item => GetPrintedFormat(item)));
Console.WriteLine(result);
}
public string GetPrintedFormat(ObjectInformation info)
{
string printedFormat = string.Empty;
printedFormat = $"Fullname of {info.FriendlyName} - {info.FullName}";
if (info.Children != null && info.Children.Any())
{
childCount++;
_formatter = $"\n{string.Empty.PadRight(childCount, '\t')}";
printedFormat += $"{_formatter}{string.Join(_formatter, info.Children.Select(child => GetPrintedFormat(child)))}";
}
else
if (childCount > 0) childCount--;
return printedFormat;
}
this is tested working solution. Let me know what you think of this.
But i like to vote #Jawad's simplest way too. (clap)
I'm building my own linked list in C#. I'm not asking for you to do my homework. Hopefully something can be cleared up for me though.
The simple answer may be to this sub-question:
Should a linked list contain only unique values? It's not explained anywhere, so it's odd to me that methods like Remove() and Find() exist since they search by the Node's key/value. It will always only find/remove the first instance.
e.g.,
// linkedList = {1, 1, 1, 2, 3, 4 };
linkedList.Remove(1);
// linkedList = {1, 1, 2, 3, 4 };
The Meat:
So far, all of my methods that create nodes don't return anything (per the homework). So whenever I create a node, I can't access it directly except for by value. Yet, the homework says that the InsertAfter() and InsertBefore() methods should accept a node and a key(value).
InsertAfter(node, key);
The only workaround I can think of is to use the Find() method to find a node and pass that in as the first param. It seems hacky though, and it would have to return type Node.
Should I be returning values to store nodes? Should I actually be passing in two Node.Values to the methods and not actually a whole node and a key for another one?
Here's some of my code:
Node Class
public class Node
{
public Node Next;
public object Value;
}
AddToEnd
public void AddToEnd(object value)
{
Node temp = new Node();
temp.Value = value;
if (Head.Value == null)
{
Head.Value = value;
}
else
{
Node currentNode = Head;
while (currentNode.Next != null)
{
currentNode = currentNode.Next;
}
currentNode.Next = temp;
}
}
Should a linked list contain only unique values?
No. To make it more clear you can rename Find to FindFirst or FindLast or FindAfterIndex, etc.
The only workaround I can think of is to use the Find() method to find a node and pass that in as the first param.
I would do exactly the same. However the users of the linked-list don't have to be aware of implementation details, such as there's a class called Node that carries data, and so on. Therefore, I would make a method called FindNode and make it private. Therefore the publicly accessible Find method only returns the index of (first) value found, and internally I will use the other FindNode private method to deal with insertion, and other optimised operations.
Also to avoid repeated code, I'd rewrite the Find method to call FindNode internally as well.
I've been looking for an answer everywhere, but can't find anything. I have two tables, Media and Keywords, which have a many to many relationship. Now the Keywords table is quite simple - it has a ID, Name and ParentFK column that relates to ID column (it's a tree structure).
The user can assign any single keyword to the media file, which means that he can select a leaf without selecting the root or branch.
Now I have to be able to determine if a root keyword has any child, grandchild etc. which is assigned to a media object, but I have to do it from the root.
Any help will be appreciated.
Just look for any entry, which has the given ParentFK set with your ID.
public static bool HasChild(int id) {
return
db.Keywords.Any(item => item.Parent == id);
}
public static bool HasGrandChilds(int id) {
return
db.Keywords.Where(item => item.Parent == id).Any(item => HasChild(item.ID);
}
A more generic way:
public static bool HasGrandChilds(int id, int depth) {
var lst = new List<Keywords>();
for (var i = 0; i < depth - 1; i++) {
if (i == 0)
{
//Initial search at first loop run
lst = db.Keywords.Where(item => item.ParentId == id);
}
else
{
//Search all entries, where the parent is in our given possible parents
lst = db.Keywords.Where(item => lst.Any(k => k.Id == item.Parent));
}
if (!lst.Any())
{
//If no more children where found, the searched depth doesn't exist
return false;
}
}
return true;
}
From your current schema I can't think of a better solution than the following:
Issue a query to retrieve a list of all children of the root.
Issue queries to retrieve a list of all children of the children from the previous step.
So on, recursively to create a list of all descendants of the root.
Next query the DB for all media objects that have any of the keywords in the list.
But the above algorithm will entail multiple calls to the DB. You can make it in a single query of you refine your schema a little. I would suggest that you keep for each keyword not only its parent FK, but also its root FK. This way you could issue a single query to get all objects that have a keyword whose root FK is the desired one.
Thanks again for all the wonderful answers you have all posted!
I have two tables in SQL. The first defines the parent, and has a primary key column called ParentId. I also have a child table that has a primary key, and a foreign key as 'ParentId'. So the two tables form a one parent - to many children relationship.
The question is what is the most efficient way to pull the parent + child data C# code? The data has to be read into the following objects:
public class Parent
{
public int ParentId { get; set; }
public List<Child> Children { get; set; }
// ... many more properties ... //
}
public class Child
{
public int ChildId { get; set; }
public string Description { get; set; }
// ... many more properties ... //
}
If i use the following query I will get the parent and the children at once where each parent will be repeated as many times as many children it has:
SELECT
p.ParentId as 'ParentId',
c.ChildId as 'ChildId',
-- other relevant fields --
FROM
Parents p
INNER JOIN
Children c
ON
p.ParentId = c.ParentId
Using this approach I'd have to find all the unique parent rows, and then read all the children. The advantage is that I only make 1 trip to the db.
The second version of this is to read all parents separately:
SELECT * FROM Parents
and then read all children separately:
SELECT * FROM Children
and use LINQ to merge all parents with children. This approach makes 2 trips to the db.
The third and final (also most inefficient) approach is to grab all parents, and while constructing each parent object, make a trip to the DB to grab all its children. This approach takes n+1 connections: 1 for all parents and n number of trips to get all children for each parent.
Any advise on how to do this easier? Granted i can't get away from using stored procedures, and I can't use LINQ2SQL or EF. Would you prefer Data Tables vs DataReaders and if so how to use either with approach 1 or 2?
Thanks,
Martin
I prefer pulling all results in one query and just build the tree in one loop
SELECT p.ParentId as 'ParentId', null as 'ChildId'
FROM Parents p
UNION ALL
SELECT c.ParentId as 'ParentId', c.ChildId as 'ChildId'
FROM Children c
List<Parent> result = new List<Parent>();
Parent current;
while (dr.Read())
{
if (string.isNullOrEmpty(dr['ChildId']))
{
//create and initialize your parent object here and set to current
}
else if (!string.isNullOrEmpty(dr['ChildId'])
&& dr['ParentId'].ToString().Equals(current.ParentId.ToString())
{
//create and initialize child
//add child to parents child collection
}
}
Using this approach I'd have to find
all the unique parent rows, and then
read all the children.
You could just include an order by p.ParentId. This ensures all children from the same parent are in consecutive rows. So you can read the next row, if the parent has changed, create a new parent object, otherwise add the child to the previous parent. No need to search for unique parent rows.
I usually make this decision at the table level. Some tables I need the children often, so I grab them right away. In other cases accessing the children is a rarity, so I lazy-load them.
I would guess option #2 would be more efficient bandwidth wise over option #1 (as you're not repeating any data).
You can have both queries in a single stored procedure, and execute the procedure through code using a sqldataadapter (i.e. (new SqlDataAdapter(command)).Fill(myDataSet), where myDataSet would contain the two tables).
From there you'd read the first table, creating a dictionary of the parents (in a Dictionary<int, Parent>) by ParentId, then simply read each row in the 2nd table to add the children:
parents[(int)myDataSet.Tables[1]["ParentId"]].Children.Add(new Child() { etc } );
The pseudo code is probably off a bit, but hopefully you get the general idea