Saving Hierarchical (Treeview) object Iteratively with parent and child in C# - c#

I have database table object which is:
[Table("TreeViewDb")]
public class TreeViewDb
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
}
And I have a view model whcih is :
public class TreeView
{
public TreeView()
{
Children = new List<TreeView>();
}
public int Id { get; set; }
public int ParentId { get; set; }
public string Name { get; set; }
// children
public List<TreeView> Children { get; set; }
}
Now I need to save TreeView to the database. During save children or children under children to the nth Level. But my below method only goes to level 3. How can I go to nth Level to save child and parent objects with recursive way?
public bool SaveOrUpdateTreeView(TreeView viewModel)
{
// Level 1
var parentModel = new TreeViewDb
{
Id = viewModel.Id,
ParentId = viewModel.ParentId,
Name = viewModel.Name
};
// Save or update object and return primary key
var parentId = _dataRepository.SaveOrUpdateTreeView(parentModel);
// Level 2
foreach (var child in viewModel.Children)
{
var childModel = new TreeViewDb
{
Id = viewModel.Id,
ParentId = parentId, // Parent Primary Key
Name = viewModel.Name
};
// Save or update object and return primary key
var childId = _dataRepository.SaveOrUpdateTreeView(childModel);
// Level 3
foreach (var grandChild in child.Children)
{
var grandChildModel = new TreeViewDb
{
Id = viewModel.Id,
ParentId = childId, // Child Primary Key
Name = viewModel.Name
};
_dataRepository.SaveOrUpdateTreeView(grandChildModel);
}
}
return true;
}

There you go:
use a Stack including item depth
iterate nodes in order
decide to save it or not
Code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace zzzzzzzz
{
internal static class Program
{
private static void Main(string[] args)
{
var node = new Node("node 0 # level 0")
{
new Node("node 1 # level 1")
{
new Node("node 2 # level 2")
{
new Node("node 3 # level 3")
{
new Node("node 4 # level 4")
}
}
},
new Node("node 5 # level 1")
{
new Node("node 6 # level 2")
{
new Node("node 7 # level 3")
}
}
};
var stack = new Stack<(Node, int)>();
stack.Push((node, 0));
while (stack.Any())
{
var (current, depth) = stack.Pop();
ProcessNode(current, depth);
foreach (var item in current.Reverse())
{
stack.Push((item, depth + 1));
}
}
}
private static void ProcessNode(Node node, int depth)
{
Console.Write($"{new string('\t', depth)}{node}");
var color = Console.ForegroundColor;
Console.ForegroundColor = depth < 3 ? ConsoleColor.Green : ConsoleColor.Red;
Console.Write($" {(depth < 3 ? "SAVED" : "IGNORED")}{Environment.NewLine}");
Console.ForegroundColor = color;
}
}
public class Node : IList<Node>
{
public Node(string text)
{
Text = text;
}
public string Text { get; set; }
private IList<Node> List { get; } = new List<Node>();
public IEnumerator<Node> GetEnumerator()
{
return List.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) List).GetEnumerator();
}
public void Add(Node item)
{
List.Add(item);
}
public void Clear()
{
List.Clear();
}
public bool Contains(Node item)
{
return List.Contains(item);
}
public void CopyTo(Node[] array, int arrayIndex)
{
List.CopyTo(array, arrayIndex);
}
public bool Remove(Node item)
{
return List.Remove(item);
}
public int Count => List.Count;
public bool IsReadOnly => List.IsReadOnly;
public int IndexOf(Node item)
{
return List.IndexOf(item);
}
public void Insert(int index, Node item)
{
List.Insert(index, item);
}
public void RemoveAt(int index)
{
List.RemoveAt(index);
}
public Node this[int index]
{
get => List[index];
set => List[index] = value;
}
public override string ToString()
{
return $"{nameof(Text)}: {Text}";
}
}
}

You can create an generic iterator that allow you to iterate over all the nodes in the tree:
public static IEnumerable<(T Parent, T Node, int Level)> BreadthFirst<T>(T self, Func<T, IEnumerable<T>> selector)
{
var queue = new Queue<(T Parent, T Node, int Level)>();
queue.Enqueue((self, self, 0));
while (queue.Count > 0)
{
var current = queue.Dequeue();
yield return current;
var node = current.Node;
var level = current.Level + 1;
foreach (var child in selector(node))
{
queue.Enqueue((node, child, level));
}
}
}
Called like
var nodes = BreadthFirst(viewModel, v => v.Children).Where(t => t.Level < 3);
The root node will have itself as the parent, so you will need to check for this if you want to handle it some other way.

