Printing out the list of nodes to the console - c#

using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
//Step 1: Get string from user
//Step 2: Find out how many times each character in the string repeats
//Step 3: find the two characters that repeat the least adding their frequency and add them together with the sum of their frequencies
//step 4: Add this new sum back into the list, wherever it will now go, higher up
//step 5: repeat theses steps, until you have a huffman tree!
namespace HuffmanTree
{
class Program
{
static void Main(string[] args)
{
HuffmanTree.GetValue();
}
}
class HuffmanTree
{
public static void GetValue()
{
Console.WriteLine("Write a string to be encoded"); //Here we are asking the user to input a string
string encodeme = Console.ReadLine(); // here the user inputs their string, which gets declared as the variable "encodeme"
Dictionary<char, int> timesRepeated = new Dictionary<char, int>();
List<Node> HuffmanNodes = new List<Node>();
foreach (char ch in encodeme.Replace(" ", string.Empty))
{
if (timesRepeated.ContainsKey(ch))
{
timesRepeated[ch] = timesRepeated[ch] + 1;
}
else
{
timesRepeated.Add(ch, 1);
}
}
foreach (KeyValuePair<char, int> item in timesRepeated.OrderByDescending(i => i.Value))
{
char key = item.Key;
int count = item.Value;
Console.WriteLine(key.ToString() + " : " + count.ToString());
HuffmanNodes.Add(new Node());
}
Console.WriteLine(HuffmanNodes(node));
}
public class Node //These are going to be the intialisation of our nodes
{
public char Symbol { get; set; }
public int Frequency { get; set; }
public Node Right { get; set; }
public Node Left { get; set; }
}
}
}
I am trying to Console.Write my Huffman tree node list so I can see what is going on. Printing Console.WriteLine(HuffmanNodes(node)); doesn't seem to work. I have tried printing several different ways such as with [i] and it just does not seem to work. This is for debugging, so I can see whats going on with each individual step.

You are trying to invoke HuffmanNodes like a method when, because it is a List<>, you'll need to enumerate it...
foreach (Node node in HuffmanNodes)
{
Console.WriteLine($"Symbol: {node.Symbol}, Frequency: {node.Frequency}");
}
...or iterate it...
for (int i = 0; i < HuffmanNodes.Count; i++)
{
Console.WriteLine($"Symbol: {HuffmanNodes[i].Symbol}, Frequency: {HuffmanNodes[i].Frequency}");
}
...to access each Node it contains.
You could also just incorporate the code to display the list in the preceding foreach loop, since that will give you access to the same Nodes in the same order as they're added to the List<>...
foreach (KeyValuePair<char, int> item in timesRepeated.OrderByDescending(i => i.Value))
{
// [snip]
Node node = new Node();
HuffmanNodes.Add(node);
Console.WriteLine($"Symbol: {node.Symbol}, Frequency: {node.Frequency}");
}
Additional notes:
You never set any of the properties of the Node you add to HuffmanNodes.
If you override the ToString() method of the Node class you could build a diagnostic string much the same way as my Console.WriteLine() calls above. This would allow you to print the properties of a Node with simply Console.WriteLine(node);.
It's odd to have a method named GetValue() that doesn't return anything.

Related

How Can I Parse String and Get Random Sentences in C#?

