How to search Hierarchical Data with Linq in list - c#

Distributor Registration.
I want to fill list with following information about the distributor
Id
ParentId
Name
For each distributor, it must be determined on whose recommendation the distributor is registered in the system. This can be anyone already registered in the system - in which case they must be selected from a list of already registered distributors, or the referrer information can be blank, which means that the distributor registers in the system without a recommender. Each distributor can bring up to three people on its recommendation, the system should ensure that no more than three people are registered "under" the same distributor. Also, the depth of the hierarchy of the mentioned distributors should be maximum 5 - that is, the distributor can bring a person with a recommendation, who will also bring a person with his recommendation, etc. Maximum 5 levels. Accordingly, in such a group there can be a total of 1 + 3 + 9 + 27 + 81 = 121 people. The system must provide control over the given levels.

You can use recursion to find the depth of any element in the list and a plain old count to find the number of referrers.
The following code is an implementation of that idea.
void Main()
{
var distributors = new List<Distributor> {
new Distributor { Id = 1, ParentId = 0, Name = "A" },
new Distributor { Id = 7, ParentId = 1, Name = "B" },
new Distributor { Id = 9, ParentId = 7, Name = "C" },
new Distributor { Id = 13, ParentId = 9, Name = "D" },
new Distributor { Id = 28, ParentId = 13, Name = "E" },
};
var valid = IsValidToAdd(distributors, 9);
Console.WriteLine(valid);
}
public bool IsValidToAdd(List<Distributor> distributors, int parentId)
{
var referCount = distributors.Count(d => d.ParentId == parentId);
Console.WriteLine($"refer:{referCount}");
if (referCount == 3)
{
Console.WriteLine("There are already 3 referals for this parent");
return false;
}
var level = GetLevel(distributors, parentId);
Console.WriteLine($"level: {level}");
if (level > 5)
{
Console.WriteLine("There are already 5 levels of referals");
return false;
}
return true;
}
public int GetLevel(List<Distributor> distributors, int parentId)
{
var parent = distributors.FirstOrDefault(d => d.Id == parentId);
if (parent == null)
{
return 1;
}
return 1 + GetLevel(distributors, parent.ParentId);
}
public class Distributor
{
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}

Related

List of descendant ID - children, grandchildren etc

CREATE TABLE [dbo].[Topic] (
[Id] SMALLINT NOT NULL,
[ParentId] SMALLINT NULL
);
I have a simple table (above) with a parent/child hierarchy. I'm using Entity Framework to extract the data. The number of rows is less than 100.
I want to get a list of descendant ID, consisting of the children, grandchildren and so on (possibly with the option of including the original root parent). As the table only contains a small number of rows, it's probably easier to extract all the data to a List<Topic> first and work on that, but I stand to be corrected.
The preferred output would be: Dictionary<int, List<int>>
Where the key would be the ID and the list would contain child/grandchild ID's
I've looked at tens and tens of solutions online but I can't find a solution that meets my needs. Can anyone help?
You could populate a dictionary with the ParentId->Id relations and use that to resolve sub-topics:
// prepare dictionary
var dictionary = new Dictionary<short, List<Topic>>();
// in real life this would get replaced by your database query
var topics = new List<Topic>
{
new Topic { Id = 1 },
new Topic { Id = 2, ParentId = 1 },
new Topic { Id = 3, ParentId = 1 },
new Topic { Id = 4, ParentId = 1 },
new Topic { Id = 5, ParentId = 1 },
new Topic { Id = 6, ParentId = 2 },
new Topic { Id = 7, ParentId = 2 },
new Topic { Id = 8, ParentId = 3 },
new Topic { Id = 9, ParentId = 4 },
new Topic { Id = 10, ParentId = 4 },
new Topic { Id = 11, ParentId = 8 },
new Topic { Id = 12, ParentId = 8 }
};
// populate dictionary with relations from DB
foreach(var topic in topics)
{
var key = topic.ParentId ?? -1;
if(!dictionary.ContainsKey(key))
{
dictionary.Add(key, new List<Topic>());
}
dictionary[key].Add(topic);
}
Now that you have the mappings available, you can write a simple recursive iterator method to resolve the descendants of a given id:
IEnumerable<short> GetDescendantIDs(short from)
{
if(dictionary.ContainsKey(from))
{
foreach(var topic in dictionary[from])
{
yield return topic.Id;
foreach(var child in GetDescendants(topic.Id))
yield return child;
}
}
}
// resolves to [1, 2, 6, 7, 3, 8, 11, 12, 4, 9, 10, 5]
var descendantsOfRoot = GetDescendantIDs(-1);
// resolves to [8, 11, 12]
var descendantsOfThree = GetDescendantIDs(3);
The Topic class used in the example above is just:
class Topic
{
internal short Id { get; set; }
internal short? ParentId { get; set; }
}
Consider that result has to be stored in tree:
public class TopicModel
{
public int Id { get; set; }
public int? ParentId{ get; set; }
public List<TopicModel> Children { get; set; };
}
Building tree:
// retrieveing from database
var plainResult = context.Topic
.Select(t => new TopicModel
{
Id = x.Id
ParentId = x.ParentId
})
.ToList();
var lookup = plainResult.Where(x => x.ParentId != null).ToLookup(x => x.ParentId);
foreach (c in plainResult)
{
if (lookup.Contains(c.Id))
{
c.Children = lookup[c.Id].ToList();
}
}
// here we have all root items with children intialized
var rootItems = plainResult.Where(x => x.ParentId == null).ToList();
And searching for children:
public static IEnumerable<TopicModel> GetAllChildren(TopicModel model)
{
if (model.Children != null)
{
foreach (var c in model.Children)
{
yield return c;
foreach (sc in GetAllChildren(c))
yield return sc;
}
}
}

