diff --git a/appveyor.yml b/appveyor.yml index e0c6dc7..743db0c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,7 +33,8 @@ build: verbosity: minimal test_script: -- OpenCover.Console.exe -register:user -target:"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe" -targetargs:"/testcontainer:.\phirSOFT.ConstraintedComparisonTests\bin\Release\phirSOFT.ConstraintedComparisonTests.dll" -output:".\phirSOFT_TopologicalComparisonCoverage.xml" + +- OpenCover.Console.exe -register:user -target:"vstest.console" -targetargs:"/logger:Appveyor ""C:\projects\phirsoft-topologicalcomparison\phirSOFT.ConstraintedComparisonTests\bin\Release\phirSOFT.ConstraintedComparisonTests.dll""" -output:".\phirSOFT_TopologicalComparisonCoverage.xml" - codecov -f "phirSOFT_TopologicalComparisonCoverage.xml" deploy: - provider: NuGet diff --git a/phirSOFT.ConstraintedComparison/DirectedGraphNode`1.cs b/phirSOFT.ConstraintedComparison/DirectedGraphNode`1.cs new file mode 100644 index 0000000..911bde7 --- /dev/null +++ b/phirSOFT.ConstraintedComparison/DirectedGraphNode`1.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace phirSOFT.TopologicalComparison +{ + public class DirectedGraphNode : IDirectedGraphNode + { + private readonly HashSet> _children; + private readonly HashSet> _parents; + private int _distance; + + internal DirectedGraphNode(T node, bool root = false) + { + Value = node; + Distance = root ? 0 : int.MaxValue; + _children = new HashSet>(); + _parents = new HashSet>(); + } + + public int Distance + { + get => _distance; + private set + { + if (_distance == value) return; + var old = _distance; + _distance = value; + + foreach (var child in _children) + { + child.UpdateDistance(value, old); + } + } + } + + private void AddParent(DirectedGraphNode parent) + { + _parents.Add(parent); + Distance = Math.Min(Distance, parent.Distance + 1); + } + + private void RemoveParent(DirectedGraphNode parent) + { + _parents.Remove(parent); + if (_parents.Count > 0) + Distance = _parents.Min(node => node.Distance) + 1; + else + Distance = int.MaxValue; + } + + public T Value { get; set; } + public IEnumerable> Children => _children.AsEnumerable(); + ITreeNode ITreeNode.AddChild(T node) + { + return AddChild(node); + } + + public void Detach(ITreeNode child) + { + Detach(child as DirectedGraphNode); + } + + public void Attach(ITreeNode child) + { + Attach(child as DirectedGraphNode); + } + + IEnumerable> ITreeNode.Children => Children; + + public IDirectedGraphNode AddChild(T node) + { + var newNode = new DirectedGraphNode(node); + Attach(newNode); + return newNode; + } + + public void Detach(IDirectedGraphNode child) + { + if (!(child is DirectedGraphNode directedChid)) + throw new InvalidOperationException(); + + _children.Remove(directedChid); + directedChid.RemoveParent(this); + } + + public void Attach(IDirectedGraphNode child) + { + if (!(child is DirectedGraphNode directedChid)) + throw new InvalidOperationException(); + + _children.Add(directedChid); + directedChid.AddParent(this); + } + + private void UpdateDistance(int distance, int oldDistance) + { + if (oldDistance + 1 == distance && distance > oldDistance) + Distance = _parents.Min(node => node.Distance) + 1; + else + Distance = Math.Min(Distance, distance + 1); + } + + public IEnumerable> Parents => _parents.AsEnumerable(); + } +} \ No newline at end of file diff --git a/phirSOFT.ConstraintedComparison/DirectedGraph`1.cs b/phirSOFT.ConstraintedComparison/DirectedGraph`1.cs new file mode 100644 index 0000000..fc971c6 --- /dev/null +++ b/phirSOFT.ConstraintedComparison/DirectedGraph`1.cs @@ -0,0 +1,19 @@ +using System.Text; + +namespace phirSOFT.TopologicalComparison +{ + public class DirectedGraph : IDirectedGraph + { + private readonly DirectedGraphNode _root; + + public DirectedGraph(T rootValue) + { + _root = new DirectedGraphNode(rootValue); + } + + ITreeNode ITree.Root => _root; + + public IDirectedGraphNode Root => _root; + + } +} diff --git a/phirSOFT.ConstraintedComparison/IBidirectionalTreeNode.cs b/phirSOFT.ConstraintedComparison/IBidirectionalTreeNode.cs new file mode 100644 index 0000000..1dfe92e --- /dev/null +++ b/phirSOFT.ConstraintedComparison/IBidirectionalTreeNode.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace phirSOFT.TopologicalComparison +{ + public interface IBidirectionalTreeNode : ITreeNode> + { + IBidirectionalTreeNode Parent {get;} + } +} diff --git a/phirSOFT.ConstraintedComparison/IDirectedGraphNode`1.cs b/phirSOFT.ConstraintedComparison/IDirectedGraphNode`1.cs new file mode 100644 index 0000000..da47a05 --- /dev/null +++ b/phirSOFT.ConstraintedComparison/IDirectedGraphNode`1.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace phirSOFT.TopologicalComparison +{ + public interface IDirectedGraphNode : ITreeNode> + { + IEnumerable> Parents { get; } + } +} \ No newline at end of file diff --git a/phirSOFT.ConstraintedComparison/IDirectedGraph`1.cs b/phirSOFT.ConstraintedComparison/IDirectedGraph`1.cs new file mode 100644 index 0000000..33424a5 --- /dev/null +++ b/phirSOFT.ConstraintedComparison/IDirectedGraph`1.cs @@ -0,0 +1,10 @@ +namespace phirSOFT.TopologicalComparison +{ + /// + /// Represents a directs cycle free graph. + /// + public interface IDirectedGraph : ITree> + { + + } +} \ No newline at end of file diff --git a/phirSOFT.ConstraintedComparison/ITreeNode.cs b/phirSOFT.ConstraintedComparison/ITreeNode`1.cs similarity index 91% rename from phirSOFT.ConstraintedComparison/ITreeNode.cs rename to phirSOFT.ConstraintedComparison/ITreeNode`1.cs index 34494a5..801b1ef 100644 --- a/phirSOFT.ConstraintedComparison/ITreeNode.cs +++ b/phirSOFT.ConstraintedComparison/ITreeNode`1.cs @@ -22,13 +22,13 @@ public interface ITreeNode /// Adds a child to this node. /// /// The value of the node to attach - /// The new added . + /// The new added . ITreeNode AddChild(T node); /// /// Detaches an from this node. /// - /// The node to detach + /// The node to detach /// /// The child node has to be an direct child of this node. /// diff --git a/phirSOFT.ConstraintedComparison/ITreeNode`2.cs b/phirSOFT.ConstraintedComparison/ITreeNode`2.cs new file mode 100644 index 0000000..95a99cc --- /dev/null +++ b/phirSOFT.ConstraintedComparison/ITreeNode`2.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace phirSOFT.TopologicalComparison +{ + /// + /// Provides an interface for a node in an + /// + /// The type of the value stored in the node. + /// The type of the nodes. + public interface ITreeNode : ITreeNode where TNode : ITreeNode + { + /// + /// Gets or sets value of the node. + /// + new TValue Value { get; set; } + + /// + /// Gets all children of this node. + /// + new IEnumerable Children { get; } + + /// + /// Adds a child to this node. + /// + /// The value of the node to attach + /// The new added . + new TNode AddChild(TValue node); + + /// + /// Detaches an from this node. + /// + /// The node to detach + /// + /// The child node has to be an direct child of this node. + /// + void Detach(TNode child); + + /// + /// Attaches an to this node. + /// + /// + /// The to attach. + /// + /// + /// The should not be attached to + /// an other parent. + /// + void Attach(TNode child); + } +} diff --git a/phirSOFT.ConstraintedComparison/ITree.cs b/phirSOFT.ConstraintedComparison/ITree`1.cs similarity index 85% rename from phirSOFT.ConstraintedComparison/ITree.cs rename to phirSOFT.ConstraintedComparison/ITree`1.cs index 886618e..0ca8590 100644 --- a/phirSOFT.ConstraintedComparison/ITree.cs +++ b/phirSOFT.ConstraintedComparison/ITree`1.cs @@ -1,7 +1,7 @@ namespace phirSOFT.TopologicalComparison { /// - /// Provides an tree data structure + /// Provides a tree data structure /// public interface ITree { diff --git a/phirSOFT.ConstraintedComparison/ITree`2.cs b/phirSOFT.ConstraintedComparison/ITree`2.cs new file mode 100644 index 0000000..ebfc571 --- /dev/null +++ b/phirSOFT.ConstraintedComparison/ITree`2.cs @@ -0,0 +1,14 @@ +namespace phirSOFT.TopologicalComparison +{ + /// + /// Provides a tree datastructure with a more specific node type. + /// + /// The type of the values stored in the nodes. + /// The type of the nodes + public interface ITree : ITree where TNode : ITreeNode + { + /// + new TNode Root { get; } + } + +} diff --git a/phirSOFT.ConstraintedComparison/TopologicalComparerProxy.cs b/phirSOFT.ConstraintedComparison/TopologicalComparerExtensions.cs similarity index 79% rename from phirSOFT.ConstraintedComparison/TopologicalComparerProxy.cs rename to phirSOFT.ConstraintedComparison/TopologicalComparerExtensions.cs index 0c0de85..19da2b9 100644 --- a/phirSOFT.ConstraintedComparison/TopologicalComparerProxy.cs +++ b/phirSOFT.ConstraintedComparison/TopologicalComparerExtensions.cs @@ -1,14 +1,16 @@ -namespace phirSOFT.TopologicalComparison +using System; + +namespace phirSOFT.TopologicalComparison { /// /// Provides extension method for the and /// interfaces. /// - public static class TopologicalComparerProxy + public static class TopologicalComparerExtensions { /// /// Tries to compare two objects using an . + /// cref="ITopologicalComparer"/>. /// /// The comparer to use. /// The first object to compare. @@ -57,5 +59,12 @@ public static bool TryCompare(this ITopologicalComparer comparer, T lhs, T result = 0; return false; } + + public static ITopologicalComparer Map(this ITopologicalComparer comparer, + Func converter) + { + return TopologicalComparer.Create((x, y) => comparer.Compare(converter(x), converter(y)), + (x, y) => comparer.CanCompare(converter(x), converter(y))); + } } } diff --git a/phirSOFT.ConstraintedComparison/TreeExtensions.cs b/phirSOFT.ConstraintedComparison/TreeExtensions.cs index 5d9833b..f6b609b 100644 --- a/phirSOFT.ConstraintedComparison/TreeExtensions.cs +++ b/phirSOFT.ConstraintedComparison/TreeExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; namespace phirSOFT.TopologicalComparison { @@ -89,6 +90,110 @@ public static void Insert(this ITree tree, T node, ITopologicalComparer var newNode = lastParent.AddChild(node); + foreach (var futureChild in futureChildren) newNode.Attach(futureChild); + } + + /// + /// Inserts a node into an topological ordered tree. Using the + /// default comparer. + /// + /// + /// The tree to insert the into. + /// + /// + /// The node to insert into the tree. + /// + public static void Insert(this IDirectedGraph tree, T node) + { + tree.Insert(node, TopologicalComparer.Default); + } + + /// + /// Inserts a node into an topological ordered tree. Using a + /// custom comparer. + /// + /// + /// The tree to insert the into. + /// + /// + /// The node to insert into the tree. + /// + /// + /// The comparer to use. + /// + public static void Insert(this IDirectedGraph tree, T node, ITopologicalComparer comparer) + { + var lastParent = tree.Root ?? throw new ArgumentException($"{nameof(tree)} needs a root."); + if (comparer.TryCompare(lastParent.Value, node, out var rootres) && rootres == 0) + return; + + var possibleParents = new Queue>(); + possibleParents.Enqueue(lastParent); + var futureChildren = new LinkedList>(); + var parents = new HashSet>(); + + while (possibleParents.Count > 0) + { + var currentParent = possibleParents.Dequeue(); + var begin = futureChildren.Last; + + bool foundLessElement = false; + foreach (var child in currentParent.Children) + { + if (!comparer.TryCompare(child.Value, node, out var result)) + { + possibleParents.Enqueue(child); + continue; + } + + if (result == 0) + return; + if (result < 0) + { + possibleParents.Enqueue(child); + foundLessElement = true; + } + else if (result > 0) + { + futureChildren.AddLast(child); + } + } + + if (foundLessElement && comparer.CanCompare(currentParent.Value, node)) + { + parents.Add(currentParent); + } + + begin = begin?.Next ?? futureChildren.First; + + while (begin != null) + { + currentParent.Detach(begin.Value); + begin = begin.Next; + } + } + + IDirectedGraphNode newNode; + using (var parentEnumerator = parents.GetEnumerator()) + { + if (!parentEnumerator.MoveNext()) + { + newNode = lastParent.AddChild(node); + } + else + { + Debug.Assert(parentEnumerator.Current != null, "parentEnumerator.Current != null"); + newNode = parentEnumerator.Current.AddChild(node); + } + + while(parentEnumerator.MoveNext()) + { + Debug.Assert(parentEnumerator.Current != null, "parentEnumerator.Current != null"); + parentEnumerator.Current.Attach(newNode); + } + } + + foreach (var futureChild in futureChildren) newNode.Attach(futureChild); } }