Related

Delete a record by matching Value

I have a dictionary where values are stored in the following format -
userID, empDetails
For example,
1234, 'empName,jobDesc,CardNumber,Type'
I have to compare this information with another set of information such that -
If entered userId is present in the above dictionary, then remove this record from the dictionary.
If entered CardNumber is present (here userId is not known) in the above dictionary, then remove this record from the dictionary.
The first condition is simple and can be done by
dictionary.Remove(key)
But I am confused as to how would I implement the second condition. I want something like
if(CardNumber.PresentinAboveDictionary)
then
Remove that record
I know we can compare a partial string in a key like this, but I want to remove the record.
Check if any part of a hashtable value contains certain string c#
Assuming the employment details in your dictionary are a string in the specified format you would need to:
Search the values within the dictionary
Parse/Split the values to get the card numbers
Check the card numbers to see if they match the card number you are checking
Return the key value pair when a match occurs
Remove the entry for the key in the returned key value pair
Example code for the solution:
var dictionary = new Dictionary<int, string>() { { 1, "empName,jobDesc,124124134,Type" } };
var cardNumber = 124124134;
var entry = dictionary.FirstOrDefault(x => DoEmploymentDetailsContainCardNumber(x.Value, cardNumber));
if (!entry.Equals(default(KeyValuePair<int, string>)))
{
dictionary.Remove(entry.Key);
}
Method that checks if card number is present in employment details:
private static bool DoEmploymentDetailsContainCardNumber(string empDetails, int cardNumber)
{
var splitEmpDetails = empDetails.Split(',');
var empDetailsCardNumber = splitEmpDetails[2];
return empDetailsCardNumber == cardNumber.ToString();
}
Instead of Dictionary you can use a strongly typed List
Use the Linq builtin Remove method
Use Parallel.ForEach, iterate the list and remove the item (beware, takes more time)
pseudo code:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Collections;
namespace ConsoleApp4
{
public class Employee
{
public Employee(int userID, string empDetails)
{
string[] props = empDetails.Split(new char[] { ',' }, StringSplitOptions.None);
this.userID = userID;
this.empName = props[0];
this.jobDesc = props[1];
this.CardNumber = props[2];
this.Type = props[3];
}
public int userID { get; set; }
public string empName { get; set; }
public string jobDesc { get; set; }
public string CardNumber { get; set; }
public string Type { get; set; }
}
public class MyCustomList : List<Employee>
{
public void Add(int userID, string empDetails)
{
this.Add(new Employee(userID, empDetails));
}
public bool Remove(string CardNumber)
{
bool found = false ;
Parallel.ForEach(this,
(i, state) =>
{
if (i.CardNumber == CardNumber)
{
this.Remove(i);
state.Break();
}
});
return found;
}
public bool RemoveV2(string CardNumber)
{
bool found = false;
if (this.Any(x => x.CardNumber == CardNumber))
{
this.Remove(this.Where(x => x.CardNumber == CardNumber).First());
found = true;
}
return found;
}
}
class Program
{
static void Main(string[] args)
{
var dict = new MyCustomList();//userID, empDetails list
dict.Add(12341, "empName1,jobDesc,CardNumber1,Type");
dict.Add(12342, "empName2,jobDesc,CardNumber2,Type");
dict.Add(12343, "empName3,jobDesc,CardNumber3,Type");
dict.Add(12344, "empName4,jobDesc,CardNumber4,Type");
dict.Add(12345, "empName5,jobDesc,CardNumber5,Type");
dict.Add(12346, "empName6,jobDesc,CardNumber6,Type");
dict.Add(12347, "empName7,jobDesc,CardNumber7,Type");
dict.Add(12348, "empName8,jobDesc,CardNumber8,Type");
//remove CardNumber5
dict.Remove("CardNumber5");
Console.Write(dict);
}
}
}
you can follow the simple approach to remove the key by using a loop here.
Here I am assuming that there is no key with a value of -1 in the dictionary.
int keyToRemove = -1;
foreach (var entry in dictionary)
{
if (entry.Value.Contains(CardNumber))
{
keyToRemove = entry.Key;
break;
}
}
if (keyToRemove != -1)
{
dictionary.Remove(keyToRemove);
}
This is possibly overkill and is not optimised for reading the full dataset repeatedly but it is considerably faster than the accepted solution. I put together a test of the solution below which did the following:
Generated 1,000,000 data rows with unique IDs and card numbers (the solution would also work if the card numbers were not unique)
Randomly removed 100,000 data items by ID and 100,000 data items by card number
Generated a list of the remaining data items
The process took around 75 seconds.
I then tried to repeat steps 1) and 2) using the accepted answer - after around 10 minutes it's about 7% of the way through removing data items. Therefore I think the solution below is around 2 orders of magnitude faster for this type of operation.
There are probably better doubley linked list implementations out there but I am not too familiar with any of them.
namespace Question
{
public class EmployeeCollection
{
private readonly Dictionary<int, ListNode<EmployeeDetails>> _idDictionary = new();
private readonly Dictionary<string, Dictionary<int, EmployeeDetails>> _cardNumberDictionary = new();
private readonly LinkedList<EmployeeDetails> _list = new();
public void AddEmployee(EmployeeDetails details)
{
var node = new ListNode<EmployeeDetails>(details);
_list.AddToStart(node);
_idDictionary.Add(details.Id, node);
if(!_cardNumberDictionary.ContainsKey(details.CardNumber))
{
_cardNumberDictionary.Add(details.CardNumber, new Dictionary<int, EmployeeDetails>());
}
_cardNumberDictionary[details.CardNumber].Add(details.Id, details);
}
public void RemoveById(int id)
{
if (_idDictionary.TryGetValue(id, out var node))
{
_idDictionary.Remove(id);
_list.Remove(node);
var list = _cardNumberDictionary[node.Value.CardNumber];
list.Remove(id);
if(list.Count == 0)
{
_cardNumberDictionary.Remove(node.Value.CardNumber);
}
}
}
public void RemoveByCardNumber(string cardNumber)
{
if (_cardNumberDictionary.TryGetValue(cardNumber, out var employees))
{
_cardNumberDictionary.Remove(cardNumber);
foreach (var employee in employees)
{
if (_idDictionary.TryGetValue(employee.Key, out var node))
{
_list.Remove(node);
}
}
}
}
public IEnumerable<EmployeeDetails> Employees => _list.GetAllValues();
public EmployeeDetails? GetById(int id)
{
if(_idDictionary.ContainsKey(id))
{
return _idDictionary[id].Value;
}
return null;
}
}
public class EmployeeDetails
{
public int Id { get; init; }
public string Name { get; init; }
public string JobDescription { get; init; }
public string CardNumber { get; init; }
public string Type { get; init; }
public static EmployeeDetails FromData(int id, string details)
{
var parts = details.Split(',');
return new EmployeeDetails
{
Id = id,
Name = parts[0],
JobDescription = parts[1],
CardNumber = parts[2],
Type = parts[3],
};
}
}
public class LinkedList<T>
{
public int Count { get; private set; }
private ListNode<T>? Start { get; set; }
private ListNode<T>? End { get; set; }
public bool IsEmpty => Count == 0;
public void AddToStart(ListNode<T> node)
{
ArgumentNullException.ThrowIfNull(nameof(node));
node.Next = null;
node.Previous = null;
if (IsEmpty)
{
Start = End = node;
}
else
{
Start!.Previous = node;
node.Next = Start;
Start = node;
}
Count++;
}
public void Remove(ListNode<T> node)
{
if (node != Start)
{
node.Previous!.Next = node.Next;
}
else
{
Start = node.Next;
}
if (node != End)
{
node.Next!.Previous = node.Previous;
}
else
{
End = node.Previous;
}
Count--;
}
public IEnumerable<T> GetAllValues()
{
var counter = Start;
while (counter != null)
{
yield return counter.Value;
counter = counter.Next;
}
}
}
public class ListNode<T>
{
public T Value { get; }
public ListNode<T>? Previous { get; set; }
public ListNode<T>? Next { get; set; }
public ListNode(T value)
{
Value = value;
}
}
}
you can do something like this.
var recordsToRemove = dictionary.Where(x => x.Value.Contains("what you are looking for"))
.ToList();
if (recordsToRemove.Any())
{
foreach (var record in recordsToRemove)
{
dictionary.Remove(record.Key);
}
}