I'm trying to figure out how to parse a string in this format into a tree like data structure of arbitrary depth.
and after that make random sentences.
"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"
where
, means or
{ means expand
} means collapse up to parent
for example, i want to get output like this:
1) hello world planet.
2) hi earth globe!
3) goodby planet.
and etc.
The input string must be parsed. Since it can contain nested braces, we need a recursive parser. But to begin with, we need a data model to represent the tree structure.
We can have three different types of items in this tree: text, a list representing a sequence and a list representing a choice. Let's derive three classes from this abstract base class:
abstract public class TreeItem
{
public abstract string GetRandomSentence();
}
The TextItem class simply returns its text as "random sentence":
public class TextItem : TreeItem
{
public TextItem(string text)
{
Text = text;
}
public string Text { get; }
public override string GetRandomSentence()
{
return Text;
}
}
The sequence concatenates the text of its items:
public class SequenceItem : TreeItem
{
public SequenceItem(List<TreeItem> items)
{
Items = items;
}
public List<TreeItem> Items { get; }
public override string GetRandomSentence()
{
var sb = new StringBuilder();
foreach (var item in Items) {
sb.Append(item.GetRandomSentence());
}
return sb.ToString();
}
}
The choice item is the only one using randomness to pick one random item from the list:
public class ChoiceItem : TreeItem
{
private static readonly Random _random = new();
public ChoiceItem(List<TreeItem> items)
{
Items = items;
}
public List<TreeItem> Items { get; }
public override string GetRandomSentence()
{
int index = _random.Next(Items.Count);
return Items[index].GetRandomSentence();
}
}
Note that the sequence and choice items both call GetRandomSentence() recursively on their items to descend the tree recursively.
This was the easy part. Now lets create a parser.
public class Parser
{
enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }
int _index;
string _definition;
Token _token;
string _text; // If token is Token.Text;
public TreeItem Parse(string definition)
{
_index = 0;
_definition = definition;
GetToken();
return Choice();
}
private void GetToken()
{
if (_index >= _definition.Length) {
_token = Token.EndOfString;
return;
}
switch (_definition[_index]) {
case '{':
_index++;
_token = Token.LeftBrace;
break;
case '}':
_index++;
_token = Token.RightBrace;
break;
case ',':
_index++;
_token = Token.Comma;
break;
default:
int startIndex = _index;
do {
_index++;
} while (_index < _definition.Length & !"{},".Contains(_definition[_index]));
_text = _definition[startIndex.._index];
_token = Token.Text;
break;
}
}
private TreeItem Choice()
{
var items = new List<TreeItem>();
while (_token != Token.EndOfString && _token != Token.RightBrace) {
items.Add(Sequence());
if (_token == Token.Comma) {
GetToken();
}
}
if (items.Count == 0) {
return new TextItem("");
}
if (items.Count == 1) {
return items[0];
}
return new ChoiceItem(items);
}
private TreeItem Sequence()
{
var items = new List<TreeItem>();
while (true) {
if (_token == Token.Text) {
items.Add(new TextItem(_text));
GetToken();
} else if (_token == Token.LeftBrace) {
GetToken();
items.Add(Choice());
if (_token == Token.RightBrace) {
GetToken();
}
} else {
break;
}
}
if (items.Count == 0) {
return new TextItem("");
}
if (items.Count == 1) {
return items[0];
}
return new SequenceItem(items);
}
}
It consists of a lexer, i.e., a low level mechanism to split the input text into tokens. We have have four kinds of tokens: text, "{", "}" and ",". We represent these tokens as
enum Token { Text, LeftBrace, RightBrace, Comma, EndOfString }
We also have added a EndOfString token to tell the parser that the end of the input string was reached. When the token is Text we store this text in the field _text. The lexer is implemented by the GetToken() method which has no return value and instead sets the _token field, to make the current token available in the two parsing methods Choice() and Sequence().
One difficulty is that when we encounter an item, we do not know whether it is a single item or whether it is part of a sequence or a choice. We assume that the whole sentence definition is a choice consisting of sequences, which gives sequences precedence over choices (like "*" has precedence over "+" in math).
Both Choice and Sequence gather items in a temporary list. If this list contains only one item, then this item will be returned instead of a choice list or a sequence list.
You can test this parser like this:
const string example = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
var parser = new Parser();
var tree = parser.Parse(example);
for (int i = 0; i < 20; i++) {
Console.WriteLine(tree.GetRandomSentence());
}
The output might look like this:
Goodbye rock
Hi earth
Goodbye globe.
Hey world
Goodbye rock
Hi earth
Hey earth
farewell planet
Goodbye globe.
Hey world
Goodbye planet
Hello world
Hello world
Goodbye planet
Hey earth
farewell globe!
Goodbye globe.
Goodbye globe.
Goodbye planet
farewell rock
I think that can be a complicated job, for that I used this tutorial, I strongly advice you to read the entire page to understand how this works.
First, you have to pass this "tree" as an array. You can parse the string, manually set the array or whatever. That's important because there isn't a good model for that tree model so it's better if you use a already available one. Also, it's important that if you want to set a correct grammar, you'll need to add "weight" to those words and tell the code how to correctly set and in what order.
Here is the code snippet:
using System;
using System.Text;
namespace App
{
class Program
{
static void Main(string[] args)
{
string tree = "{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}";
string[] words = { "Hello", "Hi", "Hey", "world", "earth", "Goodbye", "farewell", "planet", "rock", "globe" };
RandomText text = new RandomText(words);
text.AddContentParagraphs(12, 1, 3, 3, 3);
string content = text.Content;
Console.WriteLine(content);
}
}
public class RandomText
{
static Random _random = new Random();
StringBuilder _builder;
string[] _words;
public RandomText(string[] words)
{
_builder = new StringBuilder();
_words = words;
}
public void AddContentParagraphs(int numberParagraphs, int minSentences,
int maxSentences, int minWords, int maxWords)
{
for (int i = 0; i < numberParagraphs; i++)
{
AddParagraph(_random.Next(minSentences, maxSentences + 1),
minWords, maxWords);
_builder.Append("\n\n");
}
}
void AddParagraph(int numberSentences, int minWords, int maxWords)
{
for (int i = 0; i < numberSentences; i++)
{
int count = _random.Next(minWords, maxWords + 1);
AddSentence(count);
}
}
void AddSentence(int numberWords)
{
StringBuilder b = new StringBuilder();
// Add n words together.
for (int i = 0; i < numberWords; i++) // Number of words
{
b.Append(_words[_random.Next(_words.Length)]).Append(" ");
}
string sentence = b.ToString().Trim() + ". ";
// Uppercase sentence
sentence = char.ToUpper(sentence[0]) + sentence.Substring(1);
// Add this sentence to the class
_builder.Append(sentence);
}
public string Content
{
get
{
return _builder.ToString();
}
}
}
}
If the question is how to parse the text. I think maybe you can use the stack to parse it.
"{{Hello,Hi,Hey} {world,earth},{Goodbye,farewell} {planet,rock,globe{.,!}}}"
Basically, you push char in the stack when you read a char is not '}'. And when you get a '}', you pop from stack many time, until you reach a '{'.
But it has more details, because you have a rule ',' for OR.
The parsing is like do the calculation by stack. This is the way how you handle parenthesis for equation.

