Custom Sorting in .NET - c#

I have designed a Class for Parent Child relationship
class Category
{
public string CatName;
public string CatId;
public IList<Category> childCategory = new List<Category>();
public void addChildCat(Category childCat)
{
this.childCategory.Add(childCat);
}
public Category SortedCategory(Category cat)
{
// Should return the sorted cat i.e topmost parent
}
}
Here by Parent will not have Catname or CatId, it will have Child Categories which has CatName, CatId as well as another Child Category List and it goes till "N" categories
Here I need to get the Top Parent with all the child categories sorted by CatName. Any ideas How this can be achieved?
Is my class design GOOD?

You can't because you have not a reference to the parent. You have to add a field:
public Category Parent { get; set; }
and modify the add method to set the parent:
public void addChildCat(Category childCat)
{
childCat.Parent = this;
this.childCategory.Add(childCat);
}
You need the parent to get the root:
public static Category SortedCategory(Category cat)
{
// get the root
var root = cat;
while(root.Parent != null) root = root.Parent;
return root.GetSorted();
}
private Category GetSorted()
{
var sortedChildren = new List<Category>(childCategories).ConvertAll(c => c.GetSorted());
sortedChildren.Sort((c1, c2) => c1.CatName.CompareTo(c2.Catname));
return new Category { CatName = root.CatName,
Catid = root.CatId,
childCategories = sortedChildren; }
}

You can use the SortedList to keep track of the child categories instead.

If I understand this, we have a tree structure right? And what is the result you are expecting, the sorted children of the topmost parent (root)?

Instead of :
public string CatName;
public string CatId;
I would do:
class Cat
{
public string Name { get; set; }
public string Id { get; set; }
}
And instead of:
public Category SortedCategory(Category cat)
{
// Should return the sorted cat i.e topmost parent
}
I would do:
var category = new List<Cat>
{
new Cat() {Name = "cat1", Id = "123"},
new Cat() {Name = "cat2", Id = "124"},
new Cat() {Name = "cat3", Id = "125"},
new Cat() {Name = "cat4", Id = "126"}
};
category.Sort(( cat1, cat2) => ((Convert.ToInt32(cat1.Id) > Convert.ToInt32(cat2.Id)) ? 1 : 0) );

Related

C# Recursively loop over object and return all children [duplicate]

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;
}
}
}

Multilevel parent child relation sorting using Linq c#

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;
}
}

LINQ select items in a list that are all in a list of another type