Recursively create a tree hierarchy off an array of strings

I have an array of strings separated by "!". I am trying to break that string up and create a tree hierarchy recursively in my custom class called PivotGroup. For example, what I am aiming at is to break up string array
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891"}
Into the PivotGroup class such as PivotGroup contains ChildGroups[] that embed the array strings.
So for example:
PivotGroup pgGroup = new PivotGroup();
pgGroup.ChildGroups[0] = PivotGroup[]; // Key:Book Level 3 Value: "AAA"
Now within Book Level 3 ChildGroups I need to set Book Level 4 which value is "EEE" and within the ChildGroups of "EEE" I would need to create another childGroup array which size in the case would be 3 called Book Level 5 and set another PivotGroup for each of following 15712, 15722, 13891
Here is my PivotGroup Class and embedded class Objects:
public class PivotGroup
{
public PivotGroup() { }
public PivotGroup(PivotGroupKey groupKey, PivotRow data, PivotGroup[] childGroups, bool leaf, int groupLevel)
{
GroupKey = groupKey;
Data = data;
ChildGroups = childGroups;
Leaf = leaf;
GroupLevel = groupLevel;
}
public PivotGroupKey GroupKey { get; private set; }
public PivotRow Data { get; private set; }
public PivotGroup[] ChildGroups { get; set; }
public bool Leaf { get; private set; }
public int GroupLevel { get; private set; }
public override string ToString()
{
return GroupKey + ", GroupLevel: " + GroupLevel + ", Children: " +
ChildGroups.Length + (Leaf ? " (Leaf)" : "");
}
}
public class PivotGroupKey
{
public PivotGroupKey()
{
}
public PivotGroupKey(string keyGroup, string keyValue)
{
if(keyGroup != null)
KeyGroup = string.Intern(keyGroup);
if (keyValue != null)
KeyValue = string.Intern(keyValue);
}
public string KeyGroup { get; private set; }
public string KeyValue { get; private set; }
public override string ToString()
{
return KeyGroup + ": " + KeyValue;
}
}
public class PivotRow
{
public PivotRow()
{
}
public PivotRow(string key, params object[] data) : this(key, true, data) { }
public PivotRow(string key, bool entitled, params object[] data)
{
Data = data;
Key = null;
Entitled = entitled;
}
public object[] Data { get; private set; }
public bool Entitled { get; private set; }
public string Key { get { return null; } set { } }
}
Main program I tried:
public class BookLevels
{
public string Root { get; set; }
public string BookLevel2 { get; set; }
public string BookLevel3 { get; set; }
public string BookLevel4 { get; set; }
public string BookLevel5 { get; set; }
}
class Program
{
static void BuildTree(string[] paths)
{
var BookPaths = paths.Select(x => x.Split('!'))
.Select(x => new BookLevels
{
Root = x[0],
BookLevel2 = x[1],
BookLevel3 = x[2],
BookLevel4 = x[3],
BookLevel5 = x[4]
}).GroupBy(z => new { z.BookLevel3, z.BookLevel4 }).ToArray();
var BookLevel3Cnt = BookPaths.Select(q => q.Key.BookLevel3).Count();
PivotGroup root = new PivotGroup(
new PivotGroupKey("Total", ""),
new PivotRow(null, new string[8]),
new PivotGroup[BookLevel3Cnt], false, 0);
foreach (var booklevel3 in BookPaths)
{
AddChildren(root, booklevel3);
}
}
private static void AddChildren(PivotGroup root, IGrouping<object, BookLevels> booklevel, int index = 0)
{
root.ChildGroups[index] = new PivotGroup(
new PivotGroupKey("Book Level " + (index + 3).ToString(), booklevel.Key.ToString()),
new PivotRow(null, new string[8]),
AddChildren(root, booklevel[index], index + 1), false, 0);
}
static void Main(string[] args)
{
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891",
"ROOT!ZZZ!AAA!DDD!15712",
"ROOT!ZZZ!AAA!DDD!15722",
"ROOT!ZZZ!AAA!DDD!13891",
"ROOT!ZZZ!BBB!DDD!15812",
"ROOT!ZZZ!BBB!DDD!15822",
"ROOT!ZZZ!BBB!DDD!13891",
};
BuildTree(paths);
Console.WriteLine();
Console.ReadLine();
}
I think my issue might be the way I am creating the Linq statement that breaks up the string, since I'm not sure how to progress thru it recursively.
I'm not sure what goes into which property. Also, for sake of simplicity and to be able to concentrate on the recursive algorithm, I redefine the group class like this (it does not mean that you have to change your class, instead, adapt my algorithm):
public class PivotGroup
{
public string Key { get; set; }
public List<PivotGroup> ChildGroups { get; } = new List<PivotGroup>();
public override string ToString() => Key; // Makes debugging easier.
}
The idea is that the values of the path go into the key. I made ChildGroups a list to be able to add children successively. My BuildTree returns the root
static PivotGroup BuildTree(string[] paths)
{
var root = new PivotGroup { Key = "ROOT" };
foreach (string path in paths) {
AddChildren(root, path.Split('!').Skip(1).ToList());
}
return root;
}
The recursive part goes into AddChildren. I convert the path into a List<string> to be able to remove the added part. AddChildren assumes that the first item in path is the first child to be added.
static void AddChildren(PivotGroup group, List<string> path)
{
string key = path[0];
int index = group.ChildGroups.FindIndex(g => g.Key == key);
PivotGroup child;
if (index >= 0) { // A child with this key exists.
child = group.ChildGroups[index]; // Select this existing child.
} else { // This key is missing. Add a new child.
child = new PivotGroup { Key = key };
group.ChildGroups.Add(child);
}
if (path.Count > 1) {
path.RemoveAt(0); // Remove the added child key and add the rest recursively.
AddChildren(child, path);
}
}
We add children by walking down the tree and adding new children if necessary.
This prints the tree recursively:
private static void PrintTree(PivotGroup group, int level)
{
Console.WriteLine(new String(' ', 2 * level) + group.Key);
foreach (PivotGroup child in group.ChildGroups) {
PrintTree(child, level + 1);
}
}
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
...
};
PivotGroup root = BuildTree(paths);
PrintTree(root, 0);
Console.ReadKey();
We could also use a loop instead of doing a recursion, since we add one branch at a time:
static PivotGroup BuildTree(string[] paths)
{
var root = new PivotGroup { Key = "ROOT" };
foreach (string path in paths) {
PivotGroup group = root;
string[] pathElements = path.Split('!');
for (int i = 1; i < pathElements.Length; i++) { // Element [0] is ROOT, we skip it.
string key = pathElements[i];
int index = group.ChildGroups.FindIndex(g => g.Key == key);
PivotGroup child;
if (index >= 0) { // A child with this key exists.
child = group.ChildGroups[index]; // Select this existing child.
} else { // This key is missing. Add a new child.
child = new PivotGroup { Key = key };
group.ChildGroups.Add(child);
}
group = child;
}
}
return root;
}
List<T>.FindIndex is inefficient for large lists. If you have large data sets and the order does not matter, switch to Dictionary<string, PivotGroup>. If you need the data to be sorted, use SortedDictionary<string, PivotGroup>.
Here is some simple recursive code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] paths = new string[] {
"ROOT!ZZZ!AAA!EEE!15712",
"ROOT!ZZZ!AAA!EEE!15722",
"ROOT!ZZZ!AAA!EEE!13891"};
List<List<string>> inputData = paths.Select(x => x.Split(new char[] {'!'}).ToList()).ToList();
Node root = new Node();
Node.ParseTree(root, inputData);
}
}
public class Node
{
public string name { get; set; }
public List<Node> children { get; set; }
public static void ParseTree(Node parent, List<List<string>> inputData)
{
parent.name = inputData.First().FirstOrDefault();
var groups = inputData.Select(x => x.Skip(1)).GroupBy(x => x.Take(1).FirstOrDefault());
foreach (var group in groups)
{
if (group.Key != null)
{
if (parent.children == null) parent.children = new List<Node>();
Node newNode = new Node();
parent.children.Add(newNode);
ParseTree(newNode, group.Select(x => x.Select(y => y).ToList()).ToList());
}
}
}
}
}

