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);
}
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;
}
i have table looks like below
ID | Reason | PrID
-----------------
1 abc null
2 dhe null
3 aerc 1
4 dwes 2
5 adfje 1
i have class
public class Reason
{
public int Id { get; set; }
public string Reson{ get; set; }
public List<SecondryReason> SecReason{ get; set; }
public int? PrimaryId { get; set; }
}
public class SecondryReason
{
public int Id { get; set; }
public string Reason { get; set; }
public int PrimaryReasonId { get; set; }
}
I want this to be displayed in hierarchy level
if the prid is Null need to treat this as the parent remaining all child
i am trying Linq and unable to achieve this
Suggest me how to do this in an easy way in linq
So: You have a list/enumerable of type , whereof the SecReason List property is null. Then, using linq you want a list, were the only the "root" reasons remain, but the Sub-reasons got put in the lists, but as type SecondaryReason?
If so, I found this way to do it (linq and foreach):
static IEnumerable<Reason> GetReasonsGrouped(List<Reason> reasons)
{
var result = reasons.Where(x => x.PrimaryId == null);
foreach (var item in result)
{
item.SecReason = reasons.Where(x => x.PrimaryId == item.Id)
.Select(x => new SecondryReason()
{ Id = x.Id,
ReasonName = x.ReasonName,
PrimaryReasonId = item.Id
})
.ToList();
}
return result;
}
Or just linq, but harder to read:
var result = reasons.Where(x => x.PrimaryId == null)
.Select(x =>
{
x.SecReason = reasons.Where(r => x.PrimaryId == x.Id)
.Select(r => new SecondryReason()
{
Id = r.Id,
ReasonName = x.ReasonName,
PrimaryReasonId = x.Id
})
.ToList();
return x;
});
Not sure if linq will be the best solution, here is my proposed changes and method to get an Hierarchy type:
public class Reason
{
public int Id { get; set; }
public string Reson { get; set; }
public List<Reason> SecReason { get; set; }
public int? PrimaryId { get; set; }
//Adds child to this reason object or any of its children/grandchildren/... identified by primaryId
public bool addChild(int primaryId, Reason newChildNode)
{
if (Id.Equals(primaryId))
{
addChild(newChildNode);
return true;
}
else
{
if (SecReason != null)
{
foreach (Reason child in SecReason)
{
if (child.addChild(primaryId, newChildNode))
return true;
}
}
}
return false;
}
public void addChild(Reason child)
{
if (SecReason == null) SecReason = new List<Reason>();
SecReason.Add(child);
}
}
private List<Reason> GetReasonsHierarchy(List<Reason> reasons)
{
List<Reason> reasonsHierarchy = new List<Reason>();
foreach (Reason r in reasons)
{
bool parentFound = false;
if (r.PrimaryId != null)
{
foreach (Reason parent in reasonsHierarchy)
{
parentFound = parent.addChild(r.PrimaryId.Value, r);
if (parentFound) break;
}
}
if (!parentFound) reasonsHierarchy.Add(r);
}
return reasonsHierarchy;
}
I have a class called Section
public class Section
{
public Section() { construct(0); }
public Section(int order) { construct(order); }
private void construct(int order)
{
Children = new List<Section>();
Fields = new List<XfaField>();
Hint = new Hint();
Order = order;
}
[Key]
public int Id { get; set; }
public int FormId { get; set; }
public string Name { get; set; }
[InverseProperty("Parent")]
public List<Section> Children { get; set; }
public List<XfaField> Fields { get; set; }
public Section Parent { get; set; }
public Hint Hint { get; set; }
public int Order { get; private set; }
#region Methods
public void AddNewChild()
{
AddChild(new Section
{
Name = "New Child Section",
FormId = FormId,
});
}
private void AddChild(Section child)
{
child.Parent = this;
if (Children == null) Children = new List<Section>();
int maxOrder = -1;
if(Children.Count() > 0) maxOrder = Children.Max(x => x.Order);
child.Order = ++maxOrder;
Children.Add(child);
FactoryTools.Factory.PdfSections.Add(child);
}
// Other methods here
#endregion
}
I am trying to add a new child Section to an already existing parent like this:
private void AddChildSection()
{
var parent = FactoryTools.Factory.PdfSections.FirstOrDefault(x => x.Id == ParentId);
if (parent == null) throw new Exception("Unable to create child because parent with Id " + ParentId.ToString() + " doesn't exist.");
parent.AddNewChild();
FactoryTools.Factory.SaveChanges();
}
When I look at the database, I see that a new row has been added, so for example:
Id Name Parent_Id Hint_Id FormId Order
19 New Child Section 1 27 1 0
However, when I load the parent Section, the Children property is always of Count 0, like this:
public ActionResult EditSection(int formId, int sectionId)
{
var model = FactoryTools.Factory.PdfSections.FirstOrDefault(x => x.Id == sectionId);
if (model == null || model.FormId != formId) model = new Section();
//model.Children = FactoryTools.Factory.PdfSections.Where(x => x.Parent.Id == sectionId).ToList();
return PartialView(model);
}
Of course, when I manually add the children, then they are there (in the above code, by uncommenting the model.Children = ... line)
I am used to the NHibernate way of doing things and am therefore quite frustrated that the above, seemingly simple, task is not working in EntityFramework, what am I doing wrong?
Entity Framework won't eagerly load related entities. Try forcing it to include the children:
var model = FactoryTools.Factory.PdfSections.Include("Children").FirstOrDefault(x => x.Id == sectionId);
There's also a strongly-typed overload to which you can pass a lambda:
var model = FactoryTools.Factory.PdfSections.Include(s => s.Children).FirstOrDefault(x => x.Id == sectionId);
I have a collection of strings:
"Alberton;Johannesburg"
"Allendale;Phoenix"
"Brackenhurst;Alberton"
"Cape Town;"
"Durban;"
"Johannesburg;"
"Mayville;Durban"
"Phoenix;Durban"
"Sandton;Johannesburg"
that I want to structure into a hierarchical structure in the fastest possible manner, like:
Johannesburg
Alberton
Brackenhurst
Sandton
Cape Town
Durban
Phoenix
Allandale
Mayville
Currently I have nested for loops and checks, but was hoping I could achieve this with a single LAMBDA query?
The above mentioned strings are in a List.
I prepared lambda-like solution, but you should really think if it's more readable/efficient then your current one:
Helper Extension Method:
public static class ChildrenGroupExtensions
{
public static List<CityInfo> GetChildren(this IEnumerable<IGrouping<string, City>> source, string parentName)
{
var cities = source.SingleOrDefault(g => g.Key == parentName);
if (cities == null)
return new List<CityInfo>();
return cities.Select(c => new CityInfo { Name = c.Name, Children = source.GetChildren(c.Name) }).ToList();
}
}
Helper Classes:
public class City
{
public string Name { get; set; }
public string Parent { get; set; }
}
public class CityInfo
{
public string Name { get; set; }
public List<CityInfo> Children { get; set; }
}
Usage:
var groups = (from i in items
let s = i.Split(new[] { ';' })
select new City { Name = s[0], Parent = s[1] }).GroupBy(e => e.Parent);
var root = groups.GetChildren(string.Empty);
Where items is your List<string>
You can look the results with simple helper method like that one:
private static void PrintTree(List<CityInfo> source, int level)
{
if (source != null)
{
source.ForEach(c =>
{
Enumerable.Range(1, level).ToList().ForEach(i => Console.Write("\t"));
Console.WriteLine(c.Name);
PrintTree(c.Children, level + 1);
});
}
}
And the results are:
Cape Town
Durban
Mayville
Phoenix
Allendale
Johannesburg
Alberton
Brackenhurst
Sandton
You haven't specified any specific data structure so I just used a class called Area with a list of children of itself. Also, it's in 2 lines of linq. There is also no check to see if an area is a child of 2 separate parents as the code is. Here's the code for the test I used(Relevant lines in-between the equals comments):
[TestFixture]
public class CitiesTest
{
[Test]
public void Test()
{
var strings = new List<string>
{
"Alberton;Johannesburg",
"Allendale;Phoenix",
"Brackenhurst;Alberton",
"Cape Town;",
"Durban;",
"Johannesburg;",
"Mayville;Durban",
"Phoenix;Durban",
"Sandton;Johannesburg"
};
//===================================================
var allAreas = strings.SelectMany(x=>x.Split(';')).Where(x=>!string.IsNullOrWhiteSpace(x)).Distinct().ToDictionary(x=>x, x=>new Area{Name = x});
strings.ForEach(area =>
{
var areas = area.Split(';');
if (string.IsNullOrWhiteSpace(areas[1]))
return;
var childArea = allAreas[areas[0]];
if (!allAreas[areas[1]].Children.Contains(childArea))
allAreas[areas[1]].Children.Add(childArea);
childArea.IsParent = false;
});
var result = allAreas.Select(x=>x.Value).Where(x => x.IsParent);
//===================================================
}
public class Area
{
public string Name;
public bool IsParent;
public List<Area> Children { get; set; }
public Area()
{
Children = new List<Area>();
IsParent = true;
}
}
}