Generating a directory folder structure from database

Finding myself in loopie loops. I have a database table that defines a directory folder structure with potentially infinite number of subfolders.
The end result folder structure should look like this, but the logic should allow this structure requirement to change:
Given this data for the above folder structure:
The most important fields are id and pid. pid of NULL represents the top level folders (Email, TM Applications, TM Disputes). All other folders are subfolders which go down 3 levels stored in level_count field. Not sure I really need level_count field though. I've been trying to make the logic as "flexible" as possible. pid defines a folder's immediate parent:
My current solution is not good enough because it doesn't handle the "infinite" number of levels, it only supports three levels. I prefer not to have to know the number of levels.
I want to be able to keep the core logic if possible and I do not want to change this in a way that creates all parent folders first, and then goes back to create subfolders. Instead I want to go down the deepest level, create those folders, then back up to parents. I think this code represents that idea, if I'm making sense.
foreach (DataRow r in dtParentFolders.Rows) // these are the 3 parent rows with null pid
{
int parentFolderId = Convert.ToInt32(r["id"]);
string parentFolderName = r["folder_name"].ToString();
//Create folder
Console.WriteLine(parentFolderName);
DataTable dt = GetFolders(parentFolderId);
foreach (DataRow r2 in dt.Rows)
{
parentFolderId = Convert.ToInt32(r2["id"]);
CreateFolder(r2);
dt = GetFolders(parentFolderId);
foreach (DataRow r3 in dt.Rows)
{
parentFolderId = Convert.ToInt32(r3["id"]);
CreateFolder(r3);
dt = GetFolders(parentFolderId);
}
}
}
I hope this can help you in some way.
public class Record
{
public int Id { get; set; }
public int PId { get; set; }
public string Name { get; set; }
}
public static void Main()
{
var records = new List<Record>()
{
new Record { Id = 1, Name = "MainDir1", PId = 0 },
new Record { Id = 2, Name = "MainDir2", PId = 0 },
new Record { Id = 3, Name = "MainDir3", PId = 0 },
new Record { Id = 4, Name = "SubDir1", PId = 1 },
new Record { Id = 5, Name = "SubDir2", PId = 2 },
new Record { Id = 6, Name = "SubSubDir1", PId = 5 },
new Record { Id = 7, Name = "SubSubDir2", PId = 5 },
new Record { Id = 8, Name = "SubSubDir3", PId = 5 },
new Record { Id = 9, Name = "SubSubDir4", PId = 5 },
new Record { Id = 10, Name = "SubSubDir5", PId = 5 },
};
var node = new Directory(0, null, null);
records
.OrderBy(x => x.PId)
.ThenBy(x => x.Id)
.ThenBy(x => x.Name)
.ToList()
.ForEach(x => node.AddChild(x.Name, x.Id, x.PId));
node.Print();
}
public class Directory
{
public Directory(int id, string name, Directory parent)
{
this.Id = id;
this.Name = name;
this.Parent = parent;
this.Indentation = parent is null ? 0 : parent.Indentation + 1;
this.Children = new HashSet<Directory>();
}
public int Id { get; set; }
public int Indentation { get; set; }
public string Name { get; set; }
public Directory Parent { get; set; }
public ICollection<Directory> Children { get; set; }
public void AddChild (string name, int id, int parentId)
{
if (this.Id == parentId)
{
this.Children.Add(new Directory(id, name, this));
return;
}
foreach (var child in this.Children)
{
child.AddChild(name, id, parentId);
}
}
public void Print()
{
Console.WriteLine($"{new string(' ', this.Indentation * 4)}{this.Name}");
foreach (var child in this.Children)
{
child.Print();
}
}
}