How to avoid out of memory using trie by C#

how to write billions of data into a trie with less memory
I want to extract some infomation from news like company names,so I write billions of company names into a trie,but it needs much memory and throw out of memory exception,I don't know how to solve it,so anyone can help,thanks in advance.
public class Node
{
public char Value { get; set; }
public List<Node> Children { get; set; }
public int Depth { get; set; }
public string Code { get; set; }
public bool Terminal { get; set; }
public Node(char value, int depth)
{
Value = value;
Depth = depth;
Children = new List<Node>();
}
public Node FindChildNode(char c)
{
foreach (var child in Children)
if (child.Value == c)
return child;
return null;
}
}
public class Trie
{
private Node _root;
public Trie()
{
_root = new Node('^',0);
}
public Node Prefix(string s)
{
var currentNode = _root;
var result = currentNode;
foreach (var c in s)
{
currentNode = currentNode.FindChildNode(c);
if (currentNode == null)
break;
result = currentNode;
}
return result;
}
public void Insert(string randomLength,string code)
{
var commonPrefix = Prefix(randomLength);
var current = commonPrefix;
for (var i = current.Depth; i < s.Length; i++)
{
var newNode = new Node(s[i], current.Depth + 1);
if (i+1==s.Length)
{
newNode.Terminal = true;
newNode.Code = code;
}
current.Children.Add(newNode);
current = newNode;
}
}
}
Trie t=new Trie();
t.Insert("C","ABCG00DFD");
The aboved statement run 1000000000 Loops and the "C" can be replaced with different string with different length,as the loops increasing,it throw out of memory exception,so how to avoid or change it?
Have a go at this Trie and see if you can get it to work for what you need:
public class Trie : Dictionary<char, Trie>
{
public void Add(string value)
{
var c = String.IsNullOrEmpty(value) ? '\0' : value[0];
if (!this.ContainsKey(c))
{
this[c] = new Trie();
}
if (c != '\0')
{
this[c].Add(value.Substring(1));
}
}
}