I have a many to many relation created using Entity Framework.
public class Animal
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AnimalID { get; set; }
[MaxLength(50)]
public string AnimalName { get; set; }
public virtual ICollection<Food> FoodList { get; set; }
}
public class Den
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int DenID { get; set; }
[MaxLength(50)]
public string DenName { get; set; }
public virtual ICollection<Food> FoodList { get; set; }
}
Both Animal and Den contain virtual lists of type food.
public class Food
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int FoodID { get; set; }
[MaxLength(50)]
public string FoodName { get; set; }
public virtual ICollection<Animal> AnimalList { get; set; }
public virtual ICollection<Den> DenList { get; set; }
}
I have a method which passes in a DenID and I need to return a list of animals that have ALL the foods in their Animal.FoodList that the Den has in it's Den.FoodList.
example if the Den.FoodList contains Meat and Veg then I want a list of Animals that have Meat and Veg in their foodList.
Edit
I have attempted a few things so far.
First of all I have this in my ViewModel
denFoodList = new List<Food>();
//Check if myDen requires any Food.
denFoodList = MyDen.FoodList.ToList();
I tried looping through each member of the DenFoodList and adding Animals to an Animal list and then gathering that any animal in the list x number of times (where x is the FoodList.Count() ) was the animals I needed, however this method was slow and messy.
I tried using the All method with the animals list and the DenList but I couldn't get it to work.
animalList = context.Animals.Where(a => a.FoodList.All(f => f.DenList));
I've been looking into using Joins and intersects but I have not been successful in using them to solve this problem yet.
End of Edit
Any help is appreciated.
Thanks.
Let's try this
class MyContext : DbContext {}
// ...
using (MyContext context = new MyContext())
{
var den = context.Den.Find(DenId);
// Inner join Linq
var foodList = from a in context.Animals
from b in a.FoodList
join c in d.FoodList on c.FoodId equals b.FoodId
select c;
}
Untested:
class Test
{
private static IEnumerable<Den> Dens()
{
var dens = new List<Den>
{
new Den
{
DenID = 1,
DenName = "GamePark",
FoodList = new Collection<Food>()
{
new Food
{
FoodID = 1,
FoodName = "Veg",
AnimalList = new Collection<Animal>
{
new Animal
{
AnimalID = 234,
AnimalName = "Zebra",
FoodList = new Collection<Food>{new Food {FoodID = 1, FoodName = "Veg"} }
},
new Animal
{
AnimalID = 125,
AnimalName = "Buffalo",
FoodList = new Collection<Food>{new Food {FoodID = 1, FoodName = "Veg"} }
}
}
},
new Food
{
FoodID = 2,
FoodName = "Meat",
AnimalList = new Collection<Animal>
{
new Animal
{
AnimalID = 003,
AnimalName = "Leopard",
FoodList = new Collection<Food>{new Food {FoodID = 2, FoodName = "Meat"} }
},
new Animal
{
AnimalID = 001,
AnimalName = "Lion",
FoodList = new Collection<Food>{new Food {FoodID = 2, FoodName = "Meat"} }
}
}
}
}
}
};
return dens;
}
public static IEnumerable<Animal> GetAnimalsWithFoodsInDen(int denId)
{
var den = Dens().FirstOrDefault(x => x.DenID == denId);
var animals = new List<Animal>();
if (den != null)
{
var foods = den.FoodList;
if (foods != null)
{
animals = foods.ToList().Aggregate(animals, (current, food) => current.Union(food.AnimalList).ToList());
}
}
return animals;
}
static void Main(string[] args)
{
var result = GetAnimalsWithFoodsInDen(1);
foreach (var a in result)
{
Console.WriteLine(a.AnimalName);
}
Console.ReadLine();
}
}
First get the list of food, then get the animals:
Den d = SomeDen();
var food = d.FoodList;
var animals = new List<Animal>();
foreach(var f in food) foreach(var a in f.AnimalList) if(!animals.Contains(a)) animals.Add(a);
Maybe you'd like a dictionary instead of List for performance, depending on your data.
Or perhaps you're looking for something like this?
Dan d = SomeDen();
var food = d.FoodList;
var animals = from a in DB.Animals
where a.FoodList.Any((f)=>food.Contains(f))
select a;
The latter should be your intuitive idea, but it's going to be quite slow.

C# - Recursive Grouping

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;
}
}

Linq query to return a flatened list of parent child

still new to the world of linq, and i need some help flatening a list of parents that have children, into a single list of ParentChild's.
Just like this:
class Program
{
static void Main()
{
List<Parent> parents = new List<Parent>();
parents.Add(new Parent { Name = "Parent1", Children = new List<Child> { new Child { Name = "Child1" }, new Child { Name = "Child2" } } });
parents.Add(new Parent { Name = "Parent2", Children = new List<Child> { new Child { Name = "Child3" }, new Child { Name = "Child4" } } });
// linq query to return List<ParentChild> parentChildList;
// ParentName = Parent1, ChildName = Child1
// ParentName = Parent1, ChildName = Child2
// ParentName = Parent2, ChildName = Child3
// ParentName = Parent2, ChildName = Child4
}
internal class ParentChild
{
public string ParentName { get; set; }
public string ChildName { get; set; }
}
internal class Parent
{
public string Name { get; set; }
public List<Child> Children { get; set; }
}
internal class Child
{
public string Name { get; set; }
}
}
Many thanks,
Chris
from parent in parents
from child in parent.Children
select new ParentChild() { ParentName = parent.Name, ChildName = child.Name };
This should do it for you:
var k = from p in parents
from c in p.Children
select new {Name = p.Name, Child = c.Name };
EDIT: Opps forgot to return a new ParentChild object. but Kent beat me to it ;)

Categories