How can I convert a list of items with ParentID into a tree?

I'm using SQL Server and Entity Framework. On my database I have the following data:
ID | Name | ParentID
1 | Fire | null
2 | Fire2 | 1
3 | Fire3 | 2
4 | Blast | 2
5 | Water | null
6 | Water2 | 5
7 | WaterX | 5
I won't have massive data, so retrieving everything at once from the database is perfectly acceptable.
I want to retrieve this data and display on screen as a "tree".
Fire
Fire2
Fire3 Blast
Water
Water2 WaterX
How should I do this? Should I create some sort of recursion to render it? Should I somehow convert the list to an IGrouping?
I'm having trouble converting the flat list into something that I can render hierarchically on the screen, how could I do that?
This is the easiest way I know:
var things = new []
{
new { Id = 1, Name = "Fire", ParentId = (int?)null },
new { Id = 2, Name = "Fire2", ParentId = (int?)1 },
new { Id = 3, Name = "Fire3", ParentId = (int?)2 },
new { Id = 4, Name = "Blast", ParentId = (int?)2 },
new { Id = 5, Name = "Water", ParentId = (int?)null },
new { Id = 6, Name = "Water2", ParentId = (int?)5 },
new { Id = 7, Name = "Waterx", ParentId = (int?)5 }
};
var tree = things.ToLookup(x => x.ParentId, x => new { x.Id, x.Name });
The tree looks like this:
That should be fairly easy to render now.
If you can add another property to your class that has the child items like this:
public class Thing
{
public Thing()
{
Things = new List<Thing>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public List<Thing> Things { get; set; }
}
Then you can easily group the items to their parents like this:
var things = new List<Thing>
{
new Thing { Id = 1, Name = "Fire", ParentId = null },
new Thing { Id = 2, Name = "Fire2", ParentId = 1 },
new Thing { Id = 3, Name = "Fire3", ParentId = 2 },
new Thing { Id = 4, Name = "Blast", ParentId = 2},
new Thing { Id = 5, Name = "Water", ParentId = null },
new Thing { Id = 6, Name = "Water2", ParentId = 5 },
new Thing { Id = 7, Name = "Waterx", ParentId = 6 }
};
var groupedThings = new List<Thing>();
foreach (var thing in things)
{
if (thing.ParentId != null)
{
things.First(t => t.Id == thing.ParentId).Things.Add(thing);
}
else
{
groupedThings.Add(thing);
}
}
groupedThings.Dump();

Writing linq/lambda query between two tables to get values using EF

Hi I have two entity models that has data in tables as:
Table Group
id name
1 NW
2 SW
3 NE
Second Table is
Location
id groupId count
1 1 34
2 2 5
3 2 90
4 1 33
5 1 23
I want to write a linq query or using context in EF so that i can group by each groupId so that result is
groupdId 1
{count 34, 33, 23}
groupId 2
{count 90,5}
I have tried following
from e in DbContext.Set<Group>()
join a in DbContext.Set<locatin>() on e.Id equals a.groupId
Select new Test
{
groupId= e.groupdId
tables = new List<tbl>()
{
count= a.count;
}
}
public class Test
{
public int groupId {get; set;}
public IEnumerable<gbl> Tables {get; set;}
}
public class tbl
{
public int count {get; set;}
}
In writing the query when I get to count=a.count, I get intellisense error can not resolve. Please let me know how to correct my query so that I get heading of groupId then another array with count numbers only. Thanks.
Following will work in this case:
var result =
groups.Join(locations,g=>g.id,l=>l.groupId,(g,l)=>new {l})
.GroupBy(x =>x.l.groupId,x=>x.l.count)
.Select(y=>new Test
{
groupId= y.Key,
tables = y.Select(n => new tbl{count = n})
});
Following are the steps:
Join two tables groups and locations using the id and groupid
GroupBy the result and project the count
Select and result should be IEnumerable<Test>
Following is my complete code, that I have used to create the solution (using LinqPad):
void Main()
{
var groups = Group.CreateList();
var locations = Location.CreateList();
var result =
groups.Join(locations,g=>g.id,l=>l.groupId,(g,l)=>new {l})
.GroupBy(x =>x.l.groupId,x=>x.l.count)
.Select(y=>new Test
{
groupId= y.Key,
tables = y.Select(n => new tbl{count = n})
});
result.Dump();
}
public class Group
{
public int id;
public string name;
public static List<Group> CreateList()
{
return new List<Group>
{
new Group
{
id = 1,
name = "NW"
},
new Group
{
id = 2,
name = "SW"
},
new Group
{
id = 3,
name = "NE"
}
};
}
}
public class Location
{
public int id;
public int groupId;
public int count;
public static List<Location> CreateList()
{
return new List<Location>
{
new Location
{
id = 1,
groupId = 1,
count = 34
},
new Location
{
id = 2,
groupId = 2,
count = 5
},
new Location
{
id = 3,
groupId = 2,
count = 90
},
new Location
{
id = 4,
groupId = 1,
count = 33
},
new Location
{
id = 5,
groupId = 1,
count = 23
}
};
}
}
public class Test
{
public int groupId {get; set;}
public IEnumerable<tbl> tables {get; set;}
}
public class tbl
{
public int count {get; set;}
}

Flattening a parent/child object-chain, merging values along the way

We have been staring at this problem for ages, losing valuable time.
We have these objects, lets call them Components. Our application allows you to create a new Component, based on an existing Component.
All values of the parent are 'inherited' (read: accessible via a component.Parent property) unless you override a value -- e.g. you want to give it a new name, but keep the rest of the parent values.
// Exiting parent-child object chain
BaseObject {Id = 1, Name = "Base", Description = "Base description", Notes = "Awesome object", ParentId = null}
ChildObject1 {Id = 2, Name = "", Description = "", Notes = "", ParentId = 1}
ChildObject2 {Id = 3, Name = "Custom Name", Description = "", Notes = "", ParentId = 2}
ChildObject3 {Id = 4, Name = "", Description = "Final object", Notes = "", ParentId = 3}
Now I want to flatten this from the TOP DOWN, using existing values of the parent for any empty values on the child.
// End result after flattening/merging
ChildObject3 {Id = 4, Name = "Custom Name", Description = "Final object", Notes = "Awesome object", ParentId = 3}
Noteworthy: there is no child property, only the child knows about a parent. The parent is unaware of the child.
How to solve this without ugly while(child.Parent != null) constructions and creating previousComponent, currentComponent, etc. objects to keep track of set values.
How about a recursive DeepCopy method, something like:
public class ComponentClass()
{
public ComponentClass DeepCopy(ComponentClass comp)
{
CopyAllNonEmptyPropertiesTo(comp);
if (comp.StillHasEmptyProperties())
return Parent.DeepCopy(comp);
else
return comp;
}
}
Create a Dictionary of your data objects so you lookup your parents.
For each of the items you have, do a recursive lookup of parent values when:
You don't have a value for one of the target properties
You have a parent
E.g. (the Dump() command is from LinqPad)
var data = new List<Bob>
{
new Bob { Id = 1, Name = "Base", Description = "Base description", Notes = "Awesome object", ParentId = null },
new Bob { Id = 2, Name = "", Description = "", Notes = "", ParentId = 1 },
new Bob { Id = 3, Name = "Custom Name", Description = "", Notes = "", ParentId = 2 },
new Bob { Id = 4, Name = "", Description = "Final object", Notes = "", ParentId = 3 },
};
var map = data.ToDictionary(d => d.Id, d => d);
data.ForEach(row => CopyFromParent(map, row, row.ParentId.HasValue ? map[row.ParentId.Value] : null));
data.Dump();
}
void CopyFromParent(Dictionary<int, Bob> map, Bob target, Bob current)
{
// set properties
if (current != null && string.IsNullOrEmpty(target.Name) && !string.IsNullOrEmpty(current.Name)) target.Name = current.Name;
if (current != null && string.IsNullOrEmpty(target.Description) && !string.IsNullOrEmpty(current.Description)) target.Description = current.Description;
if (current != null && string.IsNullOrEmpty(target.Notes) && !string.IsNullOrEmpty(current.Notes)) target.Notes = current.Notes;
// dive deeper if we need to, and if we can
var needToDive = string.IsNullOrEmpty(target.Name) || string.IsNullOrEmpty(target.Description) || string.IsNullOrEmpty(target.Notes);
var canDive = current != null && current.ParentId.HasValue && map.ContainsKey(current.ParentId.Value);
if (needToDive && canDive)
{
CopyFromParent(map, target, map[current.ParentId.Value]);
}
}
class Bob
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Notes { get; set; }
public int? ParentId { get; set; }
}

Categories

Resources