read csv file and return indented menu c#

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

Parse string by space, Place each item into linked list. NO ARRAYS

I'm currently working with linked lists. My assignment is asking me to make use of them in order to create a reverse polish calculator. I am having trouble understanding how to place each part of the string into the stack. My code currently looks like this
Main:
static void Main(string[] args)
{
LinkedList data = new LinkedList();
string UserData = Console.ReadLine();
Console.WriteLine("Enter a calculation in polish notation.");
data.Parse(UserData);
}
here i am simply accessing the parse method within the "LinkedList" class that receives the string "UserData". From there I'd like to start reading through the string and placing each part into the stack. I am not clear on how I can achieve that without arrays.
Here is my parse method:
public void Parse(string Input)
{
int data;
if(Input!=null)
{
}
}
As you can see I have nothing in it. In my head I'm thinking I should parse the string, then place each item in the stack but I'm more than likely wrong. Here is the entire LinkedList Class just in case:
public class LinkedList
{
private Node FrontHead;
public void printNodes()
{
Node Current = FrontHead;
while (Current != null)
{
Console.WriteLine(Current.data);
Current = Current.next;
}
}
public void Parse(string Input)
{
int data;
if(Input!=null)
{
}
}
public void Add(Object data)
{
Node NextToadd = new Node();
NextToadd.data = data;
NextToadd.next = FrontHead;
FrontHead = NextToadd;
}
public void last(Object data)
{
if (FrontHead == null)
{
FrontHead = new Node();
FrontHead.data = data;
FrontHead.next = null;
}
else
{
Node New = new Node();
New.data = data;
Node Crt = FrontHead;
while (Crt.next != null)
{
Crt = Crt.next;
}
Crt.next = New;
}
}
}
And the Node Class:
public class Node
{
public Object data;
public Node next;
}
a sample run would look like this:
input:
1 2 + =
output:
3
You could do
public void Parse(string Input)
{
if(Input!=null)
{
foreach (string s in Input.Split(' '))
{
Add(s);
}
}
}
But technically speaking, Split returns an array. So it depends what you mean by "NO ARRAYS", as i'm assuming that means the linked list is not an array, but arrays can be used elsewhere
Just iterate each sub-string of given string in Parse method. and call last(Name as per your code) method to add each substring to your list
public void Parse(string Input)
{
int data;
if(Input!=null)
{
foreach(string subString in Input.Split(' '))
{
//call last method
last(subString);
}
}
}

