I have this implementation of a tree recursive function. It gets all possible paths from A to D.
The recursive function is:
private static List<Path> GetPath(int depth, Path path)
{
if (depth == nodes.Length)
{
return new List<Path> { path };
}
else
{
var result = new List<Path>();
foreach(var link in nodes[depth].Links)
{
Node node = new Node { Name = nodes[depth].Name, Links = new[] { link } };
path.Add(node);
result.AddRange(
GetPath(
depth+1, path));
}
return result;
}
}
The expected results should be:
A1-B2->C3->D4
A1-B5->C3->D4
However, the paths returned are the same and they include all possible nodes twice.
What's wrong with the function?
foreach(var link in nodes[depth].Links)
{
Node node = new Node { Name = nodes[depth].Name, Links = new[] { link } };
path.Add(node);
You probably intend to make a new path (which is a copy of path) for each node that you find here, before appending the next node.
As #moreON's suggest, I add clone function into class Path, and modify the loop, in loop i copy to new instance of path:
public class Path : List<Node>
{
public override string ToString()
{
String s = "";
foreach (var node in this)
{
s += node.Name + node.Links[0] + "->";
}
return s;
}
public Path Clone()
{
var newPath = new Path();
ForEach(x => newPath.Add(new Node {Name = x.Name, Links = new int[] {x.Links[0]}}));
return newPath;
}
}
private static List<Path> GetPath(int depth, Path path)
{
if (depth == nodes.Length)
{
return new List<Path> { path };
}
else
{
var result = new List<Path>();
foreach (var link in nodes[depth].Links)
{
Node node = new Node { Name = nodes[depth].Name, Links = new[] { link } };
var currentPath = path.Clone();
currentPath.Add(node);
result.AddRange(GetPath(depth + 1, currentPath));
}
return result;
}
}
Hope this help.
Related
I am working on a WPF XAML application that scrapes certain websites for products. I have the search part working and it finds what I'm looking for. But as soon as there is more then 1 result I get a System.InvalidoperationException. I use a ObservableCollection to put the results into a <ListBox>.
Here is the search method:
private static ObservableCollection<EntryModel> _entries = new ObservableCollection<EntryModel>();
public static ObservableCollection<EntryModel> LoadCollectionData
{
get { return _entries; }
set { _entries = value; }
}
public static void PrehmSearchResults(string SearchQuery)
{
HtmlWeb web = new HtmlWeb();
try
{
string ZoekOpdracht = SearchQuery.Replace(" ", "+");
HtmlDocument doc = web.Load("https://www.prehmshop.de/advanced_search_result.php?keywords=" + ZoekOpdracht);
var title = doc.DocumentNode.CssSelect("div.header_cell > a").Single().InnerText;
var links = doc.DocumentNode.CssSelect("a.product_link");
var productLink = new List<string>();
var productTitle = new List<string>();
foreach (var item in links)
{
if (item.Attributes["href"].Value.Contains(".html"))
{
productLink.Add(item.Attributes["href"].Value);
productTitle.Add(title);
}
}
var TitleAndLink = productLink.Zip(productTitle, (l, t) => new { productLink = l, productTitle = t });
foreach (var nw in TitleAndLink)
{
var product = new List<EntryModel>();
var adDetails = new EntryModel
{
Title = nw.productTitle,
Link = nw.productLink
};
Debug.Print(adDetails.ToString());
var ZoekOpdrachtInTitle = adDetails.Title.ToLower().Contains(ZoekOpdracht.ToLower());
if (ZoekOpdrachtInTitle)
{
_entries.Add(adDetails);
}
}
}
So I found the solution without changing too much code. thanks for the help from #PaulSinnema.
The link is part of the title so I only had to change
var title = doc.DocumentNode.CssSelect("div.header_cell > a").ToList();
And I had to change the foreach loop:
foreach (var item in title)
{
if (item.Attributes["href"].Value.Contains(".html"))
{
productLink.Add(item.Attributes["href"].Value);
productTitle.Add(item.InnerText);
}
}
I have a function GetExchangeRates which needs to return IEnumerable. I dont know how to do that. I can only write the result in the console and return Null. The ExchangeRate is a class with SourceCurrency/TargetCurrency=Value with only getters(immutable) and a constructor, returning tostring() {SourceCurrency}/{TargetCurrency}={Value}";
And the main program class creates new[] set of currencies as IEnum. In the main I construct it and call:
var provider = new ExchangeRateProvider();
var rates = provider.GetExchangeRates(currencies)
And then just loop over rates and print it out. How do I pass the logic in the source code to the Main function properly?
public IEnumerable<ExchangeRate> GetExchangeRates(IEnumerable<Currency> currencies)
{
var xmldoc = new XmlDocument();
xmldoc.Load(#"http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
XmlNodeList nodes = xmldoc.SelectNodes("//*[#currency]");
if (nodes != null)
{
foreach (XmlNode node in nodes)
{
var rate = new ExchangeRate()
{
SourceCurrency = new Currency("EUR"),
TargetCurrency = new Currency(node.Attributes["currency"].Value),
Value = (Decimal.Parse(node.Attributes["rate"].Value, NumberStyles.Any,
new CultureInfo("en-Us")))
};
Console.WriteLine(rate);
}
}
return null;
}
}
You could use the yield return keyword here. For example,
yield return rate;
Complete Code
public IEnumerable<ExchangeRate> GetExchangeRates(IEnumerable<Currency> currencies)
{
var xmldoc = new XmlDocument();
xmldoc.Load(#"http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
XmlNodeList nodes = xmldoc.SelectNodes("//*[#currency]");
if (nodes != null)
{
foreach (XmlNode node in nodes)
{
var rate = new ExchangeRate()
{
SourceCurrency = new Currency("EUR"),
TargetCurrency = new Currency(node.Attributes["currency"].Value),
Value = (Decimal.Parse(node.Attributes["rate"].Value, NumberStyles.Any,
new CultureInfo("en-Us")))
};
// ADDED YIELD RETURN
yield return rate;
}
}
}
}
The yield return keyword would return each element one at a time.You can read more on Yield keyword here
Firstly, I read heaps of topics about JSON to TreeView on the Stackoverflow. After this, I create a JSON data like this:
{
"Cars": {
"Audi": [{
"A6 2.0 TDI quatro 2018 Red": ["S-Line", "17 inch rim", "Full sport packet"],
"A5 1.6 TFSI 2018 Blue": ["Desing packet", "Sunroof"]
}],
"Mercedes-Benz": [{
"E220d AMG 2018 white": ["Glass ceiling", "Vacuum doors", "Navigation"],
"E220d Exclusive Black 2018 Blue": ["Power seats", "Start & Stop"]
}]
}
}
Here is the C# code content:
private void Form1_Load(object sender, EventArgs e)
{
try
{
treeView1.Nodes.Clear();
var json = File.ReadAllText(Uz.path + #"cars.json");
var obj = JObject.Parse(json);
var parent = Json2Tree(obj);
treeView1.Nodes.Add(parent);
treeView1.ExpandAll();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, #"ERROR");
}
}
private static TreeNode Json2Tree(JObject obj)
{
//create the parent node
var parent = new TreeNode();
//loop through the obj. all token should be pair<key, value>
foreach (var token in obj)
{
//change the display Content of the parent
parent.Text = token.Key;
//create the child node
var child = new TreeNode();
child.Text = token.Key;
//check if the value is of type obj recall the method
if (token.Value.Type.ToString() == "Object")
{
// child.Text = token.Key.ToString();
//create a new JObject using the the Token.value
var o = (JObject)token.Value;
//recall the method
child = Json2Tree(o);
//add the child to the parentNode
parent.Nodes.Add(child);
}
//if type is of array
else if (token.Value.Type.ToString() == "Array")
{
int ix = -1;
// child.Text = token.Key.ToString();
//loop though the array
foreach (var itm in token.Value)
{
//check if value is an Array of objects
if (itm.Type.ToString() == "Object")
{
//child.Text = token.Key.ToString();
//call back the method
ix++;
var o = (JObject)itm;
var objTN = Json2Tree(o);
//objTN.Text = token.Key + "[" + ix + "]";
child.Nodes.Add(objTN);
//parent.Nodes.Add(child);
}
//regular array string, int, etc
else if (itm.Type.ToString() == "Array")
{
ix++;
var dataArray = new TreeNode();
foreach (var data in itm)
{
//dataArray.Text = token.Key + "[" + ix + "]";
dataArray.Nodes.Add(data.ToString());
}
child.Nodes.Add(dataArray);
}
else
{
child.Nodes.Add(itm.ToString());
}
}
parent.Nodes.Add(child);
}
else
{
//if token.Value is not nested
// child.Text = token.Key.ToString();
//change the value into N/A if value == null or an empty string
child.Nodes.Add(token.Value.ToString() == "" ? "N/A" : token.Value.ToString());
parent.Nodes.Add(child);
}
}
return parent;
}
when I run the code, the screenshot looks like this:
But marked as 1, 2 and 3 are should not be shown. It must be like this:
Although I worked 3 days, I did not succeed.
In JsonTreeView project, it show like this:
using System.Windows.Forms;
using Newtonsoft.Json.Linq;
namespace JsonTreeView
{
public static class JsonToTreeView
{
public static void Json2Tree(this TreeView treeView, string json, string group_name)
{
if (string.IsNullOrWhiteSpace(json))
{
return;
}
var obj = JObject.Parse(json);
AddObjectNodes(obj, group_name, treeView.Nodes);
}
public static void AddObjectNodes(JObject obj, string name, TreeNodeCollection parent)
{
var node = new TreeNode(name);
parent.Add(node);
foreach (var property in obj.Properties())
{
AddTokenNodes(property.Value, property.Name, node.Nodes);
}
}
private static void AddArrayNodes(JArray array, string name, TreeNodeCollection parent)
{
var node = new TreeNode(name);
parent.Add(node);
for (var i = 0; i < array.Count; i++)
{
AddTokenNodes(array[i], $"[{i}]", node.Nodes);
}
}
private static void AddTokenNodes(JToken token, string name, TreeNodeCollection parent)
{
switch (token)
{
case JValue _:
parent.Add(new TreeNode($"{((JValue) token).Value}"));
break;
case JArray _:
AddArrayNodes((JArray)token, name, parent);
break;
case JObject _:
AddObjectNodes((JObject)token, name, parent);
break;
}
}
}
}
I'm using HtmlAgilityPack. Does it have a function similar to jQuery closest? (closest parent that matches a CSS selector). I tried google and the website http://html-agility-pack.net - and both don't appear to have an answer.
As there is no built-in method currently, you can write a Extension method to achieve this.
I have written a simple extension method which can be used to find elements with tagName, ID and class names that you can use.
Anyways it can be further extended easily to match other selectors.
public static class HtmlAgilityPackExtensions
{
public static HtmlNode Closest(this HtmlNode node, string jQuerySelector)
{
if (node == null) return null;
string tagName = "", id = "";
var classes = new List<string>();
if (jQuerySelector.Contains("."))
{
var parts = jQuerySelector.Split('.');
if (!string.IsNullOrWhiteSpace(parts[0]))
{
tagName = parts[0];
}
for (int i = 1; i < parts.Length; i++)
{
classes.Add(parts[i]);
}
}
if (jQuerySelector.Contains("#"))
{
var parts = jQuerySelector.Split('#');
if (!string.IsNullOrWhiteSpace(parts[0]))
{
tagName = parts[0];
}
id = parts[1];
}
if (string.IsNullOrWhiteSpace(tagName) && string.IsNullOrWhiteSpace(id) && classes.Count == 0)
{
tagName = jQuerySelector;
}
HtmlNode closestParent = null;
while (node.ParentNode != null && closestParent == null)
{
var isClosest = true;
node = node.ParentNode;
if (!string.IsNullOrWhiteSpace(tagName))
{
isClosest = node.Name == tagName;
}
if (isClosest && !string.IsNullOrWhiteSpace(id))
{
isClosest = node.Id == id;
}
if (isClosest && classes.Count > 0)
{
var classNames = node.GetAttributeValue("class", "");
if (!string.IsNullOrWhiteSpace(classNames))
{
foreach (string c in classes)
{
isClosest = classNames.Contains(c);
if (!isClosest) break;
}
}
}
if (isClosest)
{
closestParent = node;
}
}
return closestParent;
}
}
Test Code
var html = "<div><div id='parent1' class='parent'><span id='parent2' class='parent'><div id='parent3' class='parent'><div id='TestNode' class='child'>Test node</div></div></span></div></div>";
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(html);
var testNode1 = htmlDoc.DocumentNode.SelectSingleNode("//div[#id='TestNode']");
if (testNode1 != null)
{
var parent1 = testNode1.Closest(".parent");
var parent2 = testNode1.Closest("#parent1");
var parent3 = testNode1.Closest("span.parent");
var nonExistingParent = testNode1.Closest("span.parent1");
}
I needed the same thing, but couldn't find any, so I wrote my own Closest function:
public static HtmlNode Closest(this HtmlNode node, string search)
{
search = search.ToLower();
while (node.ParentNode != null)
{
if (node.ParentNode.Name.ToLower() == search) return node.ParentNode;
node = node.ParentNode;
}
return null;
}
This one only works for tag names (as I needed) you can extend it to classes, attributes, and ...
I have a String List with items like this
"Root"
"Root/Item1"
"Root/Item2"
"Root/Item3/SubItem1"
"Root/Item3/SubItem2"
"Root/Item4/SubItem1"
"AnotherRoot"
How do I transfer this stringlist into a treeview ?
You can split each item into it's substrings. Then via recursion look for each item, if the parent exists add to it, and if the parent doesn't exists create it.
If you can't see how to do it, i`ll post you a sample code
Sample Code
public void AddItem(TreeView treeControl, TreeNode parent, string item)
{
TreeNodeCollection nodesRef = (parent != null) ? parent.Nodes : treeControl.Nodes;
string currentNodeName;
if (-1 == item.IndexOf('/')) currentNodeName = item;
else currentNodeName = item.Substring(0, item.IndexOf('/'));
if (nodesRef.ContainsKey(currentNodeName))
{
AddItem(treeControl, nodesRef[currentNodeName], item.Substring(currentNodeName.Length+1));
}
else
{
TreeNode newItem = nodesRef.Add(currentNodeName, currentNodeName);
if (item.Length > currentNodeName.Length)
{
AddItem(treeControl, newItem, item.Substring(item.IndexOf('/', currentNodeName.Length) + 1));
}
}
}
And the caller example:
string[] stringArr = {
"Root",
"Root/Item1",
"Root/Item2",
"Root/Item3/SubItem1",
"Root/Item3/SubItem2",
"Root/Item4/SubItem1",
"AnotherRoot"
};
foreach (string item in stringArr)
{
AddItem(treeView1, null, item);
}
One way is to iterate the items split the item and push them on a list and if the parent doesn't match pop an item from the list until the stack is empty or you have a match.
You can use this code:
private void button1_Click(object sender, EventArgs e) {
List<String> paths = new List<String> {
"Root", "Root/Item1", "Root/Item2", "Root/Item3/SubItem1",
"Root/Item3/SubItem2", "Root/Item4/SubItem1", "AnotherRoot"
};
List<TreeNode> nodeCollection = new List<TreeNode>();
foreach (var path in paths) {
AddPath(nodeCollection, path);
}
treeView1.Nodes.Clear();
treeView1.Nodes.AddRange(nodeCollection.ToArray());
}
public void AddPath(List<TreeNode> collection, String path) {
LinkedList<String> pathToBeAdded = new LinkedList<String>(path.Split(new String[] { #"/" }, StringSplitOptions.RemoveEmptyEntries));
if (pathToBeAdded.Count == 0) {
return;
}
String rootPath = pathToBeAdded.First.Value;
TreeNode root = collection.FirstOrDefault(n => n.Text.Equals(rootPath));
if (root == null) {
root = new TreeNode(rootPath);
collection.Add(root);
}
pathToBeAdded.RemoveFirst();
AddPath(root, pathToBeAdded);
}
public void AddPath(TreeNode rootNode, LinkedList<String> pathToBeAdded) {
if (pathToBeAdded.Count == 0) {
return;
}
String part = pathToBeAdded.First.Value;
TreeNode subNode = null;
if (!rootNode.Nodes.ContainsKey(part)) {
subNode = rootNode.Nodes.Add(part, part);
} else {
subNode = rootNode.Nodes[part];
}
pathToBeAdded.RemoveFirst();
AddPath(subNode, pathToBeAdded);
}
Hope this helps.
Ricardo Lacerda Castelo Branco