Merging Treenodes - c#

Does anyone know of an algorithm that will merge treenodes in the following way?
treeA
\ child a
\node(abc)
\ child b
\node(xyz)
+
treeB
\ child a
\node(qrs)
\ child b
\node(xyz)
\node(pdq)
\ child c
\node(pdq)
= // do merge
treeMerged
\ child a
\node(abc)
\node(qrs)
\ child b
\node(xyz)
\node(pdq)
\ child c
\node(pdq)
Any help would be greatly appreciated.

Well, once I actually took the time to think about it, the solution turns out to be far more simple than I anticipated. (I've posted the critical part of the code below)
private TreeNode DoMerge(TreeNode source, TreeNode target) {
if (source == null || target == null) return null;
foreach (TreeNode n in source.Nodes) {
// see if there is a match in target
var match = FindNode(n, target.Nodes); // match paths
if (match == null) { // no match was found so add n to the target
target.Nodes.Add(n);
} else {
// a match was found so add the children of match
DoMerge(n, match);
}
}
return target;
}
Still interested to know if someone has a better solution?

Ok, I'll admit, when I first started messing with this, I didn't think it would be too hard, so I figured I'll try to do it using LINQ. It came out to be nuts, but it works. I'm SURE there are more elegant and efficient algorithms, but here it is!
First, I have a ToEnumerable extension method on the TreeNodeCollection class:
public static class TreeNodeCollectionExtensions
{
public static IEnumerable<TreeNode> ToEnumerable(this TreeNodeCollection nodes)
{
foreach (TreeNode node in nodes)
{
yield return node;
}
}
}
Then, I implement a custom comparer:
public class TreeNodeComparer : IEqualityComparer
{
public bool Equals(TreeNode x, TreeNode y)
{
return x.Text == y.Text;
}
public int GetHashCode(TreeNode obj)
{
return obj.Text.GetHashCode();
}
}
And finally, the crazyness:
private TreeView MergeTreeViews(TreeView tv1, TreeView tv2)
{
var result = new TreeView();
foreach (TreeNode node in tv2.Nodes)
{
result.Nodes.Add(node.Clone() as TreeNode);
}
foreach (TreeNode node in tv1.Nodes)
{
var nodeOnOtherSide = result.Nodes.ToEnumerable()
.SingleOrDefault(tr => tr.Text == node.Text);
if (nodeOnOtherSide == null)
{
TreeNode clone = node.Clone() as TreeNode;
result.Nodes.Add(clone);
}
else
{
var n = node.Nodes.ToEnumerable()
.Where(t => !(nodeOnOtherSide.Nodes.ToEnumerable()
.Contains(t, new TreeNodeComparer())));
foreach (TreeNode subNode in n)
{
TreeNode clone = subNode.Clone() as TreeNode;
nodeOnOtherSide.Nodes.Add(clone);
}
}
}
return result;
}
The way I coded it was that it returns a third "merged" treeView. You can change the code, so that it takes a third treeview as a parameter, so that you can pass in a treeView you may already have.
Again, I'm SURE there are better way to do this, but it SHOULD work.
One more thing I'd like to point out, this will only work for a TreeView that is two layers deep.

I came up with this recursive example, works perfect in C# (have been using it myself), note that you'll need to find a way to convert TreeNode.Nodes to an array:
public static TreeNode[] mergeTrees(TreeNode[] target, TreeNode[] source)
{
if (source == null || source.Length == 0)
{
return target;
}
if (target == null || target.Length == 0)
{
return source;
}
bool found;
foreach (TreeNode s in source)
{
found = false;
foreach (TreeNode t in target)
{
if (s.Text.CompareTo(t.Text) == 0)
{
found = true;
TreeNode[] updatedNodes = mergeTrees(Util.treeView2Array(t.Nodes), Util.treeView2Array(s.Nodes));
t.Nodes.Clear();
t.Nodes.AddRange(updatedNodes);
break;
}
}
if (!found)
{
TreeNode[] newNodes = new TreeNode[target.Length + 1];
Array.Copy(target, newNodes, target.Length);
newNodes[target.Length] = s;
target = newNodes;
}
}
return target;
}

If you are using the Node.Name attribute to set the actual path of the item, then a merge is somewhat simple.
First, create a TreeNodeCollection extension like so (this is needed to have a Case Sensitive Find() method for the TreeNodeCollection, which ensures that the Unique Path can be unique by Case as well) :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace TreeViewApp
{
public static class TreeNodeCollectionExtensions
{
public static TreeNode[] FindExact(this TreeNodeCollection coll, string keytofind)
{
TreeNode[] retval;
if (String.IsNullOrWhiteSpace(keytofind) || coll == null)
{
retval = new TreeNode[0];
}
else
{
TreeNode[] badfinds = coll.Find(keytofind, true);
List<TreeNode> goodfinds = new List<TreeNode>();
foreach (TreeNode bad in badfinds)
{
if (bad.Name == keytofind)
goodfinds.Add(bad);
}
retval = goodfinds.ToArray();
}
return retval;
}
}
}
Second, fill a treeview with your Source nodes...
Thrid, fill a treeview with your Target nodes...
Fourth, create an empty treeview.
and then, it's as simple as this:
private void btn_Merge_Click(object sender, EventArgs e)
{
//first merge
foreach (TreeNode sourceNode in this.treeview_Source.Nodes)
{
FindOrAdd(sourceNode, ref this.treeview_Merged);
}
//second merge
foreach (TreeNode targetNode in this.treeview_Target.Nodes)
{
FindOrAdd(targetNode, ref this.treeview_Merged);
}
}
private void FindOrAdd(TreeNode FindMe, ref TreeView InHere)
{
TreeNode[] found = InHere.Nodes.FindExact(FindMe.Name);
//if the node is not found, add it at the proper location.
if (found.Length == 0)
{
if (FindMe.Parent != null)
{
TreeNode[] foundParent = InHere.Nodes.FindExact(FindMe.Parent.Name);
if (foundParent.Length == 0)
InHere.Nodes.Add((TreeNode)FindMe.Clone());
else
foundParent[0].Nodes.Add((TreeNode)FindMe.Clone());
}
else
InHere.Nodes.Add((TreeNode)FindMe.Clone());
}
else
{
//if the item was found, check all children.
foreach (TreeNode child in FindMe.Nodes)
FindOrAdd(child, ref InHere);
}
}
Once again, this solution only works if you have unique paths... with the extension, it also accounts for uniqueness at the case level.
I posted this here in the hopes of helping someone who, like me, had to search for a solution for days on end without success and had to build my own.

Related

C# Delete node from BST - Am I on the right track?

EDIT:
Right thanks for helping earlier, I have been using and the step into and step over and it looks to be working but the nodes are not being deleted and I'm not sure why.
I actually use 5 arguments for the BST but just using the one for testing purposes. It compares and finds if it has any children no problem. Just wont set it to null.
only testing nodes with 0 or 1 children.
main
Tree aTree = new Tree();
aTree.InsertNode("a");
aTree.InsertNode("s");
aTree.InsertNode("3");
aTree.InsertNode("1");
aTree.InsertNode("p");
aTree.PreorderTraversal();
aTree.RemoveNode("p");
aTree.RemoveNode("3");
aTree.PreorderTraversal();
Console.ReadKey();
My Delete Methods are:
Tree Node
public void Remove(TreeNode root, TreeNode Delete) {
if (Data == null) {
}
if (Delete.Data.CompareTo(root.Data) < 0) {
root.nodeLeft.Remove(root.nodeLeft, Delete);
}
if (Delete.Data.CompareTo(root.Data) > 0) {
root.nodeRight.Remove(root.nodeRight, Delete);
}
if (Delete.Data == root.Data) {
//No child nodes
if (root.nodeLeft == null && root.nodeRight == null) {
root = null;
}
else if (root.nodeLeft == null)
{
TreeNode temp = root;
root = root.nodeRight;
root.nodeRight = null;
temp = null;
}
//No right child
else if (root.nodeRight == null)
{
TreeNode temp = root;
root = root.nodeLeft;
root.nodeLeft = null;
temp = null;
}
//Has both child nodes
else
{
TreeNode min = minvalue(root.nodeRight);
root.Data = min.Data;
root.nodeRight.Remove(root.nodeRight, min);
}
}
}
Find Min
public TreeNode minvalue(TreeNode node)
{
TreeNode current = node;
/* loop down to find the leftmost leaf */
while (current.nodeLeft != null)
{
current = current.nodeLeft;
}
return current;
}
Tree
public void RemoveNode(string Nation)
{
TreeNode Delete = new TreeNode(Nation);
root.Remove(root, Delete);
}
Remove is of return type void, but you're trying to assign it to root.nodeLeft and root.nodeRight, causing your type conversion error.
In general your Remove function needs to return the root of the sub-tree as the result, as in
public void Remove(TreeNode root, TreeNode Delete) {
if (Data == null) {
return null;
}
if (Delete.Data.CompareTo(root.Data) < 0) {
root.nodeLeft = (root.nodeLeft.Remove(root.nodeLeft, Delete));
return root;
}
... and so on.
Otherwise, since your nodes don't refer to their parents, there would be no way for the parent to know that the child node is gone, or that a new node is now at the root of the sub-tree.

Removing a node from a LinkedList (C#)

I created a LinkedList class with a function delete to remove a certain node from the list if found, however it's not working:
public class LinkedList
{
public Node head;
<...>
public void delete(string n)
{
Node x = search(n); //returns the node to delete or null if not found
if (x != null)
x = x.next;
}
<...>
}
I figured all I needed to do is find the node and set it to the next one (so the node is "removed" out of the linked list), however it's not. If anyone could help me out it'd be much appreciated!
EDIT: Forgot to mention I have a single linked list.
EDIT2: My new code:
public void delete(string n)
{
Node x = head;
while (x != null && x.name != n)
{
if (x.next.name == n)
x.next = x.next.next;
x = x.next;
}
if (x != null)
x = null;
}
You'll want to loop through the list until the next node is the one you want to delete. Then set the current to the next nodes next node.
public void Delete(string value)
{
if (head == null) return;
if (head.Value == value)
{
head = head.Next;
return;
}
var n = head;
while (n.Next != null)
{
if (n.Next.Value == value)
{
n.Next = n.Next.Next;
return;
}
n = n.Next;
}
}
This of course assumes you only want to delete the first match.

Binary tree recursive search by node content does not return correct node

I have a binary tree class.
I need to find a first occurrence of a node with some specified content and return this node using recursion.
For example Find("B") should find a first occurrence of a node with content "B".
public Node Find(string content)
{
Node aux = null;
bool found = false;
if (this.left != null)
{
this.left.Find(content);
}
if (found != true)
{
if (content == this.content)
{
found = true;
return aux = this;
}
}
if (this.right != null)
{
this.right.Find(content);
}
return aux;
}

Is there a method for searching for TreeNode.Text field in TreeView.Nodes collection?

Like this:
TreeNode[] treeNodes = treeView.Nodes.Find(searchString, true);
but I want it to search in the text field instead of the name field.
I am not aware of any inbuilt method but you may use LINQ
TreeNode[] treeNodes = treeView.Nodes
.Cast<TreeNode>()
.Where(r => r.Text == "yourText")
.ToArray();
To search all tree nodes (not only the direct child nodes) you can use the extension method below
var nodes = treeView1.FlattenTree()
.Where(n => n.Text == "sometext")
.ToList();
--
public static class SOExtension
{
public static IEnumerable<TreeNode> FlattenTree(this TreeView tv)
{
return FlattenTree(tv.Nodes);
}
public static IEnumerable<TreeNode> FlattenTree(this TreeNodeCollection coll)
{
return coll.Cast<TreeNode>()
.Concat(coll.Cast<TreeNode>()
.SelectMany(x => FlattenTree(x.Nodes)));
}
}
If I understand you correctly (you last question was very confusing), you can write a find method yourself as follows
public static TreeNode[] Find(this TreeNode motherNode, string findNodeText)
{
List<TreeNode> nodeList = new List<TreeNode>();
foreach (TreeNode childNode in motherNode.Nodes)
if (childNode.Text.Equals(findNodeText, StringComparison.CurrentCulture))
nodeList.Add(childNode);
return nodeList.ToArray<TreeNode>();
}
This method can be used like
TreeView myTreeView = new TreeView();
foreach (TreeNode node in myTreeView.Nodes)
{
TreeNode[] childNodes = node.Find("Text");
// Do something...
}
I hope this helps.
The following code only shows the nodes which matches the search criteria.
Copy the following code in the search event
private void tbxSearch_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
trvMenu.BeginUpdate();
if (tbxSearch.Text.Length > 0)
{
for (int i = trvMenu.Nodes.Count; i > 0 ; i--)
{
NodeFiltering(trvMenu.Nodes[i - 1], tbxSearch.Text);
}
}
trvMenu.EndUpdate();
}
Then create the serch & filter function
private bool NodeFiltering(TreeNode Nodo,string Texto)
{
bool resultado = false;
if (Nodo.Nodes.Count == 0)
{
if (Nodo.Text.ToUpper().Contains(Texto.ToUpper()))
{
resultado = true;
}
else
{
Nodo.Remove();
}
}
else
{
for (int i = Nodo.Nodes.Count; i > 0; i--)
{
if (NodeFiltering(Nodo.Nodes[i - 1], Texto))
resultado = true;
}
if (!resultado)
Nodo.Remove();
}
return resultado;
}
This code is pretty nice for creating Treeview menus with many levels.

Recursively traversing a tree in C# from top down by row

Interesting problem posed by a friend recently: Imagine you've got a List< NodeType > of all nodes in a tree. How would you go about traversing down the tree from the root node, by row such that you find the first node with a specific value. So say that each node has 3 attributes: its name/location, the identity of its parent, and who "owns" the node. The problem is you want to find the highest node in the tree that you "own" no matter what branch its on. I understand the basic logic in so much as to find the first set of children you look for all nodes with a parent set as the first node. But how would you go about recursively search through a List<> of nodes to find the highest node you own?
You’re looking for breadth-first search. It is normally implemented using a queue:
public Node FindFirstByBreadthFirst(this Node node, Predicate<Node> match)
{
var queue = new Queue<Node>();
queue.Enqueue(tree.RootNode);
while (queue.Count > 0)
{
// Take the next node from the front of the queue
var node = queue.Dequeue();
// Process the node 'node'
if (match(node))
return node;
// Add the node’s children to the back of the queue
foreach (var child in node.Children)
queue.Enqueue(child);
}
// None of the nodes matched the specified predicate.
return null;
}
Algorithm:
Put the root node in a queue.
Repeat
Take item from queue;
Matching? return Item
Add all children to the queue
Until Queue is empty
Update: Haha, wow, this is completely wrong, I just realized (as in it is not doing what you asked for). Never mind -- looks like you already got a correct answer, anyway :)
I think I understand your problem. Let me know if I'm getting something wrong.
You have a NodeType class that looks something like this:
class NodeType
{
public string Name { get; }
public NodeType Parent { get; }
public int OwnderId { get; }
}
First order of business would be to write a function that takes a NodeType parameter and, given some enumerable collection of NodeType objects, returns all of its descendents in a recursive fashion:
IEnumerable<NodeType> GetNodeChildren(NodeType node, IEnumerable<NodeType> nodes)
{
var children = nodes.Where(n => n.Parent == node);
if (children.Any())
{
foreach (NodeType child in children)
{
yield return child;
var grandchildren = GetNodeChildren(child);
foreach (NodeType grandchild in grandchildren)
{
yield return grandchild;
}
}
}
}
Next up: write a function that takes a NodeType object and finds the highest descendent with a specified OwnerId. This is really a pretty simple operation, so I won't even define a proper function; I'll just use a lambda:
Func<NodeType, int, NodeType> findHighestDescendent = (node, id) => {
return GetNodeChildren(node).FirstOrDefault(child => child.OwnerId == id);
};
Now for any given Id value, it is quite trivial to find the highest matching NodeType:
int id = 10; // just for example
NodeType highestOwnedNode = nodes
.Select(n => findHighestDescendent(n, id))
.FirstOrDefault(n => (n != null));
public static Control FindChildControlByDepth(this Control Page, string ControlID, int depth)
{
if (depth > 10)
throw new ArgumentException("Cannot search beyond a depth of 10", "depth");
foreach (Control c in Page.Controls)
{
if (c.ID == ControlID)
return c;
if (depth > 0)
{
foreach (Control c1 in c.Controls)
{
if (c1.ID == ControlID)
return c1;
if (depth > 1)
{
foreach (Control c2 in c1.Controls)
{
if (c2.ID == ControlID)
return c2;
if (depth > 2)
{
foreach (Control c3 in c2.Controls)
{
if (c3.ID == ControlID)
return c3;
if (depth > 3)
{
foreach (Control c4 in c3.Controls)
{
if (c4.ID == ControlID)
return c4;
if (depth > 4)
{
foreach (Control c5 in c4.Controls)
{
if (c5.ID == ControlID)
return c5;
if (depth > 5)
{
foreach (Control c6 in c5.Controls)
{
if (c6.ID == ControlID)
return c6;
if (depth > 6)
{
foreach (Control c7 in c6.Controls)
{
if (c7.ID == ControlID)
return c7;
if (depth > 8)
{
foreach (Control c8 in c7.Controls)
{
if (c8.ID == ControlID)
return c8;
if (depth > 9)
{
foreach (Control c9 in c8.Controls)
{
if (c9.ID == ControlID)
return c9;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
return null;
}

Categories

Resources