Bug in parallel tree printing method

Classes:
public class Tree
{
public Node RootNode { get; set; }
}
public class Node
{
public int Key { get; set; }
public object Value { get; set; }
public Node ParentNode { get; set; }
public List<Node> Nodes { get; set; }
}
Methods:
This method generates a tree.
private static int totalNodes = 0;
static Tree GenerateTree()
{
Tree t = new Tree();
t.RootNode = new Node();
t.RootNode.Key = 0;
t.RootNode.Nodes = new List<Node>();
Console.WriteLine(t.RootNode.Key);
List<Node> rootNodes = new List<Node>();
rootNodes.Add(t.RootNode);
while (totalNodes <= 100000)
{
List<Node> newRootNodes = new List<Node>();
foreach (var rootNode in rootNodes)
{
for (int j = 0; j < 3; j++)
{
totalNodes++;
Console.Write(string.Format(" {0}({1}) ", totalNodes, rootNode.Key));
Node childNode = new Node() {Key = totalNodes, Nodes = new List<Node>(), ParentNode = t.RootNode};
rootNode.Nodes.Add(childNode);
newRootNodes.Add(childNode);
}
Console.Write(" ");
}
Console.WriteLine();
rootNodes = newRootNodes;
}
return t;
}
This method is supposed to print a tree, but node is null in some case:
static void PrintTreeParallel(Node rootNode)
{
List<Node> rootNodes = new List<Node>();
List<Node> newRootNodes = new List<Node>();
rootNodes.Add(rootNode);
Console.WriteLine(rootNode.Key);
while (rootNodes.Count > 0)
{
newRootNodes = new List<Node>();
Parallel.ForEach(rootNodes, node =>
{
if (node != null)
{
Console.Write(string.Format(" {0} ", node.Key));
if (node.Nodes != null)
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
}
else
{
//HOW CAN WE GET HERE?????
Debugger.Break();
Console.WriteLine(rootNodes.Count);
}
});
Console.WriteLine();
rootNodes = newRootNodes;
}
}
Execute:
static void Main(string[] args)
{
var t = GenerateTree();
Console.WriteLine("Tree generated");
PrintTreeParallel(t.RootNode);
Console.WriteLine("Tree printed paral");
Console.ReadLine();
}
Question:
What's wrong here?
Why node is null in some case?
And it happens only when there are a lot of generated nodes. For ex if there would be only 10 nodes everything is OK.
The problem is that you have this code:
Parallel.ForEach(node.Nodes, newRoot => { newRootNodes.Add(newRoot); });
Which allows multiple threads to add items to the newRootNodes list concurrently. As a commenter pointed out, List<T> is not thread-safe. What's probably happening is that one thread's Add is being interrupted by another thread's call to Add, which causes an internal index in the list to be incremented. That leaves a null value in one of the list's items.
Then, later in the loop you have:
rootNodes = newRootNodes;
Which puts the corrupted list as the list that's going to be iterated by the while.
You have a data race here:
Parallel.ForEach(node.Nodes,
newRoot => { newRootNodes.Add(newRoot); });
Adding to a list with multiple threads is not thread-safe and will cause undetermined behavior.
First try to run this part with a simple foreach and see if the problem goes away. Running two nested Parallel.ForEach statements is definitely a bizarre choice.
List<T> is indeed not thread safe, so rootNode.Nodes.Add(childNode); is dropping data in unpredictable ways.
Instead of using List<> use ConcurrentBag<> and it will all work. Note that ConcurrentBag<T> is unordered, but that is fine because you have no way of predicting the order from the threads anyway.

