I have a list and I need to sort it to this hierarchy
{ Id=1, ParentId = null, Name = "Item1", Type="0"}
{ Id=2, ParentId = 1, Name = "ItemChild1", Type="1"}
{ Id=3, ParentId = 1, Name = "ItemChild2", Type="1"}
{ Id=4, ParentId = 3, Name = "ItemGrandChild1", Type="2"}
{ Id=5, **ParentId = 1**, Name = "ItemGrandChild2", Type="2"}
{ Id=6, ParentId = null, Name = "Item7", Type="0"}
...
Unlike normal parent child relationships, here
Items of Type2 can be child of either Type1 or Type0
All the Id's are guids
I have seen may stack answers on child parent sorting using Linq. But my case is different.
Any elegant way using Linq ?
If I were you, I would give up trying to sort this with either linq or sql. Abstractions can be helpful, but in a complex case like this they will just get in your way.
You will save a lot of time if you just write your own sorter.
class MyCompare : IComparer<MyObject>
{
public int Compare(x1, x2)
{
if (x1.parent == parent1 && x2.parent == parent2)
return 1;
if (x1.parent == x2.parent)
return 0;
//ect
}
}
List<MyObject> list = GetWeirdObjects();
list.Sort(new MyCompare());
Create a lookup to find fast the children and project them to the parent collection. As far as i see it does not depend at all if your children have different types as long as they know which element is their parent.
public class TreeItem
{
public int Id { get; set; }
public int? ParentId { get; set; }
public IEnumerable<TreeItem> Children { get; set; }
public void PrintAllChildren()
{
this.PrintAllChildren(0);
}
private void PrintAllChildren(int indent)
{
Debug.WriteLine("{1}Item id: {0}", this.Id, string.Concat(Enumerable.Repeat<int>(0, indent).Select(i => " ")));
if (this.Children != null)
foreach (var item in this.Children)
item.PrintAllChildren(indent + 1);
}
}
public static class TreeItemExtension
{
public static IEnumerable<TreeItem> GetAsTree(this IEnumerable<TreeItem> data)
{
var lookup = data.ToLookup(i => i.ParentId);
return lookup[null].Select(i => {
i.FillChildren(lookup);
return i;
});
}
private static TreeItem FillChildren(this TreeItem item, ILookup<int?, TreeItem> lookup)
{
item.Children = lookup[item.Id].Select(i => i.FillChildren(lookup));
return item;
}
}
Related
This question already has answers here:
How to flatten tree via LINQ?
(15 answers)
Closed 6 months ago.
I've the following class, which contains some info about the object it also has a list of same object and hierarchy goes on. This is my class:
public class Category
{
public List<Category>? children { get; set; }
public bool var { get; set; }
public string? name { get; set; }
public bool leaf { get; set; }
public int category_id { get; set; }
}
I have a list List<Category> categories; I want to loop over the list and go deep down in every children and create this new object:
public class DBCategory
{
public string? CategoryId { get; set; }
public string? CategoryName { get; set; }
public string? CategoryParentId { get; set; }
}
I have tried to loop over my list and then call function recursively but I'm also stuck there because children isn't a category class but a list of categories so the function fails to accept parameter in if clause:
foreach (var category in categories)
{
CreateDBCategory(category);
}
DBCategory CreateDBCategory(Category category)
{
DBCategory dBCategory = new DBCategory();
if (category.children.Count > 0)
{
return CreateDBCategory(category.children);
}
return dBCategory;
}
I have also tried to reach most bottom child by this, but this code says not all paths return a value.
DBCategory testFunction(List<Category> categories)
{
foreach (var category in categories)
{
if (category.children.Count > 0)
{
return testFunction(category.children);
}
else
{
return category;
}
}
}
One of the common ways to handle such cases is to have the List to be filled passed as an argument to the method. E.g.:
List<DBCategory> dbCategories = new();
foreach (var category in categories)
{
CreateDBCategory(category, dbCategories);
}
void CreateDBCategory(Category category, List<DBCategory> dbCategories)
{
DBCategory dbCategory = new DBCategory();
// Fill dbCategory
dBCategories.Add(dbCategory);
if (category.children != null)
{
// recurse over all children categories and add them to the list
foreach (var child in category.children)
{
CreateDBCategory(child, dbCategories);
}
}
}
It could be argued that this solution does not fit the functional paradigm as it has side effects (modifying the passed in List), so an alternative, more functional approach would be to return a list from the recursive method, e.g.:
List<DBCategory> dbCategories = new();
foreach (var category in categories)
{
dbCategories.AddRange(CreateDBCategory(category));
}
IEnumerable<DBCategory> CreateDBCategory(Category category)
{
List<DBCategory> dbCategories = new();
DBCategory dbCategory = new DBCategory();
// Fill dbCategory
dbCategories.Add(dbCategory);
if (category.children != null)
{
// recurse over all children categories and add them to the list
foreach (var child in category.children)
{
dbCategories.AddRange(CreateDBCategory(child));
}
}
return dbCategories;
}
This does however perform a lot more allocations, so in some cases it can perform slower than the first approach
Noted that this is untested, but it should work.
IEnumerable<DBCategory> FlattenCategories(IEnumerable<Category> categories, int parentId)
{
DBCategory selector(Category cat, int pid) =>
return categories
.Select(c => new DBCategory {
CategoryId = cat.category_id,
CategoryName = cat.name,
CategoryParentId = pid,
})
.Concat(categories.SelectMany(
c => FlattenCategories(c.children, c.category_id)
);
}
Just call FlattenCategories(categories).ToList(); to get List<DBCategory>
From here, A generic solution.
public static IEnumerable<T> Traverse<T>(
this T root,
Func<T, IEnumerable<T>> childrenSelector)
{
ArgumentNullException.ThrowIfNull(childrenSelector);
var stack = new Stack<T>();
stack.Push(root);
while(stack.Count > 0)
{
var current = stack.Pop();
yield return current;
foreach(var child in childrenSelector(current))
{
stack.Push(child);
}
}
}
So you can do this,
foreach(var category in root.Traverse(c => c.Children))
{
...
}
or some LINQ. The beauty is, it won't allocate more memory than your biggest leaf collection and won't have a stack overflow for deep trees.
Try following :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication40
{
class Program
{
static void Main(string[] args)
{
Category root = new Category()
{
children = new List<Category>() {
new Category() {
children = new List<Category>() {
new Category() {
var = true,
name = "2A",
leaf = true,
category_id = 21
},
new Category() {
var = true,
name = "2B",
leaf = true,
category_id = 22
}
},
var = true,
name = "1A",
leaf = false,
category_id = 1
},
new Category() {
children = new List<Category>() {
new Category() {
var = true,
name = "2C",
leaf = true,
category_id = 23
},
new Category() {
var = true,
name = "2D",
leaf = true,
category_id = 24
}
},
var = true,
name = "1B",
leaf = false,
category_id = 2
},
},
category_id = 0,
name = "root",
leaf = false,
var = true
};
List<DBCategory> children = DBCategory.GetChildren(root,null);
}
}
public class Category
{
public List<Category> children { get; set; }
public bool var { get; set; }
public string name { get; set; }
public bool leaf { get; set; }
public int category_id { get; set; }
}
public class DBCategory
{
public int? CategoryId { get; set; }
public string CategoryName { get; set; }
public int? CategoryParentId { get; set; }
public static List<DBCategory> GetChildren(Category catgory, int? parentId)
{
List<DBCategory> results = new List<DBCategory>() { new DBCategory() {
CategoryId = catgory.category_id,
CategoryName = catgory.name,
CategoryParentId = parentId
}};
if (catgory.children != null)
{
foreach (Category child in catgory.children)
{
results.AddRange(GetChildren(child, catgory.category_id));
}
}
return results;
}
}
}
interface Nameable
{
string Name { get; set; }
}
class Parent : Nameable
{
public string Name { get; set; }
public List<Child> Children { get; set; } = new List<Child>();
}
class Child
{
public string Name { get; set; }
public int Value { get; set; }
public string DataOne { get; set; }
public string DataTwo { get; set; }
public double DataThree { get; set; }
}
static async void MainAsync(string[] args)
{
for (int i = 0; i < random.Next(10000, 50000); i++)
{
Parents.Add(CreateParent());
}
Parents = Parents.GroupBy(g => g.Name).Select(grp => grp.First()).ToList();
foreach (var parent in Parents)
{
await Insert<Parent>(parent);
}
// update objects randomly;
foreach (var parent in Parents)
{
for (int i = 0; i < random.Next(10, 30); i++)
{
int decision = random.Next(0, 2);
if (decision == 0 && parent.Children.Count > 0)
{
parent.Children.RemoveAt(random.Next(0, parent.Children.Count));
}
else
{
var inner = CreateChild();
if (!parent.Children.Any(io => io.Name == inner.Name))
{
parent.Children.Add(inner);
}
}
await ReplaceOne<Parent>(parent);
}
}
}
I have a list of Parents and each one contains a list of Child elements. When using the c# Mongo driver to replace these parents after they have been updated by either removing or adding new Children It sometimes creates duplicates of the Child on the Mongo side despite there being no duplicates when the code calls the replace method.
I think this is something to do with the atomic sub document structure of Mongo and how it updates/replaces items. Is there a way to prevent this from creating duplicates? and if it is not happening due to the atomic nature what is causing this?
Edit:
static async Task ReplaceOne<T>(T obj)
where T : Nameable
{
await database.GetCollection<T>(typeof(T).Name).ReplaceOneAsync(Builders<T>.Filter.Where(t => t.Name == obj.Name), obj);
}
static async Task Insert<T>(T obj)
{
await database.GetCollection<T>(typeof(T).Name).InsertOneAsync(obj);
}
static Parent CreateParent()
{
var innerObjects = new List<Child>();
for (int i = 0; i > random.Next(1, 10); i++)
{
innerObjects.Add(CreateChild());
}
return new Parent()
{
Name = RandomString(),
Children = innerObjects
};
}
static Child CreateChild()
{
return new Child()
{
Name = RandomString(),
Value = RandomInt(),
DataOne = RandomString(),
DataTwo = RandomString(),
DataThree = RandomDouble()
};
}
Added the replace/Insert snippets, they are using the mongo c# driver to insert into the db. The CreateParent and CreateChild just fills the objects with random relevant data.
I tried to guess your RandomString(), RandomInt() and RandomDouble() methods and I ran your project several times without cleaning the database. I could not detect any duplicates whatsoever based on the two "Name" properties (on parent and child).
I suspect your observation is somehow incorrect. In order to check if you do actually have duplicate children within the same parent you can use the following query:
collection.aggregate(
{
$unwind: "$Children"
},
{
$group:
{
_id:
{
"Name": "$Name",
"ChildName": "$Children.Name"
}
, "count": { $sum: 1 }
}
},
{
$match:
{
"count": { $ne: 1 } }
}
)
Objective: process an object and if the object implements an expected type, I want to change a specific property value (this part is working fine), and I also would like to apply the same logic to all property lists (that I explicit point) that are of the same expected type.
I have the following code:
public abstract class BaseObject
{
public int Id { get; set; }
}
public class Father : BaseObject
{
public DateTime CreatedOn { get; set; }
public string Name { get; set; }
public IEnumerable<ChildA> Children1 { get; set; }
public IEnumerable<ChildB> Children2 { get; set; }
public IEnumerable<ChildA> Children3 { get; set; }
public IEnumerable<ChildB> Children4 { get; set; }
}
public class ChildA : BaseObject
{
public int Val1 { get; set; }
}
public class ChildB : BaseObject
{
public string Name { get; set; }
public int Total { get; set; }
}
I want to process an object by applying some changes on a specific property on the target object and on all property children that I explicit say:
public void Start()
{
var listA = new List<ChildA> { new ChildA { Id = 1, Val1 = 1 }, new ChildA { Id = 2, Val1 = 2 } };
var listB = new List<ChildB> { new ChildB { Id = 1, Name = "1", Total = 1 } };
var obj = new Father { Id = 1, CreatedOn = DateTime.Now, Name = "F1", ChildrenA = listA, ChildrenB = listB };
// I explicit tell to process only 2 of the 4 lists....
ProcessObj(obj, x => new object[] { x.Children1, x.Children2 });
}
I was able to write this function:
public void ProcessObj<T>(T obj, Expression<Func<T, object[]>> includes = null)
{
var objBaseObject = obj as BaseObject;
if (objBaseObject == null) return;
// Here I change the ID - add 100 just as an example....
objBaseObject.Id = objBaseObject.Id + 100;
if (includes == null) return;
var array = includes.Body as NewArrayExpression;
if (array == null) return;
var exps = ((IEnumerable<object>)array.Expressions).ToArray();
for (var i = 0; i < exps.Count(); i++)
{
var name = ((MemberExpression)exps[i]).Member.Name;
var childProperty = obj.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).FirstOrDefault(prop => prop.Name == name);
if (childProperty == null) continue;
// NOT correct because I think I am getting a copy of the object
// and not pointing to the object in memory (by reference)
var childList = childProperty.GetValue(obj);
// TODO: loop on the list and apply the same logic as the father....
// change the ID field....
}
}
In this prototype I started writing reflection, but I really would like to avoid it if possible....
How can I do this???
Maybe I'm missing something, but it seems like you're complicating the problem by using expression trees. Can you just not use a regular Action and Func delegates to do this? Why do they need to be expression trees? Here's an example just using delegates:
public void ProcessObj<T>(T obj, Func<T, IEnumerable<object>> includes) {
var objBaseObject = obj as BaseObject;
if (objBaseObject == null) return;
// Create a reusable action to use on both the parent and the children
Action<BaseObject> action = x => x.Id += 100;
// Run the action against the root object
action(objBaseObject);
// Get the includes by just invoking the delegate. No need for trees.
var includes = includes(obj);
// Loop over each item in each collection. If the types then invoke the same action that we used on the root.
foreach(IEnumerable<object> include in includes)
{
foreach(object item in include)
{
var childBaseObject = item as BaseObject;
if(childBaseObject != null)
{
action(childBaseObject);
}
}
}
}
Useable just like before:
ProcessObj(obj, x => new object[] { x.Children1, x.Children2 });
No expression trees and no reflection, just regular delegate lambdas.
Hope that helps
How it would be possible to get a parent when tree structure is like this:
public class TreeModel
{
public int ID { get; set; }
public List<TreeModel> Children { get; set; }
}
Let's say we can't add a parent element item to this class (public TreeModel Parent { get; set; }).
Edit
How to get element m22 (ID=22) parent m2 (ID=2) from the m1? I thought we could iterate through m1 and somehow return parent when condition is right.
var m1 = new TreeModel() { ID = 1 };
var m2 = new TreeModel() { ID = 2 };
var m21 = new TreeModel() { ID = 21 };
var m22 = new TreeModel() { ID = 22 };
var m3 = new TreeModel() { ID = 3 };
m1.Children.Add(m2);
m2.Children.Add(m21);
m2.Children.Add(m22);
m1.Children.Add(m3);
var parent = m1.GetParent(p => p.ID == 22); //<-- How?
public IEnumerable<TreeModel> GetAllDescendants(IEnumerable<TreeModel> rootNodes)
{
var descendants = rootNodes.SelectMany(_ => GetAllDescendants(_.Children));
return rootNodes.Concat(descendants);
}
public static TreeModel GetParent(this TreeModel rootNode, Func<TreeModel, bool> childSelector)
{
var allNodes = GetAllDescendants(new [] { rootNode });
var parentsOfSelectedChildren = allNodes.Where(node => node.Children.Any(childSelector));
return parentsOfSelectedChildren.Single();
}
m1.GetParent(_ => _.ID == 22);
Obtain a flat list of all nodes
Search this list for the node whose direct children contains m22
Use this code pattern. It simplifies the code because you don't have to explicitly add nodes to the children and each node knows who its parent is and who its children are. Also it is all type safe.
class Program
{
static void Main(string[] args)
{
var m1=new TreeModel() { ID=1 };
var m2=new TreeModel(m1) { ID=2 };
var m21=new TreeModel(m2) { ID=21 };
var m22=new TreeModel(m2) { ID=22};
var m3=new TreeModel(m1) { ID=3 };
var item=m1.RecursiveFind((p) => p.ID==22);
var parent=item.Parent;
// parent.ID == 2
var root=item.Root;
// root.ID == 1;
}
}
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
When you derive from Tree<T>, you create custom tree structures that you design what the node class is (TreeModel here) and how to handle parents, children and siblings if needed.
What about:
public class SaneTreeModel: TreeModel
{
public SaneTreeModel Parent { get; set; }
}
}
I would approach this by first finding the first element that satisfies the condition (ID == 22 in your example) and then finding the parent of this element. Not the best solution but maybe you will need them separately for something else.
public TreeModel GetParent(Func<TreeModel, bool> function)
{
return GetParent(Where(function));
}
private TreeModel GetParent(TreeModel treeModel)
{
if (Children == null) return null;
if (Children.Contains(treeModel)) return this;
foreach (TreeModel child in Children)
{
TreeModel result = child.GetParent(treeModel);
if (result != null)
return result;
}
return null;
}
private TreeModel Where(Func<TreeModel, bool> function)
{
if (Children == null) return null;
foreach (TreeModel child in Children)
{
if (function(child))
return child;
TreeModel result = child.Where(function);
if (result != null)
return result;
}
return null;
}
If you put this code block in your TreeModel class, the example you provided will return m2
Absolutely not, with a child node like that you can't get its parent. Simply because there isn't any reference to it.
To get the parent of the node, you have to either add the parent field or save the reference in somewhere else (by a variable or something).
EDIT
#Zulis If you search from the root node, you can definitely find the node you want. But as I said, with just the child node you can't do that.
But I think you should avoid searching because that would be slow
I have a List of objects of type IGroup. These can be nested to an umlimited level, and I'm trying to group them after retrieving them from a database. I can't get my head around how to recursively add all groups to the right parents. Any groups with null as a parent are top level groups. I can't guarantee the order they come out of the database.
public interface IGroup {
string ID { get; set; }
string Name { get; set; }
string ParentID { get; set; }
IList<IGroup> Groups { get; set; }
...
So if I had a list of:
Group1: ID = g1, ParentID = null
Group1a: ID = g2, ParentID = g1
Group2: ID = g3, ParentID = null
Group1b: ID = g4, ParentID = g3
Group1bc: ID = g5, ParentID = g4
I'm trying to group them as:
|Group1
|--Group1a
|--Group1b
|--|
|--Group1bc
|Group2
Anyone fancy a stab at grouping them recursively?
No need to be recursive. To wit:
var lookup = items.ToDictionary(g => g.ID); // items is IEnumerable<IGroup>
foreach (var item in items.Where(g => g.ParentID != null)) {
lookup[item.ParentID].Groups.Add(item);
}
var parents = items.Where(g => g.ParentID == null);
Note that lookup[item.ParentID] will throw if there is no IGroup with the corresponding ParentID. You can handle this more gracefully with TryGetValue.
My implementation of IGroup:
public class Group : IGroup {
public string ID { get; set; }
public string Name { get; set; }
public string ParentID { get; set; }
public IList<IGroup> Groups { get; set; }
public Group() {
Groups = new List<IGroup>();
}
}
My test items:
IEnumerable<IGroup> items = new List<IGroup>() {
new Group() { ID = "g1", ParentID = null },
new Group() { ID = "g2", ParentID = "g1" },
new Group() { ID = "g3", ParentID = null },
new Group() { ID = "g4", ParentID = "g3" },
new Group() { ID = "g5", ParentID = "g4" },
new Group() { ID = "g6", ParentID = "g5" }
};
This is not recursive, but here's a solution (assuming you have all you groups in a list called groups)
var rootGroups = new List<IGroup>();
var dic = groups.ToDictionary(g => g.ID);
foreach (var g in groups)
{
if (g.ParentID == null)
{
rootGroups.Add(g);
}
else
{
IGroup parent;
if (dic.TryGetValue(g.ParentID, out parent))
{
parent.Groups.Add(g);
}
}
}
You could try ordering them by parent id, assuming that the parent group is always created before the child group.
Group by ParentID (Linq: GroupBy), order by ID.
Start with an empty root node (ID: null) and add all items with this ParentID. Recursively continue this process for any item that has been added.
As you extract each element from the database, you need to add it to its parent. So keep a Dictionary to help find the parent. If you get a child before its parent, then you can insert a placeholder until you get the real thing.
void BuildGroups()
{
foreach( IGroup x /* obtained from database or a collection or wherever */ )
AddGroup( x );
}
Dictionary<string,IGroup> _groups = new Dictionary<string,IGroup>;
string _parentGroupName = "PARENT";
void AddGroup( IGroup x )
{
// locate (or create) parent and add incoming group
IGroup parent;
string parentID = x.ParentID ?? _parentGroupName;
if( !groups.TryGetValue( parentID, out parent ) )
{
parent = new Group( parentID ); // SEE NOTE BELOW!
_groups[parentID] = parent;
}
parent.Groups.Add( x );
// locate (or insert) child, and make sure it's up to date
IGroup child;
if( groups.TryGetValue( x.ID, out child ) )
{
// We must have inserted this ID before, because we found one
// of ITS children first. If there are any other values besides
// ParentID and ID, then copy them from X to child here.
}
else
{
// first time we've seen this child ID -- insert it
_groups[x.ID] = x;
}
}
The dictionary element at _parentGroupName will then be a dummy node whose children are all of your top-level groups (i.e. the ones with NULL as ParentID from the database); from that element you can do a recursive traversal:
VisitGroups( _groups[_parentGroupName], "" );
void VisitGroups( string ID, string indent )
{
IGroup x;
if( _groups.TryGetValue( ID, out x ) )
{
foreach( IGroup y in x.Groups )
{
WriteLine( indent + " {0}", y.ID );
VisitGroups( y.ID, indent + " " );
}
}
}
NOTE: This implementation runs in a single inline pass through the data -- you can add elements immediately as they're retrieved from the database, and you only need to make a single pass through the data. That means you save some time and some memory. But in return, it requires that you be able to allocate an object with type IGroup() to act as a placeholder, in case a child is retrieved before its parent. You can only avoid that requirement if you know something about the order of the objects or if you process your dictionary in two passes, as is shown in the other answers.
I use a sentinel value, _parentGroupName, to keep the top-level nodes in the same collection as all the others. You can easily alter this to use a separate collection for the top level nodes instead if you prefer.
You could try this
public interface IGroup
{
string ID { get; set; }
string Name { get; set; }
string ParentID { get; set; }
List<IGroup> Groups { get; set; }
}
public class Group : IGroup
{
public string ID { get; set; }
public string Name { get; set; }
public string ParentID { get; set; }
public List<IGroup> Groups { get; set; }
public Group()
{
}
public Group(string id, string name, List<IGroup> childs)
{
ID = id;
Name = name;
Groups = (List<IGroup>)childs.Cast<IGroup>();
}
}
class Program
{
static void Main(string[] args)
{
List<IGroup> OriginalList;
List<IGroup> HirarchList = new List<IGroup>();
OriginalList = new List<IGroup>()
{
new Group() { ID = "g1", ParentID = null },
new Group() { ID = "g2", ParentID = "g1" },
new Group() { ID = "g3", ParentID = null },
new Group() { ID = "g4", ParentID = "g3" },
new Group() { ID = "g5", ParentID = "g4" },
new Group() { ID = "g6", ParentID = "g5" } };
HirarchList = GetCreateList(null, OriginalList);
}
public static List<IGroup> GetCreateList(string id, List<IGroup> list)
{
List<IGroup> temp = new List<IGroup>();
temp = (from item in list
where item.ParentID == id
select (IGroup)new Group(item.ID, item.Name,GetCreateList(item.ID, list))).ToList();
return (List<IGroup>)temp;
}
}