How to get parent node in a tree structure like this

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

How to transform list of hierarchyid into a binary tree

I am working on a multi-level marketing (binary) which looks like this:
(but the binary tree is not required to be perfect. A node can have 0-2 child)
My problem is the data that I fetch from the database is flat list.
Notice that I am using hierarchyid (sql server 2014)
Basically the TextNode column is like a breadcrumb.
every slash / represents a level.
If I have TextNode of /1/ as root. then every node that starts with /1/ belongs to that root which are /1/, /1/1/ and /1/1/1/ (the root node is included which will be the level 0)
I've tried the accepted answer in this question but its not working.
How can I transform the flatlist to a Binary Tree so that I can easily traverse and display it on a screen?
Im using C#, ASP MVC 5, SQL Server 2014 if it matters.
I implement exactly this code According to Alex implementation but as is mentioned in some case it didn't work correctly .. have a look to my Image and my code (which copied from Alex post) [data in the database are correct but in tree view seems some problems ]
public class Row : IRow<string>
{
public string TextNode { get; }
public string Value { get; }
public long Id { get; }
public string FIN { get; }
public Row(string textNode, string userName, long id, string fin)
{
FIN = fin;
Id = id;
TextNode = textNode;
Value = userName;
}
}
public interface IRow<out T>
{
string TextNode { get; }
long Id { get; }
string FIN { get; }
T Value { get; }
}
public class TreeNode<T>
{
private struct NodeDescriptor
{
public int Level { get; }
public int ParentIndex { get; }
public NodeDescriptor(IRow<T> row)
{
var split = row.TextNode.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
Level = split.Length;
ParentIndex = split.Length > 1 ? int.Parse(split[split.Length - 2]) - 1 : 0;
}
}
public T title { get; }
public long Id { get; }
public string FIN { get; }
public List<TreeNode<T>> children { get; }
private TreeNode(T value, long id, string fin)
{
Id = id;
FIN = fin;
title = value;
children = new List<TreeNode<T>>();
}
public static TreeNode<T> Parse(IReadOnlyList<IRow<T>> rows)
{
if (rows.Count == 0)
return null;
var result = new TreeNode<T>(rows[0].Value, rows[0].Id, rows[0].FIN);
FillParents(new[] { result }, rows, 1, 1);
return result;
}
private static void FillParents(IList<TreeNode<T>> parents, IReadOnlyList<IRow<T>> rows, int index, int currentLevel)
{
var result = new List<TreeNode<T>>();
for (int i = index; i < rows.Count; i++)
{
var descriptor = new NodeDescriptor(rows[i]);
if (descriptor.Level != currentLevel)
{
FillParents(result, rows, i, descriptor.Level);
return;
}
var treeNode = new TreeNode<T>(rows[i].Value, rows[i].Id, rows[i].FIN);
parents[descriptor.ParentIndex].children.Add(treeNode);
result.Add(treeNode);
}
}
}
g
this is also my JSON output for more information :
{"title":"Earth","Id":32,"FIN":"FIN","children":[{"title":"Europe","Id":33,"FIN":"FIN001","children":[{"title":"France","Id":35,"FIN":"FIN001001","children":[{"title":"Paris","Id":36,"FIN":"FIN001001001","children":[]},{"title":"Brasilia","Id":41,"FIN":"FIN002001001","children":[]},{"title":"Bahia","Id":42,"FIN":"FIN002001002","children":[]}]},{"title":"Spain","Id":38,"FIN":"FIN001002","children":[{"title":"Madrid","Id":37,"FIN":"FIN001002001","children":[{"title":"Salvador","Id":43,"FIN":"FIN002001002001","children":[]}]}]},{"title":"Italy","Id":45,"FIN":"FIN001003","children":[]},{"title":"Germany","Id":48,"FIN":"FIN001004","children":[]},{"title":"test","Id":10049,"FIN":"FIN001005","children":[]}]},{"title":"South America","Id":34,"FIN":"FIN002","children":[{"title":"Brazil","Id":40,"FIN":"FIN002001","children":[{"title":"Morano","Id":47,"FIN":"FIN001003001","children":[]}]}]},{"title":"Antarctica","Id":39,"FIN":"FIN003","children":[{"title":"McMurdo Station","Id":44,"FIN":"FIN003001","children":[]}]}]}
Here is a very simple implementation (assuming that Nodes are in the right order), which may be enhanced in multiple ways
public interface IRow<out T>
{
string TextNode { get; }
T Value { get; }
}
public class TreeNode<T>
{
private struct NodeDescriptor
{
public int Level { get; }
public int ParentIndex { get; }
public NodeDescriptor(IRow<T> row)
{
var split = row.TextNode.Split(new [] {"/"}, StringSplitOptions.RemoveEmptyEntries);
Level = split.Length;
ParentIndex = split.Length > 1 ? int.Parse(split[split.Length - 2]) - 1 : 0;
}
}
public T Value { get; }
public List<TreeNode<T>> Descendants { get; }
private TreeNode(T value)
{
Value = value;
Descendants = new List<TreeNode<T>>();
}
public static TreeNode<T> Parse(IReadOnlyList<IRow<T>> rows)
{
if (rows.Count == 0)
return null;
var result = new TreeNode<T>(rows[0].Value);
FillParents(new[] {result}, rows, 1, 1);
return result;
}
private static void FillParents(IList<TreeNode<T>> parents, IReadOnlyList<IRow<T>> rows, int index, int currentLevel)
{
var result = new List<TreeNode<T>>();
for (int i = index; i < rows.Count; i++)
{
var descriptor = new NodeDescriptor(rows[i]);
if (descriptor.Level != currentLevel)
{
FillParents(result, rows, i, descriptor.Level);
return;
}
var treeNode = new TreeNode<T>(rows[i].Value);
parents[descriptor.ParentIndex].Descendants.Add(treeNode);
result.Add(treeNode);
}
}
}
Sample usage:
public class Row : IRow<string>
{
public string TextNode { get; }
public string Value { get; }
public Row(string textNode, string userName)
{
TextNode = textNode;
Value = userName;
}
}
class Program
{
static void Main(string[] args)
{
IRow<string>[] rows =
{
new Row("/", "Ahmed"),
new Row("/1/", "Saeed"),
new Row("/2/", "Amjid"),
new Row("/1/1/", "Noura"),
new Row("/2/1/", "Noura01"),
new Row("/2/2/", "Reem01"),
new Row("/1/1/1", "Under_noura")
};
var tree = TreeNode<string>.Parse(rows);
PrintTree(tree);
}
private static void PrintTree<T>(TreeNode<T> tree, int level = 0)
{
string prefix = new string('-', level*2);
Console.WriteLine("{0}{1}", prefix, tree.Value);
foreach (var node in tree.Descendants)
{
PrintTree(node, level + 1);
}
}
}

Categories