Recursive LINQ calls

I'm trying to build an XML tree of some data with a parent child relationship, but in the same table.
The two fields of importance are
CompetitionID
ParentCompetitionID
Some data might be
CompetitionID=1,
ParentCompetitionID=null
CompetitionID=2,
ParentCompetitionID=1
CompetitionID=3,
ParentCompetitionID=1
The broken query I have simply displays results in a flat format. Seeing that I'm working with XML, some sort of recursive functionality is required. I can do this using normal for loop recursion, but would like to see the linq version. Any help appreciated.
var results =
from c1 in comps
select new {
c.CompetitionID,
SubComps=
from sc in comps.Where (c2 => c2.CompetitionID == c1.CompetitionID)
select sc
};
Update
I found an interesting article by Chris Eargle here that shows you how to call lambda delegates recursively. Here is the code. Thanks Chris!
Func<int, int> factoral = x => x <= 1 ? 1 : x + factoral(--x);
Func<int, int> factoral = null;
factoral = x => x <= 1 ? 1 : x + factoral(--x);
^ added code formatting to show the lamba funcs
The trick is to assign null to the Func delegate first.
Don't know how to write a recursive LINQ. But I think no recursion is actually required here. A tree may be built in just two steps:
Dictionary<int, Competition> dic = comps.ToDictionary(e => e.CompetitionID);
foreach (var c in comps)
if (dic.ContainsKey(c.ParentCompetitionID))
dic[c.ParentCompetitionID].Children.Add(c);
var root = dic[1];
The root variable now contains the complete tree.
Here's a complete sample to test:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication2
{
class Competition
{
public int CompetitionID;
public int ParentCompetitionID;
public List<Competition> Children=new List<Competition>();
public Competition(int id, int parent_id)
{
CompetitionID = id;
ParentCompetitionID = parent_id;
}
}
class Program
{
static void Main(string[] args)
{
List<Competition> comps = new List<Competition>()
{
new Competition(1, 0),
new Competition(2,1),
new Competition(3,1),
new Competition(4,2),
new Competition(5,3)
};
Dictionary<int, Competition> dic = comps.ToDictionary(e => e.CompetitionID);
foreach (var c in comps)
if (dic.ContainsKey(c.ParentCompetitionID))
dic[c.ParentCompetitionID].Children.Add(c);
var root = dic[1];
}
}
}
I know I'm a little too late here. But you said you already had a version using foreach :) So if it should actually be recursive and use linq this would be a solution:
internal class Competition
{
public int CompetitionID;
public int ParentCompetitionID;
public Competition(int id, int parentId)
{
CompetitionID = id;
ParentCompetitionID = parentId;
}
}
internal class Node
{
public Node(int id, IEnumerable<Node> children)
{
Children = children;
Id = id;
}
public IEnumerable<Node> Children { get; private set; }
public int Id { get; private set; }
}
internal class Program
{
static void Main(string[] args)
{
var comps = new List<Competition>
{
new Competition(1, 0),
new Competition(2, 1),
new Competition(3, 1),
new Competition(4, 2),
new Competition(5, 3)
};
Node root = ToTree(0, comps);
}
static readonly Func<int, IEnumerable<Competition>, Node> ToTree =
(nodeId, competitions) => new Node(nodeId, from c in competitions where c.ParentCompetitionID == nodeId select ToTree(c.CompetitionID, competitions));
}
You can get a tree like structure, combining LINQ and recursion with delegates. In this example I use a XML structure like this:
<Competitions>
<Competition ID="1" />
<Competition ID="2" ParentCompetitionID="1" />
<Competition ID="3" ParentCompetitionID="1" />
<Competition ID="4" />
</Competitions>
So to store node data in code and facilitate navigation, create a class like this:
class Competition
{
public int CompetitionID { get; set; }
public IEnumerable<Competition> Childs { get; set; }
}
Now using Linq to XML you load the xml file into an XDocument. After that declare a delegate that iterates over all the xml elements inside the document selecting nodes that have an id mathing the delegate's id paremeter. When selecting each node, it calls to the delegate again, passing the id of the parent node to look for. It first starts with the id parameter set to null, so, selecting firts the root nodes:
var doc = XDocument.Load("tree.xml");
//Declare the delegate for using it recursively
Func<int?, IEnumerable<Competition>> selectCompetitions = null;
selectCompetitions = (int? id) =>
{
return doc.Elements("Competitions").Elements().Where(c =>
{
//If id is null return only root nodes (without ParentCompetitionID attribute)
if (id == null)
return c.Attribute("ParentCompetitionID") == null;
else
//If id has value, look for nodes with that parent id
return c.Attribute("ParentCompetitionID") != null &&
c.Attribute("ParentCompetitionID").Value == id.Value.ToString();
}).Select(x => new Competition()
{
CompetitionID = Convert.ToInt32(x.Attribute("ID").Value),
//Always look for childs with this node id, call again to this
//delegate with the corresponding ID
Childs = selectCompetitions(Convert.ToInt32(x.Attribute("ID").Value))
});
};
var competitions = selectCompetitions(null);
To test it you can do a simply recurring method that prints the tree to the console:
private static void Write(IEnumerable<Competition> competitions, int indent)
{
foreach (var c in competitions)
{
string line = String.Empty;
for (int i = 0; i < indent; i++)
{
line += "\t";
}
line += "CompetitionID = " + c.CompetitionID.ToString();
Console.WriteLine(line);
if (c.Childs != null && c.Childs.Count() > 0)
{
int id = indent + 1;
Write(c.Childs, id);
}
}
}
Hope it helps!
I've done something very similar using LINQ's group by
I don't use the query syntax of LINQ, so forgive me if this is wrong:
var results = from c in comps
group c by c.ParentCompetitionID into g
select new { ParentId = g.Key, ChildId = g };
Of course, this would be better if your classes looked something like:
class Competition {
int Id;
string Description;
Competition ParentCompetition;
}
Then, instead of grouping by ID only, you can group by the entire competition, which makes generating the XML faster and easier.
var results = from c in comps
group c by c.ParentCompetition into g
select new { Parent = g.Key, Child = g };
class Competition
{
int ID { get; set;}
int ParentID { get; set; }
IEnumerable<Competition> Children { get; set; }
}
public IEnumerable<Competition> GetChildren(
IEnumerable<Competition> competitions, int parentID)
{
IEnumerable<Competition> children =
competitions.Where(c => c.ParentID == parentID);
if (children.Count() == 0)
return null;
return children.Select(
c => new Competition { ID = c.ID, Children = GetChildren(c.ID) };
}
Then you can just call GetChildren, passing the ID of the root as the parentID, and that will return a tree structure. You can also change the Competition object to the XML API of your choice.
I know this isn't exactly what you're looking for, but afaik LINQ doesn't support recursion. Nevertheless, the LIN part of LINQ means language integrated, which is exactly what I used.
While you can not do this with a single query (unless you are calling SQL directly with a CTE), you can limit the number of queries to the depth of the tree.
The code is too long to paste, but the basic steps are:
Collect root nodes and add to 'all' nodes
Collect nodes with parent in 'all' nodes (pass list to query)
Add nodes in step 2 to 'all' nodes
Repeat 2 - 3 till step 2 returns 0 nodes (which should be depth of tree + 1, I think).
You can minimize the amount of nodes passed to the query in step 2. SQL server tends to bomb out with a list of more than 2000 entries. (SQL Compact has no such issue though).

Categories