I've been looking for a way to implement both A* and Dijkstra to be able to get the shortest path and start to finish.
I retrieve a list of nodes and edges from a SQL database put the items into two dictionaries (Nodes and Edges) with the node/edge id as the keys.
The start (148309) and end (1483093) node I used to test, returns a result, but it visits 21 other nodes and should return 3 nodes (108.75m)
Attempting to use the pseduo code in the links below, I've managed to make it find the path but I'm struggling with the backtracing to get the actual shortest path it took. The links below don't mention this within their examples.
https://www.csharpstar.com/dijkstra-algorithm-csharp/
https://www.programmingalgorithms.com/algorithm/dijkstra's-algorithm/
https://www.dotnetlovers.com/article/234/dijkstras-shortest-path-algorithm
Objects
public class Node
{
public Node()
{
Edges = new Dictionary<long, Edge>();
}
public long Id { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
/// <summary>
/// The edges coming out of this node.
/// </summary>
public Dictionary<long, Edge> Edges { get; set; }
public double DistanceFromStart { get; set; }
public double DistanceToEnd { get; set; }
public bool Visited { get; set; }
/// <summary>
/// Specified the distance in KM between this node and the
/// specified node using their lat/longs.
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public double DistanceTo(ref Node node)
{
return DistanceHelper.DistanceTo(this, node);
}
}
public class Edge
{
public long UID { get; set; }
public long StartNodeId { get; set; }
public long EndNodeId { get; set; }
public double Distance { get; set; }
public Node EndNode { get; set; }
}
public class SPResult
{
public double Distance { get; set; }
public long[] Nodes { get; set; }
public long[] Edges { get; set; }
}
Code so far.
public static Graph graph = new Graph();
static void Main(string[] args)
{
Console.WriteLine("Starting");
//Loads the nodes and edges from a SQL database.
LoadInfrastructure(3);
var res = graph.GetShortestPathDijkstra(1483099, 1483093);
//var res = graph.GetShortestPathDijkstra(1483129, 3156256);
Console.WriteLine("Done. Press any key to exit.");
Console.ReadKey();
}
public class Graph
{
public Graph()
{
Nodes = new Dictionary<long, Node>();
Edges = new Dictionary<long, Edge>();
}
Dictionary<long, Node> Nodes { get; set; }
Dictionary<long, Edge> Edges { get; set; }
Dictionary<long, double> queue;
Stopwatch stopwatch = new Stopwatch();
public void AddNode(Node n)
{
if (Nodes.ContainsKey(n.Id))
throw new Exception("Id already in graph.");
Nodes.Add(n.Id, n);
}
public void AddEdge(Edge e)
{
if (Edges.ContainsKey(e.UID))
throw new Exception("Id already in graph.");
e.EndNode = Nodes[e.EndNodeId];
Edges.Add(e.UID, e);
Nodes[e.StartNodeId].Edges.Add(e.UID, e);
}
public SPResult GetShortestPathDijkstra(long start, long end)
{
return GetShortestPathDijkstra(Nodes[start], Nodes[end]);
}
public SPResult GetShortestPathDijkstra(Node start, Node end)
{
if (!Nodes.ContainsKey(start.Id))
throw new Exception("Start node missing!");
if (!Nodes.ContainsKey(end.Id))
throw new Exception("End node missing!");
Console.WriteLine($"Finding shortest path between {start.Id} and {end.Id}...");
ResetNodes(null);
stopwatch.Restart();
Node current = start;
current.DistanceFromStart = 0;
queue.Add(start.Id, 0);
while (queue.Count > 0)
{
long minId = queue.OrderBy(x => x.Value).First().Key;
current = Nodes[minId];
queue.Remove(minId);
if (minId == end.Id)
{
current.Visited = true;
break;
}
foreach (var edge in current.Edges.OrderBy(ee => ee.Value.Distance))
{
var endNode = edge.Value.EndNode;
if (endNode.Visited)
continue;
double distance = current.DistanceFromStart + edge.Value.Distance;
if (queue.ContainsKey(endNode.Id))
{
if (queue[endNode.Id] > distance)
{
queue[endNode.Id] = endNode.Id;
Nodes[endNode.Id].DistanceFromStart = distance;
}
}
else
{
Nodes[endNode.Id].DistanceFromStart = distance;
queue.Add(endNode.Id, distance);
}
}
current.Visited = true;
}
stopwatch.Stop();
Console.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
Debug.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
**//Get path used.**
var rr = Nodes.Values.Where(nn => nn.Visited).OrderBy(nn => nn.DistanceFromStart).ToList();
return null;
}
public SPResult GetShortestPathAstar(long start, long end)
{
return GetShortestPathAstar(Nodes[start], Nodes[end]);
}
public SPResult GetShortestPathAstar(Node start, Node end)
{
ResetNodes(end);
start.DistanceFromStart = 0;
throw new NotImplementedException();
}
private void ResetNodes(Node endNode)
{
queue = new Dictionary<long, double>();
foreach (var node in Nodes)
{
node.Value.DistanceFromStart = double.PositiveInfinity;
node.Value.Visited = false;
if (endNode != null)
node.Value.DistanceToEnd = node.Value.DistanceTo(ref endNode);
}
}
}
I managed to use a YouTube video to go through the pseudocode and implement both Dijkstra and A* algorithms.
https://www.youtube.com/watch?v=nhiFx28e7JY
https://www.youtube.com/watch?v=mZfyt03LDH4
Program.cs (Snippet)
LoadInfrastructureFromSQL();
var resA5 = graph.GetShortestPathAstar(startId, endId);
Node
public class Node
{
public Node()
{
}
public Node(long id, double lat, double lon) : this()
{
Id = id;
Latitude = lat;
Longitude = lon;
}
public long Id { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Gcost { get; set; }
public double Hcost { get; set; }
public double Fcost => Gcost + Hcost;
public Node Parent { get; set; }
/// <summary>
/// The edges coming out of this node.
/// </summary>
public Dictionary<long, Edge> Edges { get; set; }
public void AddEdges(Edge e)
{
if (Edges == null)
Edges = new Dictionary<long, Edge>();
if (Edges.ContainsKey(e.UID))
throw new Exception($"Edge id {e.UID} already exists.");
Edges.Add(e.UID, e);
}
public double DistanceTo(Node point)
{
double p = 0.017453292519943295;
double a = 0.5 - Math.Cos((point.Latitude - Latitude) * p) / 2 + Math.Cos(Latitude * p) * Math.Cos(point.Latitude * p) * (1 - Math.Cos((point.Longitude - Longitude) * p)) / 2;
return 12742 * Math.Asin(Math.Sqrt(a));
}
}
Edge
public class Edge
{
public Edge()
{
}
public Edge(long uid, long id, double distance)
{
UID = uid;
WayId = id;
Distance = distance;
}
public Edge(long uid, long id, double distance, long startNode, long endNode) : this(uid, id, distance)
{
StartNodeId = startNode;
EndNodeId = endNode;
}
/// <summary>
/// Unique way id for every single edge.
/// </summary>
public long UID { get; set; }
/// <summary>
/// Duplicate edges will share the same WayId i.e. if the way is bi-directional.
/// </summary>
public long WayId { get; set; }
public long StartNodeId { get; set; }
public long EndNodeId { get; set; }
public Node EndNode { get; set; }
public double Distance { get; set; }
}
Result (Optional)
public class SPResult
{
public double Distance { get; set; }
public Node[] Nodes { get; set; }
public long NodesChecked { get; set; }
public Edge[] Edges { get; set; }
public long CalculationTime { get; set; }
}
Graph and Algorithm
//Routing algorithm taken from https://www.youtube.com/watch?time_continue=5&v=-L-WgKMFuhE
public class Graph
{
public Graph()
{
Nodes = new Dictionary<long, Node>();
Edges = new Dictionary<long, Edge>();
}
Dictionary<long, Node> Nodes { get; set; }
Dictionary<long, Edge> Edges { get; set; }
Dictionary<long, Node> open;
Dictionary<long, Node> closed;
Stopwatch stopwatch = new Stopwatch();
public void AddNode(Node n)
{
if (Nodes.ContainsKey(n.Id))
throw new Exception("Id already in graph.");
Nodes.Add(n.Id, n);
}
public void AddEdge(Edge e)
{
e.EndNode = Nodes[e.EndNodeId];
Edges.Add(e.UID, e);
Nodes[e.StartNodeId].AddEdges(e);
}
public Node FindClosestNode(double lat, double lon, int radius)
{
Node n = new Node();
n.Latitude = lat;
n.Longitude = lon;
return FindClosestNode(n, radius);
}
public Node FindClosestNode(Node node, int radius)
{
Console.WriteLine($"Finding closest node [Latitude: {Math.Round(node.Latitude, 6)}, Longitude:{Math.Round(node.Longitude, 6)}]...");
Stopwatch sw = new Stopwatch();
sw.Start();
var res = Nodes.Select(x => new { Id = x.Key, Distance = x.Value.DistanceTo(node) }).Where(nn => nn.Distance < radius).OrderBy(x => x.Distance).FirstOrDefault();
if (res != null)
{
Debug.WriteLine($"Found nearest node in {sw.ElapsedMilliseconds}ms [{res.Id}]");
return Nodes[res.Id];
}
Debug.WriteLine($"No nearest node {sw.ElapsedMilliseconds}ms [{res.Id}]");
return null;
}
#region ROUTING
public async Task<SPResult> GetShortestPathDijkstra(long start, long end)
{
return await GetShortestPathDijkstra(Nodes[start], Nodes[end]);
}
public async Task<SPResult> GetShortestPathDijkstra(Node start, Node end)
{
if (!Nodes.ContainsKey(start.Id))
throw new Exception("Start node missing!");
if (!Nodes.ContainsKey(end.Id))
throw new Exception("End node missing!");
Console.WriteLine($"Finding Dijkstra shortest path between {start.Id} and {end.Id}...");
ResetNodes(null);
stopwatch.Restart();
open.Add(start.Id, start);
start.Gcost = 0;
Node current = start;
while (true)
{
long lowest = open.OrderBy(qq => qq.Value.Fcost).FirstOrDefault().Key;
current = Nodes[lowest];
open.Remove(lowest);
closed.Add(lowest, current);
if (current.Id == end.Id)
break;
foreach (var neigh in current.Edges)
{
Node edgeEnd = neigh.Value.EndNode;
if (closed.ContainsKey(edgeEnd.Id))
continue;
double distance = current.Gcost + neigh.Value.Distance;
if (distance < edgeEnd.Gcost || !open.ContainsKey(edgeEnd.Id))
{
edgeEnd.Gcost = distance;
edgeEnd.Parent = current;
if (!open.ContainsKey(edgeEnd.Id))
open.Add(edgeEnd.Id, edgeEnd);
}
}
}
stopwatch.Stop();
Console.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
Debug.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
SPResult spr = new SPResult();
spr.CalculationTime = stopwatch.ElapsedMilliseconds;
spr.Distance = current.Gcost;
var traceback = Traceback(current);
spr.Nodes = traceback.Item1;
spr.Edges = traceback.Item2;
spr.NodesChecked = closed.Count + open.Count;
return spr;
}
public async Task<SPResult> GetShortestPathAstar(long start, long end)
{
return await GetShortestPathAstar(Nodes[start], Nodes[end]);
}
public async Task<SPResult> GetShortestPathAstar(Node start, Node end)
{
if (!Nodes.ContainsKey(start.Id))
throw new Exception("Start node missing!");
if (!Nodes.ContainsKey(end.Id))
throw new Exception("End node missing!");
Console.WriteLine($"Finding A* shortest path between {start.Id} and {end.Id}...");
ResetNodes(end);
stopwatch.Restart();
start.Gcost = 0;
Node current = start;
open.Add(start.Id, current);
while (true)
{
if (open.Count() == 0)
return null;
long lowest = open.OrderBy(qq => qq.Value.Fcost).FirstOrDefault().Key;
current = Nodes[lowest];
open.Remove(lowest);
closed.Add(lowest, current);
if (current.Id == end.Id)
break;
foreach (var neigh in current.Edges)
{
Node edgeEnd = neigh.Value.EndNode;
if (closed.ContainsKey(edgeEnd.Id))
continue;
double distance = current.Gcost + neigh.Value.Distance;
if (distance < (edgeEnd.Gcost) || !open.ContainsKey(edgeEnd.Id))
{
edgeEnd.Gcost = distance;
edgeEnd.Hcost = current.DistanceTo(end);
edgeEnd.Parent = current;
if (!open.ContainsKey(edgeEnd.Id))
open.Add(edgeEnd.Id, edgeEnd);
}
}
}
stopwatch.Stop();
Console.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
Debug.WriteLine($"Found shortest path between {start.Id} and {end.Id} in {stopwatch.ElapsedMilliseconds}ms.");
SPResult spr = new SPResult();
spr.CalculationTime = stopwatch.ElapsedMilliseconds;
spr.Distance = current.Gcost;
var traceback = Traceback(current);
spr.Nodes = traceback.Item1;
spr.Edges = traceback.Item2;
spr.NodesChecked = closed.Count + open.Count;
return spr;
}
private (Node[],Edge[]) Traceback(Node current)
{
Stopwatch sw = new Stopwatch();
sw.Start();
List<Node> nodes = new List<Node>();
List<Edge> edges = new List<Edge>();
nodes.Add(current);
while (current.Parent != null)
{
edges.Add(current.Parent.Edges.FirstOrDefault(ee => ee.Value.EndNodeId == current.Id).Value);
current = current.Parent;
nodes.Add(current);
}
Debug.WriteLine($"Traceback in {sw.ElapsedMilliseconds}ms.");
return nodes.Reverse<Node>().ToArray();
}
private void ResetNodes(Node endNode)
{
open = new Dictionary<long, Node>();
closed = new Dictionary<long, Node>();
foreach (var node in Nodes)
{
node.Value.Gcost = double.PositiveInfinity;
node.Value.Hcost = double.PositiveInfinity;
}
}
#endregion
}
Related
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.
I'm currently trying to scrape a cannabis strain database as it is no longer being maintained. I seem to be running into an issue where table rows are skipped in my logic but it really doesn't make sense, it's like a break is being called when I'm iterating through the //tr elements of the table that is storing the chemical reports.
Is it something like the δ α symbols in the next row. I've tried regex replacing them out to now luck. Any help would be appreciated all code is in a single class console app.
Issue is in the table.ChildNodes not iterating all the way through. Located in the ParseChemical() method.
Sample Page: http://ocpdb.pythonanywhere.com/ocpdb/420/
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.Design;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using HtmlAgilityPack;
using Microsoft.VisualBasic.CompilerServices;
namespace CScrape
{
class Program
{
private static readonly HttpClient Client = new HttpClient();
private static readonly string BaseUri = "http://ocpdb.pythonanywhere.com/ocpdb/";
private static int _startId = 420;
private static int _endId = 1519;
private static List<Lab> _labs = new List<Lab>();
private static List<ChemicalItem> _chemicalItems = new List<ChemicalItem>();
private static List<UnitOfMeasure> _uoms = new List<UnitOfMeasure>();
private static List<Strain> _strains = new List<Strain>();
static void Main(string[] args)
{
Client.DefaultRequestHeaders.Accept.Clear();
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13;
_uoms.AddRange(GetUoms());
for (var i = _startId; i <= _endId; i++)
{
var result = GetUri($"{BaseUri}{i}").Result;
_strains.Add(ParseChemical(result));
}
}
private static long AddChemicalItem(ChemicalItem item)
{
if (ChemicalExists(item.Symbol))
return _chemicalItems.FirstOrDefault(ci => ci.Symbol == item.Symbol)?.Id ?? -1;
item.Id = _chemicalItems.Count + 1;
_chemicalItems.Add(item);
return item.Id;
}
private static void UpdateChemicalItem(ChemicalItem item)
{
if (!ChemicalExists(item.Symbol)) return;
var index = _chemicalItems.IndexOf(item);
if (!(index >= 0)) return;
_chemicalItems.RemoveAt(index);
AddChemicalItem(item);
}
private static long AddLab(Lab lab)
{
if (LabExists(lab.Name))
return _labs.FirstOrDefault(l => l.Name == lab.Name)?.Id ?? -1;
lab.Id = _labs.Count + 1;
_labs.Add(lab);
return lab.Id;
}
private static async Task<string> GetUri(string uri)
{
var response = await Client.GetByteArrayAsync(uri);
return Encoding.UTF8.GetString(response, 0, response.Length - 1);
}
private static Strain ParseChemical(string html)
{
html = Regex.Replace(html, #"Δ", "Delta");
HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
var strain = new Strain();
strain.Reports ??= new List<ChemicalReport>();
try
{
strain.Name = htmlDoc.DocumentNode.SelectSingleNode("/html/body/div/div[1]/div[1]/h3/b").InnerText;
}
catch (Exception e)
{
// TODO: DOcument Exception
Console.WriteLine(e.Message);
}
if (string.IsNullOrWhiteSpace(strain.Name)) return null;
try
{
var ocpId = htmlDoc.DocumentNode.SelectSingleNode("/html/body/div/div[1]/div[2]/p/b/text()[1]");
strain.OcpId = SanitizeHtml(ocpId.InnerText).Split(':')[1];
}
catch (Exception e)
{
// TODO: Document Exception
Console.WriteLine(e.Message);
}
if (string.IsNullOrWhiteSpace(strain.OcpId)) return null;
try
{
var date = htmlDoc.DocumentNode.SelectSingleNode("/html/body/div/div[1]/div[2]/p/text()");
}
catch (Exception e)
{
// TODO: Document Exception
Console.WriteLine(e.Message);
}
var chemReport = new ChemicalReport();
chemReport.Items ??= new List<ReportItem>();
try
{
var table = htmlDoc.DocumentNode.SelectSingleNode("/html/body/div/div[2]/div[1]/table/tbody");
var children = table.ChildNodes.ToList();
// On the sample page there are 200 children here
// However it only interates through the first few and then just breaks out of the loop
foreach (var child in children)
{
var name = child.Name;
if (child.Name == "tr")
{
var infos = child.SelectNodes("th|td");
foreach (var info in infos)
{
if(string.IsNullOrWhiteSpace(info.InnerText)) continue;
if (info.InnerText.Contains("Report")) continue;
if (double.TryParse(info.InnerText, out var isNumber))
{
var last = chemReport.Items.LastOrDefault();
if (last == null) continue;
if (last.Value <= 0.0000) last.Value = isNumber;
else
{
var further = chemReport.Items.ToArray()[chemReport.Items.Count - 2];
if (further.Value <= 0.0000)
further.Value = isNumber;
}
continue;
}
var _ = new ChemicalItem
{
Name = info.InnerText,
Symbol = info.InnerText
};
_.Id = AddChemicalItem(_);
var report = new ReportItem
{
Chemical = _,
ChemicalItemId = _.Id,
UnitOfMeasureId = 1,
UoM = GetUoms()[0]
};
chemReport.Items.Add(report);
}
}
}
strain.Reports.Add(chemReport);
}
catch (Exception e)
{
// TODO: Document exception
Console.Write(e.Message);
}
return strain;
}
private static List<UnitOfMeasure> GetUoms()
{
return new List<UnitOfMeasure>
{
new UnitOfMeasure {Name = "Milligrams per Gram", Symbol = "mg/g"},
new UnitOfMeasure {Name = "Percent", Symbol = "%"}
};
}
private static string SanitizeHtml(string text, string replacement = "")
{
return Regex.Replace(text, #"<[^>]+>| |α|\n|\t", replacement);
}
private static string GetLabName(string[] split)
{
var strip = split[0].Split(':')[1];
for(var i = 1; i < split.Length - 2; i ++)
{
if (string.IsNullOrWhiteSpace(split[i])) break;
strip += $" {split[i]}";
}
return strip;
}
private static string GetSampleId(string[] split)
{
var found = false;
foreach (var item in split)
{
if (found)
return item.Split(':')[1];
if (item == "Sample") found = true;
}
return "NA";
}
private static bool LabExists(string name)
{
return _labs.Any(lab => lab.Name == name);
}
private static bool ChemicalExists(string name)
{
return _chemicalItems.Any(ci => ci.Symbol == name);
}
private static ReportItem GetReportItem(string text)
{
if (string.IsNullOrWhiteSpace(text)) return null;
ReportItem ri = null;
try
{
var clean = SanitizeHtml(text);
var check = 0;
var split = clean.Split(':');
var label = split[0];
if (string.IsNullOrWhiteSpace(label)) return null;
if (double.TryParse(label, out var invalidType)) return null;
var val = string.Empty;
if (split.Length == 1)
{
if (split[0].Contains("Total"))
{
Regex re = new Regex(#"([a-zA-Z]+)(\d+)");
Match result = re.Match(split[0]);
label = result.Groups[1].Value;
val = result.Groups[2].Value;
}
}
if(split.Length > 1)
val = split[1];
if (!ChemicalExists(label)) AddChemicalItem(new ChemicalItem {Id = _chemicalItems.Count + 1,Symbol = label});
ri = new ReportItem();
ri.Chemical = _chemicalItems.FirstOrDefault(ci => ci.Symbol == label);
ri.UoM = val.Contains("%")
? _uoms.FirstOrDefault(uom => uom.Symbol == "%")
: _uoms.FirstOrDefault(uom => uom.Symbol == "mg/g");
if (string.IsNullOrWhiteSpace(val)) return ri;
var value = val.Contains("%") ? split[1].Substring(0, val.Length - 1) : val;
ri.Value = Convert.ToDouble(value);
}
catch (Exception e)
{
// TODO: Document Exception
Console.WriteLine(e.Message);
}
return ri;
}
//private static ChemicalItem GetChemicalItem(string text)
//{
//}
public class Strain
{
public long Id { get; set; }
public string Name { get; set; }
public DateTime Created { get; set; }
public string OcpId { get; set; }
public bool IsHidden { get; set; } = false;
public virtual ICollection<ChemicalReport> Reports { get; set; }
}
public class Lab
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<ChemicalReport> Reports { get; set; }
}
public class ChemicalReport
{
public long Id { get; set; }
[ForeignKey("Lab")]
public long LabId { get; set; }
public virtual Lab Lab { get; set; }
public string SampleId { get; set; }
public DateTime Created { get; set; }
public virtual ICollection<ReportItem> Items { get; set; }
[ForeignKey("Strain")]
public long StrainId { get; set; }
public virtual Strain Strain { get; set; }
}
public class ChemicalItem
{
public long Id { get; set; }
public string Name { get; set; }
public string Symbol { get; set; }
}
public class ReportItem
{
public long Id { get; set; }
[ForeignKey("Chemical")]
public long ChemicalItemId { get; set; }
public virtual ChemicalItem Chemical { get; set; }
public double Value { get; set; }
[ForeignKey("UoM")]
public long UnitOfMeasureId { get; set; }
public virtual UnitOfMeasure UoM { get; set; }
}
public class UnitOfMeasure
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Symbol { get; set; }
}
}
}
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));
}
}
}
I have a scenario where N number of items were delivered at N locations (lat, long). I want to find group of items which are delivered near to each other(100 meters). Say for example (Item1, Item9,...Item499) were delivered such that they had distance less than 100 between them. Item9 can be more than 100 meters away from Item499 but it will be in that group if it has less than 100 meters distance from any one of the item in the group.
Find all such groups.
I want someone to point out the right algorithm. I'll then work on that. Preferable Language is C#
Below is the algorithm to solve this problem which I found here
public class ClusterCreator
{
public Dictionary<int, SimpleCluster> GetClusters(List<SimpleCluster> clusterList, int maxDistance)
{
//LOAD DATA
//var clusterList = new List<SimpleCluster>(); // LoadSimpleClusterList();
//var latitudeSensitivity = 3;
//var longitutdeSensitivity = 3;
//CLUSTER THE DATA
return ClusterTheData(clusterList, maxDistance);
}
public Dictionary<int, SimpleCluster> ClusterTheData(List<SimpleCluster> clusterList, int maxDistance)
{
//CLUSTER DICTIONARY
var clusterDictionary = new Dictionary<int, SimpleCluster>();
//Add the first node to the cluster list
if (clusterList.Count > 0)
{
clusterDictionary.Add(clusterList[0].ID, clusterList[0]);
}
//ALGORITHM
for (int i = 1; i < clusterList.Count; i++)
{
SimpleCluster combinedCluster = null;
SimpleCluster oldCluster = null;
foreach (var clusterDict in clusterDictionary)
{
//Check if the current item belongs to any of the existing clusters
if (CheckIfInCluster(clusterDict.Value, clusterList[i], maxDistance))
{
//If it belongs to the cluster then combine them and copy the cluster to oldCluster variable;
combinedCluster = CombineClusters(clusterDict.Value, clusterList[i]);
oldCluster = new SimpleCluster(clusterDict.Value);
}
}
//This check means that no suitable clusters were found to combine, so the current item in the list becomes a new cluster.
if (combinedCluster == null)
{
//Adding new cluster to the cluster dictionary
clusterDictionary.Add(clusterList[i].ID, clusterList[i]);
}
else
{
//We have created a combined cluster. Now it is time to remove the old cluster from the dictionary and instead of it add a new cluster.
clusterDictionary.Remove(oldCluster.ID);
clusterDictionary.Add(combinedCluster.ID, combinedCluster);
}
}
return clusterDictionary;
}
public static SimpleCluster CombineClusters(SimpleCluster home, SimpleCluster imposter)
{
//Deep copy of the home object
var combinedCluster = new SimpleCluster(home);
combinedCluster.LAT_LON_LIST.AddRange(imposter.LAT_LON_LIST);
combinedCluster.NAMES.AddRange(imposter.NAMES);
//Combine the data of both clusters
//combinedCluster.LAT_LON_LIST.AddRange(imposter.LAT_LON_LIST);
//combinedCluster.NAMES.AddRange(imposter.NAMES);
//Recalibrate the new center
combinedCluster.LAT_LON_CENTER = new LAT_LONG
{
LATITUDE = ((home.LAT_LON_CENTER.LATITUDE + imposter.LAT_LON_CENTER.LATITUDE) / 2.0),
LONGITUDE = ((home.LAT_LON_CENTER.LONGITUDE + imposter.LAT_LON_CENTER.LONGITUDE) / 2.0)
};
return combinedCluster;
}
public bool CheckIfInCluster(SimpleCluster home, SimpleCluster imposter, int maxDistance)
{
foreach (var item in home.LAT_LON_LIST)
{
var sCoord = new GeoCoordinate(item.LATITUDE, item.LONGITUDE);
var eCoord = new GeoCoordinate(imposter.LAT_LON_CENTER.LATITUDE, imposter.LAT_LON_CENTER.LONGITUDE);
var distance = sCoord.GetDistanceTo(eCoord);
if (distance <= maxDistance)
return true;
}
return false;
//if ((home.LAT_LON_CENTER.LATITUDE + latitudeSensitivity) > imposter.LAT_LON_CENTER.LATITUDE
// && (home.LAT_LON_CENTER.LATITUDE - latitudeSensitivity) < imposter.LAT_LON_CENTER.LATITUDE
// && (home.LAT_LON_CENTER.LONGITUDE + longitutdeSensitivity) > imposter.LAT_LON_CENTER.LONGITUDE
// && (home.LAT_LON_CENTER.LONGITUDE - longitutdeSensitivity) < imposter.LAT_LON_CENTER.LONGITUDE
// )
//{
// return true;
//}
//return false;
}
}
public class SimpleCluster
{
#region Constructors
public SimpleCluster(int id, string name, double latitude, double longitude)
{
ID = id;
LAT_LON_CENTER = new LAT_LONG
{
LATITUDE = latitude,
LONGITUDE = longitude
};
NAMES = new List<string>();
NAMES.Add(name);
LAT_LON_LIST = new List<LAT_LONG>();
LAT_LON_LIST.Add(LAT_LON_CENTER);
}
public SimpleCluster(SimpleCluster old)
{
ID = old.ID;
LAT_LON_CENTER = new LAT_LONG
{
LATITUDE = old.LAT_LON_CENTER.LATITUDE,
LONGITUDE = old.LAT_LON_CENTER.LONGITUDE
};
LAT_LON_LIST = new List<LAT_LONG>(old.LAT_LON_LIST);
NAMES = new List<string>(old.NAMES);
}
#endregion
public int ID { get; set; }
public List<string> NAMES { get; set; }
public LAT_LONG LAT_LON_CENTER { get; set; }
public List<LAT_LONG> LAT_LON_LIST { get; set; }
}
public class LAT_LONG
{
public double LATITUDE { get; set; }
public double LONGITUDE { get; set; }
}
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);
}
}
}