How to avoid out of memory using trie by C# - 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));
}
}
}

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

Saving Hierarchical (Treeview) object Iteratively with parent and child in 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.

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

C# Parse IF-ELSE Statement

UPDATE
I don´t want you to do my Work and write code for me I just wanted a nurge in the right Direction!
So I have to be more specific with my Problem, give me a chance to do some work on this and I will update my question with the results ;-)
UPDATE 2
I´ve solved my Problem with Roslyn maybe not very elegant but it work for my needs, here is the code ;-)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
namespace ParserTest
{
public class MyParser
{
private int _currentLevel = 1;
public void TestMethod()
{
string testString =
#" if(#ISEMPTY(temp.tis_filterstatus2))
{
tis_datasheet_selection.is_selected = 'Y'
}
else
{
if(#ISEMPTY(temp.tis_programmtyp_filter)) { }
else
{
AND tis_programme_v.type = '#SUB(temp.tis_programmtyp_filter)'
}
if(#ISEMPTY(temp.tis_programmfilter)) { }
else
{
AND tis_programme_v.programm LIKE '#SUB(temp.tis_programmfilter)%'
}";
var result = this.Parse(testString);
var finalResult = this.GenerateDsl(result);
}
public List<IfStatement> Parse(string strToParse)
{
var result = new List<IfStatement>();
var syntaxTree = SyntaxTree.ParseText(#"using System;class C{static void M(){" + strToParse + "}}");
var rootNodes = syntaxTree.GetRoot().DescendantNodes().Where(getRootNodes);
result = rootNodes.Select(n => ToIfStatement(n, null)).ToList();
ApplyNestingLevel(result);
return result;
}
private string GenerateDsl(List<IfStatement> list)
{
var sb = new StringBuilder();
foreach(var ifStmt in list)
{
IfStatementToDsl(ifStmt, sb);
}
return sb.ToString();
}
private string IfStatementToDsl(IfStatement ifStmt, StringBuilder sb)
{
string sqr = "";
for (int i = 0; i < ifStmt.Level; i++)
{
sqr += "'";
}
sb.Append("#IF(");
sb.Append(ifStmt.Condition.ApplyLevel(ifStmt.Level) + "," + sqr);
sb.Append(ifStmt.Statement.ApplyLevel(ifStmt.Level));
if(ifStmt.Childs.Count > 0)
{
foreach(var c in ifStmt.Childs)
{
IfStatementToDsl(c, sb);
}
}
sb.Append(sqr + "," + sqr);
if(ifStmt.Else != null)
{
sb.Append(ifStmt.Else.Statement.ApplyLevel(ifStmt.Level));
foreach(var c in ifStmt.Else.Childs)
{
IfStatementToDsl(c, sb);
}
}
sb.Append(sqr + ")");
return sb.ToString();
}
#region Parsing-Methods
private IfStatement ToIfStatement(SyntaxNode node, SyntaxNode parent)
{
var ifNode = (IfStatementSyntax)node;
var ifStmt = new IfStatement
{
Condition = ifNode.Condition.ToString(),
Statement = GetIfStatement(ifNode),
Childs = GetIfChilds(ifNode)
};
if (ifNode.Else != null)
{
ifStmt.Else = new ElseStatement
{
Statement = GetElseStatement(ifNode.Else),
Childs = GetElseChilds(ifNode.Else)
};
}
return ifStmt;
}
private List<IfStatement> GetIfChilds(IfStatementSyntax node)
{
var childs = node.Statement.DescendantNodes().Where(n => WhereIfNodes(n, node));
return childs.Select(n => ToIfStatement(n, node)).ToList();
}
private List<IfStatement> GetElseChilds(ElseClauseSyntax node)
{
var childs = node.Statement.DescendantNodes().Where(n => WhereElseNodes(n, node));
return childs.Select(n => ToIfStatement(n, node)).ToList();
}
private string GetIfStatement(IfStatementSyntax node)
{
var result = node.Statement.DescendantNodes().Where(n => WhereIfStatement(n, node));
string returnValue = "";
foreach (var n in result)
{
returnValue += n.ToString();
}
return returnValue.CleanString();
}
private string GetElseStatement(ElseClauseSyntax node)
{
var result = node.Statement.DescendantNodes().Where(n => WhereElseStatement(n, node));
string returnValue = "";
foreach (var n in result)
{
returnValue += n.ToString() + " ";
}
return returnValue.CleanString();
}
private void ApplyNestingLevel(List<IfStatement> list)
{
foreach (var item in list)
{
item.Level = _currentLevel;
if (item.Childs.Count > 0 || (item.Else != null && item.Else.Childs.Count > 0))
{
_currentLevel++;
}
ApplyNestingLevel(item.Childs);
if (item.Else != null)
{
ApplyNestingLevel(item.Else.Childs);
}
}
}
#endregion
#region Linq Where-Conditions
private bool WhereIfNodes(SyntaxNode node, IfStatementSyntax parent)
{
if(node.Kind == SyntaxKind.IfStatement && (node.Parent.Parent == parent))
{
return true;
}
return false;
}
private bool WhereElseNodes(SyntaxNode node, ElseClauseSyntax parent)
{
if (node.Kind == SyntaxKind.IfStatement && (node.Parent.Parent == parent))
{
return true;
}
return false;
}
private bool WhereIfStatement(SyntaxNode node, IfStatementSyntax parent)
{
if ((node.Kind == SyntaxKind.ExpressionStatement || node.Kind == SyntaxKind.LocalDeclarationStatement)
&& (node.Parent.Parent == parent))
{
return true;
}
return false;
}
private bool WhereElseStatement(SyntaxNode node, ElseClauseSyntax parent)
{
if ((node.Kind == SyntaxKind.ExpressionStatement || node.Kind == SyntaxKind.LocalDeclarationStatement)
&& (node.Parent.Parent == parent))
{
return true;
}
return false;
}
private Func<SyntaxNode, bool> getRootNodes =
n => n.Kind == SyntaxKind.IfStatement &&
(n.Parent.Parent.Kind != SyntaxKind.ElseClause && n.Parent.Parent.Kind != SyntaxKind.IfStatement);
#endregion
}
public class IfStatement
{
public int Level { get; set; }
public string Condition { get; set; }
public string Statement { get; set; }
public ElseStatement Else { get; set; }
public List<IfStatement> Childs { get; set; }
}
public class ElseStatement
{
public string Statement { get; set; }
public List<IfStatement> Childs { get; set; }
}
public static class Ext
{
public static string CleanString(this string value)
{
return value.Replace("\t", "").Replace("\n", "").Replace("\r", "");
}
public static string ApplyLevel(this string value, int level)
{
int multiplier = level * 2;
if (level == 0)
multiplier = 1;
var sb = new StringBuilder(multiplier);
for (int i = 0; i < multiplier; i++)
{
sb.Append("'");
}
return value.Replace("'", sb.ToString());
}
}
}
I have to write if-else Statements in a Domain-Specific-Language which is really a pain in the ass!
(The DSL is from a Third-Party-Tool which I have to use to generate WHERE-Statements for SQL-Queries)
Syntax:
#IF(#ISEMPTY(#SUB(temp.last_name))),'if true','else')
#SUB() reads a textboxvalue
Sample:
#IF(#ISEMPTY(#SUB(temp.last_name))),'','account_contact.last_name = ''DOE'' ')
you have to double your single-quotes in the else statement and if you want to nest different "if-else" every time you go a level deeper you have to double the doubled single-quotes!
You see it´s not very simple to write if you have complicated contitions...
So I thought I write a parser that transforms a normal if-else statement to this DSL-Syntax!
The Parser should create objects of this class:
public class IfCondition
{
public int ID { get; set; }
public int ParentID { get; set; }
public int Level { get; set; }
public string Condition { get; set; }
public string Content { get; set; }
public string ElseContent { get; set; }
}
based on a Collection of this Objects I could generate the DSL-Statements!
So my Problem is I really don´t have a clue, how to parse a String like this:
IF(#ISEMPTY(#SUB(temp.last_name))) { }
ELSE
{
IF(#SUB(temp.test) = 'Y')
{
account_contact.last_name = 'DOE'
}
ELSE
{
account_contat.first_name = "JOHN"
}
}
can somebody give me a nudge in the right direction?
If you use Roslyn for syntax rewritting. Not to just write about what you can do, here is an easy example.
Basically how it works. You create a syntax tree for your code and then use predefined visitors to visit those nodes you want to rewrite, you replace them by valid C# code and then you can compile this tree and make it work.
EDIT:
Another, more reliable source with an example

Categories