I have a set of enumeration values (fault codes to be precise). The code is a 16 bit unsigned integer. I am looking for a data structure that could represent such an enumeration. A similar question has been asked here: What's the best C# pattern for implementing a hierarchy with an enum?. But this hierarchy is deeper.
Sample enumeration values
Current = 0x2000,
Current_DeviceInputSide = 0x2100,
ShortToEarth = 0x2120,
ShortToEarthInPhase1 = 0x2121,
ShortToEarthInPhase2 = 0x2122,
ShortToEarthInPhase3 = 0x2123
Use case
When the user provides a code then the UI has to display the equivalent meaning of the code with the hierarchy.
For example, if the user provides a value 0x2121 then the UI has to display Short to earth in phase 1 in the current at device input side. The best way to represent this is by using a hierarchical notation: Current : DeviceInputSide : ShortToEarth : ShortToEarthInPhase1.
Competing approaches
I have three competing approaches to represent the enumeration:
Create an enumeration at each level of the hierarchy. Then use a controller class to resolve the name.
Store the enumeration values in an xml and use LINQ to generate the meaning of the code.
Store the enumeration values in an xml. During the application startup. Create a singleton instance to retrieve the meaning. The instance contains a dictionary populated with the values from the xml.
Approach 1
The enumerations:
enum WarnCodes
{
None= 0x000,
Current = 0x2000
}
enum WarnCodes_Current
{
DeviceInputSide = 0x2100,
DeviceOutputSide = 0x2200
}
enum WarnCodes_Current_DeviceInputSide
{
ShortToEarth = 0x2120,
ShortCircuit = 0x2130
}
enum WarnCodes_Current_DeviceInputSide_ShortToEarth
{
InPhase1 = 0x2121,
InPhase2 = 0x2122
}
The controller:
public string GetMeaning(int code)
{
int bitMask = 0xF000;
int maskedCode = bitMask & code;
StringBuilder meaning = new StringBuilder();
switch (maskedCode)
{
case WarnCodes.Current:
meaning.Append("Current : ");
bitMask = 0xFF00;
maskedCode = bitMask & code;
switch (maskedCode)
{
case WarnCodes_Current.DeviceInputSide:
meaning.Append("Current : Device Input Side :");
...
break;
}
break;
...
}
}
Approach 2
The xml to store the enumeration values looks like this
<WarnCodes>
<code hex="2000" meaning="Current">
<code hex="2100" meaning="Current, Device Input side">
<code hex="2120" meaning="Short to Earth">
<code hex="2121" meaning="Short to earth in Phase L1"/>
<code hex="2122" meaning="Short to earth in Phase L2"/>
</code>
</code>
</code>
</WarnCodes>
And the method used to query the codes is:
XElement rootElement = XElement.Load(settingsFilePath);
public string GetHierarchicalMeaning(int code)
{
XElement rootElement = XElement.Load(warnCodesFilePath);
List<string> meanings = new List();
StringBuilder stringBuilder = new StringBuilder();
IEnumerable<XElement> elements;
elements = from el in rootElement.Descendants("code")
where (string)el.Attribute("hex") == code.ToString("X")
select el;
XElement element = elements.First();
while (element.Parent != null)
{
meanings.Add(element.Attribute("meaning").Value);
element = element.Parent;
}
meanings.Reverse();
foreach (string meaning in meanings)
{
stringBuilder.AppendFormat("{0} : ", meaning);
}
return stringBuilder.ToString().Trim().TrimEnd(':').Trim();
}
Approach 3
The xml to store the enumeration values is same as in Approach 2. The dictionary is populated from the xml by GetChildren().
private Dictionary<int, WarnCodeValue> warnCodesDictionary;
public void Initialize()
{
XElement rootElement = XElement.Load(settingsFilePath);
warnCodesDictionary = GetChildren(rootElement);
}
private Dictionary<int, WarnCodeValue> GetChildren(XElement element)
{
if (element.Descendants().Count() > 0)
{
Dictionary<int, WarnCodeValue> childNodeDictionary = new Dictionary();
foreach (XElement childElement in element.Elements())
{
int hex = Convert.ToInt32(childElement.Attribute("hex").Value, 16);
string meaning = childElement.Attribute("meaning").Value;
Dictionary<int, WarnCodeValue> dictionary = GetChildren(childElement);
WarnCodeValue warnCodeValue;
if (dictionary == null)
{
warnCodeValue = new WarnCodeValue() {Meaning = meaning};
}
else
{
warnCodeValue = new WarnCodeValue() {Meaning = meaning, ChildNodes = dictionary};
}
childNodeDictionary.Add(hex, warnCodeValue);
}
return childNodeDictionary;
}
return null;
}
The meanings are retrieved using GetHierarchicalMeaning():
public string GetHierarchicalMeaning(int code)
{
StringBuilder stringBuilder = new StringBuilder();
int firstLevel = code & 0xF000;
int secondLevel = code & 0xFF00;
int thirdLevel = code & 0xFFF0;
if(warnCodesDictionary.ContainsKey(firstLevel))
{
stringBuilder.AppendFormat("{0} : ", warnCodesDictionary[firstLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes.ContainsKey(secondLevel))
{
stringBuilder.AppendFormat("{0} : ", warnCodesDictionary[firstLevel].ChildNodes[secondLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes.ContainsKey(thirdLevel))
{
stringBuilder.AppendFormat("{0} : ",
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].Meaning);
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes.ContainsKey(code))
{
stringBuilder.AppendFormat("{0} : ",
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes[code].Meaning);
}
}
}
}
}
The WarnCodeValue class:
class WarnCodeValue
{
public string Meaning
{ get; set; }
public Dictionary<int, WarnCodeValue> ChildNodes { get; set; }
}
Questions
Which of the above 3 approaches is better from a performance point of view?
Are there any other approaches for representing the enumeration?
Any improvements to the code?
Consider using classes instead of enums, you then use a singleton for each value and can use the type system to build a tree, including virtual methods to produce error txt etc. (This can sometimes be a good option, but can also lead you into lots of problems if it does not fit well)
You could use FlagsAttribute.
For instance you could do something like this:
[FlagsAttribute]
enum WarnCodes
{
None= 0x0000,
Current = 0x2000,
// second level of hierarchy
DeviceInputSide = 0x0100,
DeviceOutputSide = 0x0200,
// third level of hierarchy
ShortToEarth = 0x0020,
ShortCircuit = 0x0030,
// fourth level of hierarchy
InPhase1 = 0x0001,
InPhase2 = 0x0002
}
You can test it like this:
int[] testVals = {0x0000, 0x2000, 0x2130, 0x2122, 0x2121, 0x2131};
foreach(var val in testVals)
{
Console.WriteLine( "{0,4:X} - {1}",
val, ( (WarnCodes)val ).ToString( ) );
}
Second attempt... You could implement your own tree structure where each node has a single-digit hexadecimal representation and a code like 0x2121 represents a branch of the tree:
>2 - (current)
/ \
(device input side)>1 2 (device output side)
/\ /\
>2 (short to earth)
/\
>1 (in phase 1)
So, to read what 0x2121 means, we follow the corresponding branch of the tree and (for each node) we read the message it contains.
Here's a quick and dirty implementation of the tree:
public class TreeNode
{
private List<TreeNode> _children;
public int hex {get; private set;}
public string meaning {get; private set;}
public IList<TreeNode> children {
get{
return _children.AsReadOnly();
}
}
public TreeNode(int hex, string meaning)
{
this.hex = hex;
this.meaning = meaning;
_children = new List<TreeNode>();
}
public TreeNode addChild(int hex, string meaning)
{
if(hex<=0 || hex >=16) throw new ArgumentOutOfRangeException("hex");
if(GetChildByCode(hex)!=null) throw new Exception("a child with code " +
hex.ToString() + " already exists");
var child = new TreeNode(hex,meaning);
_children.Add(child);
return child;
}
public TreeNode TryAddChild(int hex, string meaning)
{
if(hex<=0 || hex >=16) throw new ArgumentOutOfRangeException("hex");
var chd = GetChildByCode(hex);
if(chd==null) {
chd = new TreeNode(hex,meaning);
_children.Add(chd);
}
return chd;
}
public void AddBranch(int hexPath, string[] meanings)
{
var lst = intToList(hexPath,16,new LinkedList<int>()).ToList();
var curNode = this;
for(int i = 0; i<lst.Count; i++)
{
curNode = curNode.TryAddChild(lst[i], meanings[i]);
}
}
public TreeNode GetChildByCode(int hex)
{
return
(from c in _children
where c.hex == hex
select c).SingleOrDefault();
}
public string getMessagesByPath(int hexPath)
{
var lst = intToList(hexPath,16,new LinkedList<int>());
var msgs = getMessagesByPath(lst, new List<string>(),this);
return
(msgs == null || msgs.Count==0) ?
"None":
msgs.Aggregate((s1, s2) => s1 + ": " + s2);
}
// recursively follow the branch and read the node messages
protected IList<string> getMessagesByPath(LinkedList<int> hexPath, IList<string> accString, TreeNode curNode)
{
if(hexPath.Count == 0 || hexPath.First.Value == 0 || curNode==null)
return accString;
else
{
var chd = curNode.GetChildByCode(hexPath.First.Value);
string meaning = (chd==null)? "not found": chd.meaning;
accString.Add(meaning);
hexPath.RemoveFirst();
return getMessagesByPath(hexPath,accString,chd);
}
}
// convert the code to a list of digits in the given base (in this case 16)
// this could be an extension method for int
private LinkedList<int> intToList(int theInt, int theBase, LinkedList<int> acc)
{
if(theInt < theBase)
{
acc.AddFirst(theInt);
return acc;
}
else
{
acc.AddFirst(theInt % theBase);
return intToList(theInt/theBase, theBase, acc);
}
}
}
you can populate the tree this way:
var root = new TreeNode(0,"root");
root.AddBranch(0x2121, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase I"});
root.AddBranch(0x2122, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase II"});
root.AddBranch(0x2123, new string[] {"Current", "DeviceInputSide", "Short to Earth", "In phase III"});
root.AddBranch(0x2221, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase I"});
root.AddBranch(0x2222, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase II"});
root.AddBranch(0x2223, new string[] {"Current", "DeviceOutputSide", "Short to Earth", "In phase III"});
// ...
this way you get total control over the hierarchical structure of your codes and can implement checks so that the structure itself cannot be corrupted. Searching a message remains easy and (since it does not process a code after the first 0), a search for 0x2000 should be more efficient because only the 2 is actually processed.
//search meaning of path
root.getMessagesByPath(0x2122)
Found that a modified version of Approach 3 is most suitable. Thanks to #paolo for helping me come up with the answer.
Modified Approach 3
The xml containing the codes:
<?xml version="1.0" encoding="utf-8" ?>
<WarnCodes>
<code hex="2000" meaning="Current">
<code hex="2100" meaning="Current, Device Input side">
<code hex="2120" meaning="Short to Earth">
<code hex="2121" meaning="Short to earth in Phase L1"/>
<code hex="2122" meaning="Short to earth in Phase L2"/>
</code>
</code>
</code>
<code hex="3000" meaning="Voltage"/>
</WarnCodes>
The WarnCodeValue class:
class WarnCodeValue
{
public string Meaning
{ get; set; }
public string ConcatenatedMeaning
{ get; set; }
public Dictionary<int, WarnCodeValue> ChildNodes
{ get; set; }
}
The singleton processor class (to retrieve the meaning of a code):
sealed class WarnCodeProcessor
{
private static Dictionary<int, WarnCodeValue> warnCodesDictionary;
private static volatile WarnCodeProcessor _instance;
private static object instanceLockCheck = new object();
public static WarnCodeProcessor Instance
{
get
{
lock (instanceLockCheck)
{
if (_instance == null)
{
_instance = new WarnCodeProcessor();
}
}
return _instance;
}
}
private WarnCodeProcessor()
{
warnCodesDictionary = new Dictionary<int, WarnCodeValue>();
string currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string settingsFilePath = Path.Combine(currentDirectory, "WarnCodes.xml");
XElement rootElement = XElement.Load(settingsFilePath);
warnCodesDictionary = GetChildren(rootElement, string.Empty);
}
public string GetConcatenatedMeaning(int code)
{
string concatenatedMeaning = string.Empty;
int firstLevel = code & 0xF000;
int secondLevel = code & 0xFF00;
int thirdLevel = code & 0xFFF0;
if (warnCodesDictionary.ContainsKey(firstLevel))
{
concatenatedMeaning = warnCodesDictionary[firstLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes.ContainsKey(secondLevel))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes.ContainsKey(thirdLevel))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].
ChildNodes[thirdLevel].ConcatenatedMeaning;
if (warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes != null &&
warnCodesDictionary[firstLevel].ChildNodes[secondLevel].ChildNodes[thirdLevel].ChildNodes.ContainsKey(code))
{
concatenatedMeaning =
warnCodesDictionary[firstLevel].
ChildNodes[secondLevel].
ChildNodes[thirdLevel].
ChildNodes[code].ConcatenatedMeaning;
}
}
}
}
return concatenatedMeaning;
}
private static Dictionary<int, WarnCodeValue> GetChildren(XElement element, string concatenatedMeaning)
{
string elementMeaning = string.Empty;
XAttribute attribute = element.Attribute("meaning");
if (attribute != null)
{
elementMeaning = attribute.Value;
concatenatedMeaning =
string.IsNullOrEmpty(concatenatedMeaning) ? elementMeaning : string.Format("{0} : {1}", concatenatedMeaning, elementMeaning);
}
if (element.Descendants().Count() > 0)
{
Dictionary<int, WarnCodeValue> childNodeDictionary = new Dictionary<int, WarnCodeValue>();
foreach (XElement childElement in element.Elements())
{
int hex = Convert.ToInt32(childElement.Attribute("hex").Value, 16);
string meaning = childElement.Attribute("meaning").Value;
Dictionary<int, WarnCodeValue> dictionary = GetChildren(childElement, concatenatedMeaning);
WarnCodeValue warnCodeValue = new WarnCodeValue();
warnCodeValue.ChildNodes = dictionary;
warnCodeValue.Meaning = meaning;
warnCodeValue.ConcatenatedMeaning =
string.IsNullOrEmpty(concatenatedMeaning) ? meaning : string.Format("{0} : {1}", concatenatedMeaning, meaning);
childNodeDictionary.Add(hex, warnCodeValue);
}
return childNodeDictionary;
}
return null;
}
}
Usage
string concatenatedMeaning = WarnCodeProcessor.Instance.GetConcatenatedMeaning(0x2121);
Output
Current : Current, Device Input side : Short to Earth : Short to earth in Phase L1
Possible modifications include a GetMeaning(code) to retrieve the original meaning of the code, rather than the concatenated meaning.
Related
I am having a bit of a frustrating time finding a simple method to compare and prove that the contents of two lists are equal. I have looked at a number of solutions on stackoverflow but I have not been successful. Some of the solutions look like they will require a large amount of work to implement and do something that on the face of it to my mind should be simpler, but perhaps I am too simple to realize that this cannot be done simply :)
I have created a fiddle with some detail that can be viewed here: https://dotnetfiddle.net/cvQr5d
Alternatively please find the full example below, I am having trouble with the object comparison method (variable finalResult) as it's returning false and if the content were being compared I would expect the value to be true:
using System;
using System.Collections.Generic;
using System.Linq;
public class ResponseExample
{
public Guid Id { get; set; } = Guid.Parse("00000000-0000-0000-0000-000000000000");
public int Value { get; set; } = 0;
public string Initials { get; set; } = "J";
public string FirstName { get; set; } = "Joe";
public string Surname { get; set; } = "Blogs";
public string CellPhone { get; set; } = "0923232199";
public bool EmailVerified { get; set; } = false;
public bool CellPhoneVerified { get; set; } = true;
}
public class Program
{
public static void Main()
{
var responseOne = new ResponseExample();
var responseTwo = new ResponseExample();
var responseThree = new ResponseExample();
var responseFour = new ResponseExample();
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseThree);
objectListTwo.Add(responseFour);
bool result = objectListOne.Count == objectListTwo.Count();
Console.WriteLine($"Count: {result}");
bool finalResult = ScrambledEquals<ResponseExample>(objectListOne, objectListTwo);
Console.WriteLine($"Object compare: {finalResult}");
}
//https://stackoverflow.com/a/3670089/3324415
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
var cnt = new Dictionary<T,
int>();
foreach (T s in list1)
{
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T s in list2)
{
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
}
As people in comments have pointed out this will not work as comparing a complex type by default compares whether the reference is the same. Field by field comparison will not work without implementing equality methods (and then you would need to overload GetHashCode and so on). See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-5.0
However, if you can use c# 9, which is what you have in the fiddle you can define the type as a record instead of class. Records have built in field by field comparison. See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#characteristics-of-records
So public class ResponseExample would become public record ResponseExample and your code works as you expect.
Use Enumerable.All<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>) Method which Determines whether all elements of a sequence satisfy a condition.
Once you have initilized your two List
list1.All(x=>list2.Contains(x))
This works by ensuring that all elements in list2 are containted in list1 otherwise returns false
Your method as is will compare if the 2 lists contain the same objects. So it is returning false as there are 4 different objects. If you create your list like this, using the same objects, it will return true:
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseTwo);
objectListTwo.Add(responseOne);
To get a true value when the contents of the objects are the same you could serialize the objects into a json string like this:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
JavaScriptSerializer json = new JavaScriptSerializer();
var cnt = new Dictionary<string,
int>();
foreach (T _s in list1)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T _s in list2)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
If the performance is not a big deal, you can use Newtonsoft.Json. We will be able to compare different types of objects as well as run a deep equals check.
First install the package:
Install-Package Newtonsoft.Json
Here is the code snip:
public static bool DeepEqualsUsingJson<T>(IList<T> l1, IList<T> l2)
{
if (ReferenceEquals(l1, l2))
return true;
if (ReferenceEquals(l2, null))
return false;
if (l1.Count != l2.Count)
return false;
var l1JObject = l1.Select(i => JObject.FromObject(i)).ToList();
var l2JObject = l2.Select(i => JObject.FromObject(i)).ToList();
foreach (var o1 in l1JObject)
{
var index = l2JObject.FindIndex(o2 => JToken.DeepEquals(o1, o2));
if (index == -1)
return false;
l2JObject.RemoveAt(index);
}
return l2JObject.Count == 0;
}
I have to create an indented navigation menu using below data from a .csv file:
ID;MenuName;ParentID;isHidden;LinkURL1;Company;NULL;False;/company2;About Us;1;False;/company/aboutus3;Mission;1;False;/company/mission4;Team;2;False;/company/aboutus/team5;Client 2;10;False;/references/client26;Client 1;10;False;/references/client17;Client 4;10;True;/references/client48;Client 5;10;True;/references/client510;References;NULL;False;/references
Using this data I have to develop an application that will parse the file and present the content in a console as the example below:
. Company.... About Us....... Team.... Mission. References.... Client 1.... Client 2
Menu items should be indented (depending on the parent), hidden items (isHidden==true) shouldn't be presented and items should be ordered alphabetically. So far I tried:
using (StreamReader sr = new StreamReader(#"file.csv"))
{
// Read the stream to a string, and write the string to the console.
string [] lines = sr.ReadToEnd().Split(/*';', */'\n');
for (int i = 1; i < lines.Length; i++)
{
Console.WriteLine($"String no {i} is : {lines[i-1]}");
}
}
With this i'm getting the lines but I'm stuck after that. I'm new in coding so any help will be appreciated :)
heres some code that should help you get off.
Working sample:
https://dotnetfiddle.net/L37Gjr
It first parses the data to a seperate object. This then gets used to build a m-ary tree, or a hierachical structure of connected nodes. (a node has a reference to 0 or more children).
https://en.wikipedia.org/wiki/M-ary_tree
Then tree traversal (use google if you need to know more) is used to insert and print the output, There is still something wrong however. it now uses level order traversal to print, this however comes up with an error:
Found root:1 - Company
Found root:10 - References
-------------------
1 - Company
2 - About Us
3 - Mission
4 - Team
10 - References
6 - Client 1
5 - Client 2
As you can see, it prints 4 - Team on the wrong level. I'll leave it to you to fix it (because i ran out of time), and if not i hope i gave you plenty ideas to go off and research on your own.
// sample for https://stackoverflow.com/questions/61395486/read-csv-file-and-return-indented-menu-c-sharp by sommmen
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public class Node<T>
{
public T Data {get;set;}
public List<Node<T>> Children { get; set;}
public Node()
{
Children = new List<Node<T>>();
}
// Tree traversal in level order
public List<Node<T>> LevelOrder()
{
List<Node<T>> list = new List<Node<T>>();
Queue<Node<T>> queue = new Queue<Node<T>>();
queue.Enqueue(this);
while(queue.Count != 0)
{
Node<T> temp = queue.Dequeue();
foreach (Node<T> child in temp.Children)
queue.Enqueue(child);
list.Add(temp);
}
return list;
}
public List<Node<T>> PreOrder()
{
List<Node<T>> list = new List<Node<T>>();
list.Add(this);
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
return list;
}
public List<Node<T>> PostOrder()
{
List<Node<T>> list = new List<Node<T>>();
foreach (Node<T> child in Children)
list.AddRange(child.PreOrder());
list.Add(this);
return list;
}
}
public class Entity
{
public int id {get;set;}
public string menuName {get;set;}
public int? parentID {get;set;}
public bool isHidden {get;set;}
public string linkURL {get;set;}
}
public static void Main()
{
var data = #"ID;MenuName;ParentID;isHidden;LinkURL
1;Company;NULL;False;/company
2;About Us;1;False;/company/aboutus
3;Mission;1;False;/company/mission
4;Team;2;False;/company/aboutus/team
5;Client 2;10;False;/references/client2
6;Client 1;10;False;/references/client1
7;Client 4;10;True;/references/client4
8;Client 5;10;True;/references/client5
10;References;NULL;False;/references";
var lines = data.Split('\n');
var rootNodes = new List<Node<Entity>>();
var childItems = new List<Entity>();
// Parse the data to entities
// Items without a parent are used as rootnodes to build a tree
foreach(var row in lines.Skip(1))
{
var columns = row.Split(';');
var id = Convert.ToInt32(columns[0]);
var menuName = columns[1];
var parentID = ToNullableInt(columns[2]);
var isHidden = Convert.ToBoolean(columns[3]);
var linkURL = columns[4];
var entity = new Entity()
{
id = id,
menuName = menuName,
parentID = parentID,
isHidden = isHidden,
linkURL = linkURL
};
if(parentID == null)
{
Console.WriteLine("Found root:" + entity.id + " - " + entity.menuName);
rootNodes.Add(new Node<Entity>()
{
Data = entity
});
}
else
{
childItems.Add(entity);
}
}
// Add the childElements to their appropriate rootnode
foreach(var rootNode in rootNodes)
{
foreach(var childItem in childItems.OrderBy(a=>a.parentID).ThenBy(b=>b.menuName))
{
var newNode = new Node<Entity>()
{
Data = childItem
};
Insert(rootNode, newNode);
}
}
Console.WriteLine("-------------------");
foreach(var rootNode in rootNodes)
{
var indent = 0;
var previous = rootNode;
foreach(var node in rootNode.LevelOrder())
{
if(node.Data.isHidden) continue;
if(previous.Data.parentID != node.Data.parentID)
indent++;
for(var i = 0; i < indent; i++)
Console.Write("\t");
Console.WriteLine(node.Data.id + " - " + node.Data.menuName);
previous = node;
}
}
}
public static void Insert(Node<Entity> rootNode, Node<Entity> targetNode)
{
foreach(var current in rootNode.LevelOrder())
{
if(current.Data.id == targetNode.Data.parentID)
{
current.Children.Add(targetNode);
return;
}
}
}
public static int? ToNullableInt(string s)
{
int i;
if (int.TryParse(s, out i)) return i;
return null;
}
}
I have the following list of strings :
var files = new List<string> {"file0","file1","file2","file3" };
I would like to be able to add new files to this list, but if the inserted file is present in the list, I would like to insert custom value that will respect the following format $"{StringToBeInserted}"("{SomeCounter}
For instance : try to add "file0" and "file0" is already I would like to insert "file0(1)". If I try again to add "file0" ... I would like to insert with "file0(2)" and so on ... Also, I would like to provide a consistency, for instance if I delete "file0(1)" ... and try to add again "item0" ... I expect that "item0(1)" to be added. Can someone help me with a generic algorithm ?
I would use a HashSet<string> in this case:
var files = new HashSet<string> { "file0", "file1", "file2", "file3" };
string originalFile = "file0";
string file = originalFile;
int counter = 0;
while (!files.Add(file))
{
file = $"{originalFile}({++counter})";
}
If you have to use a list and the result should also be one, you can still use my set approach. Just initialize it with your list and the result list you'll get with files.ToList().
Well, you should create your own custom class for it, using the data structure you described and a simple class that includes a counter and an output method.
void Main()
{
var items = new ItemCountList();
items.AddItem("item0");
items.AddItem("item1");
items.AddItem("item2");
items.AddItem("item0");
items.ShowItems();
}
public class ItemCountList {
private List<SimpleItem> itemList;
public ItemCountList() {
itemList = new List<SimpleItem>();
}
public void DeleteItem(string value) {
var item = itemList.FirstOrDefault(b => b.Value == value);
if (item != null) {
item.Count--;
if (item.Count == 0)
itemList.Remove(item);
}
}
public void AddItem(string value) {
var item = itemList.FirstOrDefault(b => b.Value == value);
if (item != null)
item.Count++;
else
itemList.Add(new SimpleItem {
Value = value,
Count = 1
});
}
public void ShowItems() {
foreach (var a in itemList) {
Console.WriteLine(a.Value + "(" + a.Count + ")");
}
}
}
public class SimpleItem {
public int Count {get; set;}
public string Value {get; set;}
}
I am Currently maintaining an application written in C#. There is a new feature I need to code and I have hit a wall.
I am pulling data from a database for a different application written in VB6. There is a field that holds data for a tree list. I need to recreate this tree in my application. The data looks like this:
{
{
table1~col_b^table1~colc^=
}|
{
{
table1~col_b^table2~col_b^=
}|{
table2~col_a^table3~cola^=
}|AND
}|OR
}
I don't even know where to start. What I need to accomplish is this. {} denote an expression, | separate expressions. basically this tree should look like this:
OR
-- table1~col_b^table1~colc^=
-- AND
---- table1~col_b^table2~col_b^=
---- table2~col_a^table3~cola^=
Any help or direction in solving this problem would be great!
Thanks,
Doug
When you would define a grammar I suggest you looking at Irony.net. It allows to write parsers very easily. Sample.
And this is rough grammar for your code:
[Language("ExpressionEvaluator", "1.0", "Multi-line expression evaluator")]
public class ExpressionEvaluatorGrammar : Grammar
{
public ExpressionEvaluatorGrammar()
{
// 1. Terminals
var identifier = new RegexBasedTerminal("identifier", "[a-z\\d_^~]+");
// 2. Non-terminals
var root = new NonTerminal("root");
var block = new NonTerminal("block");
var expression = new NonTerminal("expression");
var expressions = new NonTerminal("expressions");
var prop = new NonTerminal("prop");
var op = new NonTerminal("op");
// 3. BNF rules
op.Rule = ToTerm("OR") | "AND";
prop.Rule = identifier + "=" ;
expression.Rule = "{" + (prop | block) + "}" + "|" ;
expressions.Rule = MakeStarRule(expressions, expression);
block.Rule = expressions + op;
root.Rule = "{" + block +"}";
Root = root;
//automatically add NewLine before EOF so that our BNF rules work correctly when there's no final line break in source
this.LanguageFlags = LanguageFlags.NewLineBeforeEOF;
}
}
} //namespace
It parses ok, all you need to do is add AST tree and use it.
Without any additional libraries, it's not too hard to parse this into an expression tree in pure C# code:
class TreeNode
{
private enum ParseState
{
Operator,
Expression
}
public static TreeNode ParseTree(string treeData)
{
Stack<TreeNode> parsed = new Stack<TreeNode>();
StringBuilder nodeData = new StringBuilder();
ParseState state = ParseState.Operator;
for (int charIndex = 0; charIndex < treeData.Length; charIndex++)
{
switch (treeData[charIndex])
{
case '{':
nodeData.Clear();
state = ParseState.Expression;
break;
case '\t':
case ' ':
case '\r':
case '\n':
case '|':
// ignore whitespace and |
break;
case '}':
{
if (state == ParseState.Expression)
{
state = ParseState.Operator;
parsed.Push(new TreeNodeData(nodeData.ToString()));
}
else // Operator
{
TreeNodeOperators op = (TreeNodeOperators)(Enum.Parse(typeof(TreeNodeOperators), nodeData.ToString()));
TreeNodeExpression exp = new TreeNodeExpression();
exp.Operator = op;
exp.Right = parsed.Pop();
exp.Left = parsed.Pop();
parsed.Push(exp);
}
nodeData.Clear();
}
break;
default:
nodeData.Append(treeData[charIndex]);
break;
}
}
return parsed.Pop();
}
}
enum TreeNodeOperators
{
AND,
OR
}
class TreeNodeExpression : TreeNode
{
public TreeNodeOperators Operator {get; set;}
public TreeNode Left { get; set; }
public TreeNode Right { get; set; }
}
class TreeNodeData : TreeNode
{
public string Data {get; set;}
public TreeNodeData(string data)
{
Data = data;
}
}
You can use regexp to tokenize and a stack to parse recursively like this
internal class Node
{
public string Terminal { get; set; }
public List<Node> Operands { get; set; }
}
internal static readonly Regex TokensPattern = new Regex(#"(?<ws>\s+)|{\s*(?<value>[^\s}]+)\s*}|(?<token>OR|AND|.)", RegexOptions.Compiled);
static Node parseData(string str)
{
// init stack
var stack = new Stack<Node>();
stack.Push(new Node() { Operands = new List<Node>() });
// define parser
var parser = new Dictionary<string, Action<string>>();
parser.Add("{", _ => stack.Push(new Node() { Operands = new List<Node>() }));
parser.Add("}", _ => { var top = stack.Pop(); stack.Peek().Operands.Add(top); });
parser.Add("|", _ => { });
parser.Add("AND", _ => stack.Peek().Terminal = "AND");
parser.Add("OR", _ => stack.Peek().Terminal = "OR");
parser.Add("", value => stack.Peek().Operands.Add(new Node { Terminal = value }));
// execute parser
TokensPattern.Matches(str).Cast<Match>()
.Where(m => string.IsNullOrEmpty(m.Groups["ws"].Value))
.Count(m => { parser[m.Groups["token"].Value](m.Groups["value"].Value); return false; });
// return top of the tree
return stack.Peek().Operands[0];
}
static void Main(string[] args)
{
const string str = #"{
{
table1~col_b^table1~colc^=
}|
{
{
table1~col_b^table2~col_b^=
}|{
table2~col_a^table3~cola^=
}|{cccc}|AND
}|OR
}";
// print tree function
Action<int, Node> dump = null;
dump = new Action<int, Node>((level, node) =>
{
Console.WriteLine("{0}{1}", new string(' ', level * 2), node.Terminal);
if (node.Operands != null)
node.Operands.ForEach(el => dump(level + 1, el));
});
dump(0, parseData(str));
}
I've a text file and I've to read the text file and then I've to convert the file data in the form of a table.
The file is in this form
{KeyValuePair}
{
Key1 = Value1 {next}
Key2 = Value2 {next}
Key3 = Value3 {next}
Key4 = {KeyValuePair} {
KeyA = ValueA {next}
KeyB = ValueB {next}
KeyC = ValueC {next}
}
}
and I need a output like this
My logic code is Here
StreamReader reader = new StreamReader("C:\\Users\\Kaushik Kishore\\Documents\\TextToRead.txt");
string data = reader.ReadToEnd();
//string[] stringSeparater = new string[] { "{KeyValuePair}" };
//string[] getData = data.Split(stringSeparater, StringSplitOptions.None);
//string[] separater = new string[] { "{next}" };
//string[] nextSplit = data.Split(separater, StringSplitOptions.None);
string pattern = #"(=)|(next)|(KeyValuePair)|({)|(})";
string[] output = Regex.Split(data, pattern);
foreach (string one in output)
{
Response.Write(one);
}
so thing I'm facing the problem is how to write the actual logic to extract the desired string.
Next specifies that We have to change the row in the table. each time the next keyword will occur I've to post the data in new Row.
Thanks in Advance
EDIT
I've done some effort and write some code
This is printing the data fine now I want to know how to pass the data from controller to view. when the data is coming in loop parts.
public ActionResult Index()
{
StreamReader reader = new StreamReader("C:\\Users\\Kaushik Kishore\\Documents\\Text2.txt");
string data = reader.ReadToEnd();
// replacing all tabs white space new line and everything
string trimmedData = Regex.Replace(data, #"\s", "");
string pattern = #"({next})|({KeyValuePair}{)|(}{next})";
string[] output = Regex.Split(trimmedData, pattern);
int length = output.Length;
int count = 0;
foreach (string one in output)
{
count++;
if (one == "{KeyValuePair}{")
{
Response.Write("Table Create</br>");
}
else if (count == length)
{
string[] last = one.Split('=');
foreach (string lastVal in last)
{
Response.Write(lastVal.Substring(0,lastVal.Length-1));
Response.Write('|');
}
}
else
{
string[] keyVal = one.Split('=');
foreach (string val in keyVal)
{
if (val == "{next}")
{
Response.Write("</br>");
}
else if (val == "}{next}")
{
Response.Write("Subtable End</br>");
}
else if (val == "}")
{
Response.Write("");
}
else
{
Response.Write(val);
Response.Write("|");
}
}
}
}
reader.Close();
return View();
}
This will be your controller part
public ActionResult Index()
{
ViewBag.DisplayTable = GetKeyValueDisplayContent(#"YourFilePath.Txt");
return View();
}
private string GetKeyValueDisplayContent(string fileToRead)
{
// 01 Get Data
string DataToProcess = GetDataToProcess(fileToRead);
// 02 Cleaning Data (replacing all tabs white space new line and everything)
DataToProcess = CleanDataToProcess(DataToProcess);
// 03 Retrieve Array from Data format
string[] output = GetDataInArray(DataToProcess);
// 04 Displaying Result
string DrawTable = GetDisplayHTML(output);
return DrawTable;
}
private string GetDataToProcess(string fileToRead)
{
StreamReader reader = new StreamReader(fileToRead);
string data = reader.ReadToEnd();
reader.Close();
return data;
}
private string CleanDataToProcess(string dataToProcess)
{
return Regex.Replace(dataToProcess, #"\s", "");
}
private string[] GetDataInArray(string dataToProcess)
{
string pattern = #"({next})|({KeyValuePair}{)|(}{next})";
string[] output = Regex.Split(dataToProcess, pattern);
return output;
}
private string GetDisplayHTML(string[] output)
{
int length = output.Length;
int count = 0;
StringBuilder OutputToPrint = new StringBuilder();
foreach (string one in output)
{
if (one == "{KeyValuePair}{")
{
count++;
if (count >= 2)
{
OutputToPrint.Append("<td><table border = \"1\">");
}
else
{
OutputToPrint.Append("<table border = \"1\">");
}
}
else if (one.Contains("=") == true)
{
string[] keyVal = Regex.Split(one, #"=");
OutputToPrint.Append("<tr>");
foreach (string val in keyVal)
{
if (val != "")
{
OutputToPrint.Append("<td>");
OutputToPrint.Append(WebUtility.HtmlEncode(val));
OutputToPrint.Append("</td>");
}
}
}
else if (one.Equals("{next}"))
{
OutputToPrint.Append("</tr>");
}
else if (one.Contains("}{next}") == true)
{
OutputToPrint.Append("</table></td>");
}
else if (one == "}")
{
OutputToPrint.Append("</table>");
}
else { }
}
return OutputToPrint.ToString();
}
This will be the View
<div>
#Html.Raw(ViewBag.DisplayTable)
</div>
Hope You'll find this well
if you use this little pattern, and if you use it recursively on the value capturing group, I think you can obtain what you want:
string pattern = #"(?>\s*(?<key>[^\s=]+)\s*=\s*|^\s*)(?>{KeyValuePair}\s*{\s*(?<value>(?>(?<c>{)|(?<-c>})|[^{}]+)+(?(c)(?!)))\s*}|(?<value>[^\s{]+)\s*(?<next>{next})\s*)";
pattern details:
(?> # possible begin of the match
\s*(?<key>[^\s=]+)\s*=\s* # a keyname
| # OR
^\s* # the start of the string
)
(?>
# case KeyValuePair #
{KeyValuePair} \s* { \s*
(?<value>
(?>(?<c>{)|(?<-c>})|[^{}]+)+ (?(c)(?!)) # content inside balanced curly brackets*
)
\s* }
| OR
# case next #
(?<value>
[^\s{]+ # all that is not a white character or an opening curly bracket
)
\s*
(?<next> {next} )\s* # the goal of this capture is to know in which case you are
)
(*)You can find more explanations about balancing groups here: What are regular expression Balancing Groups?
The idea is to write a recursive method that will call itself when the pattern matches the "keyValuePair" case. In the "next" case, the method records only the key/value in an array (or this kind of structure). The method must return this kind of array.
I created a parser-based solution that outputs a dictionary containing the key value pairs. It can nest {KeyValuePair}s as deep as you want.
Use like this:
string data = File.ReadAllText("data.txt");
var p = new Parser(text);
Dictionary<string, Value> dictionary = p.Parse();
A value can be either a string or a dictionary:
public abstract class Value { }
public class StringValue : Value
{
public string Value { get; private set; }
public StringValue(string value)
{
this.Value = value;
}
}
public class DictionaryValue : Value
{
public Dictionary<string, Value> Values { get; private set; }
public DictionaryValue()
{
this.Values = new Dictionary<string, Value>();
}
}
This allows for error reporting:
public class ParseError : Exception
{
public ParseError(string message)
: base(message) { }
}
The parser consists of two things.
The tokenizer, which transforms the input text to a stream of tokens:
KeyValuePair, OpenBracket, KeyOrValue(Key1) , Assign,
KeyOrValue(Value1) , Next, KeyOrValue(Key2) , Assign,
KeyOrValue(Value2) , Next, KeyOrValue(Key3) , Assign,
KeyOrValue(Value3) , Next, KeyOrValue(Key4) , Assign, KeyValuePair,
OpenBracket, KeyOrValue(KeyA) , Assign, KeyOrValue(ValueA) , Next,
KeyOrValue(KeyB) , Assign, KeyOrValue(ValueB) , Next, KeyOrValue(KeyC)
, Assign, KeyOrValue(ValueC) , Next, CloseBracket, CloseBracket, End
And then the parser, which transforms the token stream into a dictionary.
Here is the complete code:
public class Parser
{
private Tokenizer tk;
public Parser(string text)
{
this.tk = new Tokenizer(text);
}
public Dictionary<string, Value> Parse()
{
Stack<Dictionary<string, Value>> dictionaries = new Stack<Dictionary<string, Value>>();
Token t;
while ((t = tk.ReadToken()) != Token.End)
{
switch (t)
{
case Token.KeyValuePair:
t = tk.ReadToken();
if (t != Token.OpenBracket)
throw new ParseError("{KeyValuePair} should be followed by a '{'");
dictionaries.Push(new Dictionary<string, Value>());
break;
case Token.CloseBracket:
if (dictionaries.Count > 1)
dictionaries.Pop();
break;
case Token.KeyOrValue:
string key = tk.TokenValue;
t = tk.ReadToken();
if (t != Token.Assign)
throw new ParseError("Key should be followed by a '='");
t = tk.ReadToken();
if (t == Token.KeyValuePair)
{
var value = new DictionaryValue();
dictionaries.Peek().Add(key, value);
dictionaries.Push(value.Values);
}
else if (t != Token.KeyOrValue)
throw new ParseError("Value expected after " + key + " =");
else
{
string value = tk.TokenValue;
dictionaries.Peek().Add(key, new StringValue(value));
t = tk.ReadToken();
if (t != Token.Next)
throw new ParseError("{next} expected after Key value pair (" + key + " = " + value + ")");
}
break;
case Token.Error:
break;
default:
break;
}
}
return dictionaries.Peek();
}
private class Tokenizer
{
private string _data;
private int currentIndex = 0;
private string tokenValue;
public string TokenValue
{
get { return tokenValue; }
}
public Tokenizer(string data)
{
this._data = data;
}
public Token ReadToken()
{
tokenValue = string.Empty;
if (currentIndex >= _data.Length) return Token.End;
char c = _data[currentIndex];
if (char.IsWhiteSpace(c))
{
currentIndex++;
return ReadToken();
}
else if (c == '{')
{
if (TryReadBracketedToken("KeyValuePair"))
{
currentIndex++;
return Token.KeyValuePair;
}
else if (TryReadBracketedToken("next"))
{
currentIndex++;
return Token.Next;
}
else
{
currentIndex++;
return Token.OpenBracket;
}
}
else if (c == '}')
{
currentIndex++;
return Token.CloseBracket;
}
else if (c == '=')
{
currentIndex++;
return Token.Assign;
}
else
{
StringBuilder valueBuilder = new StringBuilder();
while (currentIndex < _data.Length && !char.IsWhiteSpace(c))
{
valueBuilder.Append(c);
currentIndex++;
c = _data[currentIndex];
}
tokenValue = valueBuilder.ToString();
return Token.KeyOrValue;
}
}
private bool TryReadBracketedToken(string token)
{
bool result = _data.Length > currentIndex + token.Length + 2
&& _data.Substring(currentIndex + 1, token.Length + 1) == token + "}";
if (result)
{
currentIndex++;
currentIndex += token.Length;
}
return result;
}
}
private enum Token
{
KeyValuePair,
Next,
OpenBracket,
CloseBracket,
Assign,
KeyOrValue,
End,
Error
}
}
public abstract class Value { }
public class StringValue : Value
{
public string Value { get; private set; }
public StringValue(string value)
{
this.Value = value;
}
}
public class DictionaryValue : Value
{
public Dictionary<string, Value> Values { get; private set; }
public DictionaryValue()
{
this.Values = new Dictionary<string, Value>();
}
}
public class ParseError : Exception
{
public ParseError(string message)
: base(message) { }
}