I have a class called NavigationElement that looks like this
public class NavigationElement
{
public int Id { get; set; }
public string Title { get; set; }
public string Link { get; set; }
public int SortOrder { get; set; }
public bool Visible { get; set; }
public int? ParentId { get; set; }
public virtual ICollection<NavigationElement> Children { get; set; }
public virtual NavigationElement Parent { get; set; }
public NavigationElement()
{
Children = new List<NavigationElement>();
}
}
As you can see, the class is self referencing. From that, I am creating a site navigation menu with drop downs (hierarchy in play).
I am struggling in the ordering of the items. I want the top-level items to be ordered by the SortOrder property, but everything underneath, I would like ordered alphabetically by the Title property.
Here is why I have done so far.
var orderedModel = unorderedModel.OrderBy(x => x.SortOrder).ThenBy(x => x.Children.OrderBy(y => y.Title).ThenBy(z => z.Children.OrderBy(a => a.Title))).ToList();
unorderedModel is of type List<NavigationElementModel>.
This is compiling, but I get an error when I run the code. The error says:
At least one object must implement IComparable.
You should just go recursive through all children elements and sort it.
Somehting like:
var ordered = unorderedModel.OrderBy(x=>x.SortOrder).ToList();
ordered.ForEach(OrderChildren);
public void OrderChildren(NavigationElement el)
{
el.Children = el.Children.OrderBy(x => x.Title).ToList();
if (el.Children != null)
{
foreach (var c in el.Children)
{
OrderChildren(c);
}
}
}
What about something like this?
public static class LinqExtension {
public static IEnumerable<T> SelectManyRecursive<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> childrenSelector) {
if (source == null) throw new ArgumentNullException("source");
foreach (var i in source) {
yield return i;
var children = childrenSelector(i);
if (children == null) continue;
foreach (var child in SelectManyRecursive(children, childrenSelector)) {
yield return child;
}
}
}
}
var orderedModel = unorderedModel
.OrderBy(x => x.SortOrder)
.SelectMany(x => new[] { x }.Union(
x.Children.SelectManyRecursive(y => y.Children)
.OrderBy(y => y.Parent.Title) // considering hierarchy
.ThenBy(y => y.Title)
))
.ToList();
I will utilize the approach from Sort hierarchy with path and depth fields using Linq for your case.
First, the general tree traversal helper from my answer to How to flatten tree via LINQ?:
public static partial class TreeUtils
{
public static IEnumerable<T> Expand<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> elementSelector)
{
var stack = new Stack<IEnumerator<T>>();
var e = source.GetEnumerator();
try
{
while (true)
{
while (e.MoveNext())
{
var item = e.Current;
yield return item;
var elements = elementSelector(item);
if (elements == null) continue;
stack.Push(e);
e = elements.GetEnumerator();
}
if (stack.Count == 0) break;
e.Dispose();
e = stack.Pop();
}
}
finally
{
e.Dispose();
while (stack.Count != 0) stack.Pop().Dispose();
}
}
}
And the solution of your particular problem:
var orderedModel = unorderedModel.Where(item => item.Parent == null).OrderBy(item => item.SortOrder)
.Expand(item => item.Children != null && item.Children.Any() ? item.Children.OrderBy(child => child.Title) : null)
.ToList();
Related
I'm trying to order List that should look like this
Parent
Child1 (simultaneously children and parent)
Child2 (Children of Child1)
Child3
In using Class that contain information's about ID, ParentID and etc.
I'm trying to make this work using LINQ and tried different solution but no one work completely, I know that with recursively function will work (but really don't like that), can someone help me to make working with LINQ ?
i tried this code but Child2 don't appearing.
List<Person> orderedList = new List<Person>();
persons.ForEach(x => {
if (x.ParentID == 0) {
orderedList.Add(x);
orderedList.AddRange(persons.Where(child => child.ParentID == x.Id));
}
});
For those who are voting "negative" remember no one was god at programming at the beginning, if i come here that means that I'm struggle to fix the problem for x hours. And also if you think that my English is bad i know that already, I'm not born to speak English perfectly but those who wants to help will help. :)
Whole Code
public class Person{
public int Id { get; set; }
public string MenuName { get; set; }
public int? ParentID { get; set; }
public string isHidden { get; set; }
public string LinkURL { get; set; }
}
public static List<Person> AddPersons(){
var persons = new List<Person>();
using (var reader = new StreamReader(#"C:\Users\AceDuk\Desktop\Navigation.csv")){
var line = reader.ReadLine(); //Da se izbegne headerot
while ((line = reader.ReadLine()) != null){
var values = line.Split(';');
if (values[2] == "NULL") {
values[2] = "0";
}
persons.Add(new Person(){
Id = Int32.Parse(values[0]),
MenuName = values[1],
ParentID = Int32.Parse(values[2]),
isHidden = values[3],
LinkURL = values[4]
});
}
}
persons.RemoveAll(x => x.isHidden == "True"); //Izbrisi gi site sto se hidden ne gi pokazuvaj..
//persons = persons.OrderBy(x => x.MenuName).ToList(); //Ordered
persons = persons.OrderBy(x => x.LinkURL).ToList(); //Ordered
return persons;
}
static void Main(string[] args){
List<Person> persons = AddPersons();
List<Person> orderedList = new List<Person>();
persons.ForEach(x => {
if (x.ParentID == 0) {
orderedList.Add(x);
orderedList.AddRange(persons.Where(child => child.ParentID == x.Id));
}
});
foreach (var item in orderedList) {
Console.WriteLine(item.MenuName);
}
}
Create a double-ended queue (deque) data structure by extending a linked list:
public class Deque<T> : LinkedList<T> {
public void Enqueue(T item) => AddLast(item);
public T Dequeue() {
var item = First.Value;
RemoveFirst();
return item;
}
public void EnqueueRange(IEnumerable<T> items) {
foreach (var item in items)
Enqueue(item);
}
public void Push(T item) => AddFirst(item);
public T Pop() => Dequeue();
public void PushRange(IEnumerable<T> items) {
foreach (var item in items)
Push(item);
}
public T Peek() => Last.Value;
}
Now, create a mapping from Id to children using ToLookup:
var childrenDictionary = persons.Where(p => p.ParentID != 0).ToLookup(p => p.ParentID);
Finally, use the deque to create a working list and add all the root nodes:
var workDeque = new Deque<Person>();
workDeque.EnqueueRange(persons.Where(p => p.ParentID == 0));
Now you can go through the workDeque, adding each root node to the orderedPersons and then pushing the children of the node onto workDeque to be worked next:
var orderedPersons = new List<Person>();
while (workDeque.Count > 0) {
var nextPerson = workDeque.Dequeue();
orderedPersons.Add(nextPerson);
workDeque.PushRange(childrenDictionary[nextPerson.Id]);
}
How can I find class from nested list ?
I am working on trees and just want to retreive and add child based on id.
Class
public class d3_mitch
{
public int id { get; set; }
public string name { get; set; }
public string type { get; set; }
public string description { get; set; }
public List<d3_mitch> children { get; set; }
}
Object Creation and Query
d3_mitch t = new d3_mitch();
t.id = 1;
t.type = "Root";
t.name = "Animal";
t.description = "A living organism that feeds on organic matter";
t.children = new List<d3_mitch>() {
new d3_mitch() { name = "Carnivores", type = "Type", id = 2, description = "Diet consists solely of animal materials",
children=new List<d3_mitch>(){ new d3_mitch() { id= 3 ,name="Felidae",type="Family",description="Also known as cats"} }
}
};
d3_mitch child = t.children.Where(x => x.id == 3).FirstOrDefault();
//This return null because no direct child has has id = 3 but nested
You need to use recursion. Try next code
d3_mitch FindById(d3_mitch root, int id)
{
if (root.id == id)
return root;
foreach (var child in root.children)
{
if (child.id == id)
return child;
var subTreeResult = FindById(child, id);
if (subTreeResult != null)
return subTreeResult;
}
// no such item
return null;
}
Use SelectMany
t.children.SelectMany(s => s.children)
.FirstOrDefault(s => s.children.Any(d => d.id == 3));
Using a recursive method will resolve your problem.
public static d3_mitch Find(d3_mitch main, int id)
{
if (main.id == id)
return main;
if (main.id != id && main.children != null)
{
foreach (var child in main.children)
{
return child.children.Any(x=>x.id==id)? child.children.First(x=>x.id==id) : Find(child, id);
}
}
return null;
}
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'm having a problem with my recursive method. This method is not behaving as i wish it to behave.
What i am trying to do is to is to get a sorted list of all the categories i have in my database, those lists contains a list of childs named ChildList, and i am trying to make a method that would recursively add right child to the rightful parent.
On 3 levels down it behaves exactly as i want, it adds a Child to the childlist of CategoryViewModel. after that, it tends to make duplicate childs which i dont want it to.
For example, you have
Root - Root has Electronics as child,
Electronics has Telephones as child,
Telephone has Mobilephones, here is where my recursive method duplicate the child, making 2 Mobilephones, and if i have a category Iphone under Mobilephones it would make 3 Iphone-categorychildrens to each Mobilephone. The Iphone categories doesent have any children and the list is 0. but i bet if it would have, each iphone would have 4 childs of that category.
here is my code
namespace GUI.Controllers
{
public class HomeController : Controller
{
private readonly IRepository<Category> _repo;
private static List<CategoryViewModel> _sources;
public HomeController(IRepository<Category> repo)
{
_repo = repo;
}
public HomeController()
: this(new Repository<Category>())
{
}
public ViewResult Index()
{
var items = _repo.GetAll().ToList();
var sortedList = new CategoryViewModel();
_sources = items.Select(c => new CategoryViewModel
{
Id = c.Id,
Name = c.Name,
Parent = c.Parent.HasValue ? c.Parent.Value : (Guid?) null,
Products = c.Product.ToList(),
ChildList = new List<CategoryViewModel>()
}).ToList();
_sources = _sources.OrderBy(o => o.Parent).ToList();
var root = _sources.First();
sortedList.Id = root.Id;
sortedList.Name = root.Name;
sortedList.Parent = null;
sortedList.ChildList = _sources.Where(o => o.Parent != null && o.Parent == root.Id).ToList();
foreach (var l in _sources)
{
if(l.Parent == null)
continue;
l.ChildList = _sources.Where(o => o.Parent != null && o.Parent == l.Id).ToList();
if (l.ChildList.Any())
AddItem(l.ChildList, ref sortedList);
}
return View(sortedList);
}
private static void AddItem(List<CategoryViewModel> children, ref CategoryViewModel sortedList)
{
foreach (var child in children)
{
var childs = _sources.Where(o => o.Parent != null && o.Parent == child.Id).ToList();
foreach (var c in childs)
{
child.ChildList.Add(c);
}
if (child.ChildList.Any())
AddItem(child.ChildList, ref sortedList);
}
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using ClassLibrary.Entities;
namespace GUI.Models
{
public class CategoryViewModel
{
[Required]
public string Name { get; set; }
[Required]
public string SearchString { get; set; }
public Guid Id { get; set; }
public Guid? Parent { get; set; }
public string ParentName { get; set; }
public List<Product> Products { get; set; }
public List<CategoryViewModel> ChildList { get; set; }
}
}
Ok i found out what the solution to my problem is;
My recursive method was overflow of code, i went too deep without understanding how to practicly use the recursive method. as they say; one thing led to another and i eventually deleted my AddItem Method, it turns out that my lambda expression sorted out the lists for me in the right collection of lists; the final code looks alot more clean and is alot smaller now;
public ViewResult Index()
{
var items = _repo.GetAll().ToList();
var categories = items.Select(c => new CategoryViewModel
{
Id = c.Id,
Name = c.Name,
Parent = c.Parent.HasValue ? c.Parent.Value : (Guid?) null,
Products = c.Product.ToList(),
ChildList = new List<CategoryViewModel>()
}).OrderBy(o => o.Parent).ToList();
var sortedCategories = categories.First();
sortedCategories.ChildList = categories.Where(o => o.Parent != null && o.Parent == sortedCategories.Id).ToList();
foreach (var l in categories.Where(l => l.Parent != null))
{
l.ChildList = categories.Where(o => o.Parent != null && o.Parent == l.Id).ToList();
}
return View(sortedCategories);
}
What is the best fastest way to Synchronize 2 Lists?
public class UserGroup
{
public UserGroup(string group, string user)
{
this.Group = group;
this.User = user;
}
public string Group { get; set; }
public string User { get; set; }
}
IList<UserGroup> userGroup1 = new IList<UserGroup>();
IList<UserGroup> userGroup2 = new IList<UserGroup>();
Each group has different number of members.
How can i find out the different and merge both in one new list?
PS: I can change the type from IList to whatever if it would be more efficient.
Thanks
So first we need an effective way of comparing these objects. Since the default Equals and GetHashCode implementations won't be useful in your context you either need to override them, or create an IEqualityComparer. I did the latter, you can feel free to do the former if you want. Here's a simple comparer:
public class UserGroupComparer : IEqualityComparer<UserGroup>
{
public bool Equals(UserGroup x, UserGroup y)
{
return x.Group == y.Group && x.User == y.User;
}
public int GetHashCode(UserGroup obj)
{
return 37 * obj.Group.GetHashCode() + 19 * obj.User.GetHashCode();
}
}
Now that you have this comparer you can leverage LINQ to do the work for you:
var combinedList = userGroup1.Union(userGroup2, new UserGroupComparer())
.ToList();
That will have all of the user groups that are in either list, but without any duplicates.
You can try:
userGroup1.Concat(userGroup2).Distinct();
And don't forget to override Equals and GetHashCode for UserGroup class.
The following could be used if the items in collections are of two different types:
class CollectionSynchronizer<TSource, TDestination>
{
public Func<TSource, TDestination, bool> CompareFunc { get; set; }
public Action<TDestination> RemoveAction { get; set; }
public Action<TSource> AddAction { get; set; }
public Action<TSource, TDestination> UpdateAction { get; set; }
public void Synchronizer(ICollection<TSource> sourceItems, ICollection<TDestination> destinationItems)
{
// Remove items not in source from destination
RemoveItems(sourceItems, destinationItems);
// Add items in source to destination
AddOrUpdateItems(sourceItems, destinationItems);
}
private void RemoveItems(ICollection<TSource> sourceCollection, ICollection<TDestination> destinationCollection)
{
foreach (var destinationItem in destinationCollection.ToArray())
{
var sourceItem = sourceCollection.FirstOrDefault(item => CompareFunc(item, destinationItem));
if (sourceItem == null)
{
RemoveAction(destinationItem);
}
}
}
private void AddOrUpdateItems(ICollection<TSource> sourceCollection, ICollection<TDestination> destinationCollection)
{
var destinationList = destinationCollection.ToList();
foreach (var sourceItem in sourceCollection)
{
var destinationItem = destinationList.FirstOrDefault(item => CompareFunc(sourceItem, item));
if (destinationItem == null)
{
AddAction(sourceItem);
}
else
{
UpdateAction(sourceItem, destinationItem);
}
}
}
}
And the usage would be like this:
var collectionSynchronizer = new CollectionSynchronizer<string, ContentImageEntity>
{
CompareFunc = (communityImage, contentImage) => communityImage == contentImage.Name,
AddAction = sourceItem =>
{
var contentEntityImage = _contentImageProvider.Create(sourceItem);
contentEntityImages.Add(contentEntityImage);
},
UpdateAction = (communityImage, contentImage) =>
{
_contentImageProvider.Update(contentImage);
},
RemoveAction = contentImage =>
{
contentEntityImages.Remove(contentImage);
}
};
collectionSynchronizer.Synchronizer(externalContentImages, contentEntityImages);
See the answer to this question: Create a list from two object lists with linq
Basically you can use this in System.Linq:
userGroup1.Union(userGroup2).ToList();
You may use HashSet see following link class http://msdn.microsoft.com/en-us/library/bb383091.aspx