diff --git a/src/main/java/org/mastodon/mamut/treesimilarity/NodeMapping.java b/src/main/java/org/mastodon/mamut/treesimilarity/NodeMapping.java
index fc7b95574..012941a02 100644
--- a/src/main/java/org/mastodon/mamut/treesimilarity/NodeMapping.java
+++ b/src/main/java/org/mastodon/mamut/treesimilarity/NodeMapping.java
@@ -1,104 +1,43 @@
package org.mastodon.mamut.treesimilarity;
-import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import org.mastodon.mamut.treesimilarity.tree.Tree;
-abstract class NodeMapping< T >
+/**
+ * Helper class for {@link ZhangUnorderedTreeEditDistance}.
+ *
+ * Used to represent a mapping between the nodes of two {@link Tree trees}
+ * together with the associated costs / edit distance.
+ *
+ * @param Attribute type for the trees.
+ *
+ * @see NodeMappings
+ */
+interface NodeMapping< T >
{
- public static < T > NodeMapping< T > empty( double cost )
- {
- return new EmptyNodeMapping<>( cost );
- }
-
- public static < T > NodeMapping< T > singleton( double cost, Tree< T > tree1, Tree< T > tree2 )
- {
- return new SingletonNodeMapping<>( tree1, tree2, cost );
- }
-
- @SafeVarargs
- public static < T > NodeMapping< T > compose( NodeMapping< T >... children )
- {
- return compose( Arrays.asList( children ) );
- }
- public static < T > NodeMapping< T > compose( List< NodeMapping< T > > children )
- {
- return new ComposedNodeMapping<>( children );
- }
-
- private final double cost;
-
- protected NodeMapping( double cost )
- {
- this.cost = cost;
- }
-
- public double getCost()
- {
- return cost;
- }
+ /**
+ * @return The cost of this mapping.
+ */
+ double getCost();
- abstract protected void writeToMap( Map< Tree< T >, Tree< T > > map );
+ /**
+ * This method is needed for an efficient implementation of the
+ * {@link #asMap()} method. It is not meant to be used directly,
+ * use {@link #asMap()} instead.
+ */
+ void writeToMap( Map< Tree< T >, Tree< T > > map );
- public Map< Tree< T >, Tree< T > > asMap()
+ /**
+ * @return The mapping as a {@link Map} from nodes of the first tree to
+ * nodes of the second tree.
+ */
+ default Map< Tree< T >, Tree< T > > asMap()
{
final Map< Tree< T >, Tree< T > > map = new HashMap<>();
writeToMap( map );
return map;
}
-
- static class EmptyNodeMapping< T > extends NodeMapping< T >
- {
- private EmptyNodeMapping( double cost )
- {
- super( cost );
- }
-
- @Override
- public void writeToMap( Map< Tree< T >, Tree< T > > map )
- {
- // do nothing
- }
- }
-
- static class SingletonNodeMapping< T > extends NodeMapping< T >
- {
- private final Tree< T > tree1;
-
- private final Tree< T > tree2;
-
- private SingletonNodeMapping( Tree< T > tree1, Tree< T > tree2, double cost )
- {
- super( cost );
- this.tree1 = tree1;
- this.tree2 = tree2;
- }
-
- @Override
- public void writeToMap( Map< Tree< T >, Tree< T > > map )
- {
- map.put( tree1, tree2 );
- }
- }
-
- private static class ComposedNodeMapping< T > extends NodeMapping< T >
- {
- private final List< NodeMapping< T > > children;
-
- private ComposedNodeMapping( List< NodeMapping< T > > children )
- {
- super( children.stream().mapToDouble( NodeMapping::getCost ).sum() );
- this.children = children;
- }
-
- @Override
- public void writeToMap( Map< Tree< T >, Tree< T > > map )
- {
- children.forEach( child -> child.writeToMap( map ) );
- }
- }
}
diff --git a/src/main/java/org/mastodon/mamut/treesimilarity/NodeMappings.java b/src/main/java/org/mastodon/mamut/treesimilarity/NodeMappings.java
new file mode 100644
index 000000000..0aee94dc8
--- /dev/null
+++ b/src/main/java/org/mastodon/mamut/treesimilarity/NodeMappings.java
@@ -0,0 +1,130 @@
+package org.mastodon.mamut.treesimilarity;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.mastodon.mamut.treesimilarity.tree.Tree;
+
+/**
+ * Utility class for {@link ZhangUnorderedTreeEditDistance} that provides
+ * static factory methods for the easy creation of {@link NodeMapping}s.
+ */
+class NodeMappings
+{
+ private NodeMappings()
+ {
+ // prevent from instantiation
+ }
+
+ /**
+ * @return An empty {@link NodeMapping} with the specified cost.
+ * (Please note that the costs for an empty mapping are almost never
+ * zero!)
+ */
+ public static < T > NodeMapping< T > empty( double cost )
+ {
+ return new EmptyNodeMapping<>( cost );
+ }
+
+ /**
+ * @return A {@link NodeMapping} that represents a singleton Map from
+ * tree1 to tree2 with the specified cost.
+ */
+ public static < T > NodeMapping< T > singleton( double cost, Tree< T > tree1, Tree< T > tree2 )
+ {
+ return new SingletonNodeMapping<>( tree1, tree2, cost );
+ }
+
+ /**
+ * @return A {@link NodeMapping} that represents a composed that contains
+ * all the map entries of the given {@code children}. The costs of the
+ * composed mapping is the sum of the costs of the children.
+ */
+ @SafeVarargs
+ public static < T > NodeMapping< T > compose( NodeMapping< T >... children )
+ {
+ return compose( Arrays.asList( children ) );
+ }
+
+ /**
+ * @return A {@link NodeMapping} that represents a composed that contains
+ * all the map entries of the given {@code children}. The costs of the
+ * composed mapping is the sum of the costs of the children.
+ */
+ public static < T > NodeMapping< T > compose( List< NodeMapping< T > > children )
+ {
+ return new ComposedNodeMapping<>( children );
+ }
+
+ private abstract static class AbstractNodeMapping< T > implements NodeMapping< T >
+ {
+ private final double cost;
+
+ protected AbstractNodeMapping( double cost )
+ {
+ this.cost = cost;
+ }
+
+ @Override
+ public double getCost()
+ {
+ return cost;
+ }
+
+ @Override
+ public abstract void writeToMap( Map< Tree< T >, Tree< T > > map );
+
+ }
+
+ private static class EmptyNodeMapping< T > extends AbstractNodeMapping< T >
+ {
+ private EmptyNodeMapping( double cost )
+ {
+ super( cost );
+ }
+
+ @Override
+ public void writeToMap( Map< Tree< T >, Tree< T > > map )
+ {
+ // do nothing
+ }
+ }
+
+ private static class SingletonNodeMapping< T > extends AbstractNodeMapping< T >
+ {
+ private final Tree< T > tree1;
+
+ private final Tree< T > tree2;
+
+ private SingletonNodeMapping( Tree< T > tree1, Tree< T > tree2, double cost )
+ {
+ super( cost );
+ this.tree1 = tree1;
+ this.tree2 = tree2;
+ }
+
+ @Override
+ public void writeToMap( Map< Tree< T >, Tree< T > > map )
+ {
+ map.put( tree1, tree2 );
+ }
+ }
+
+ private static class ComposedNodeMapping< T > extends AbstractNodeMapping< T >
+ {
+ private final List< NodeMapping< T > > children;
+
+ private ComposedNodeMapping( List< NodeMapping< T > > children )
+ {
+ super( children.stream().mapToDouble( NodeMapping::getCost ).sum() );
+ this.children = children;
+ }
+
+ @Override
+ public void writeToMap( Map< Tree< T >, Tree< T > > map )
+ {
+ children.forEach( child -> child.writeToMap( map ) );
+ }
+ }
+}
diff --git a/src/main/java/org/mastodon/mamut/treesimilarity/ZhangUnorderedTreeEditDistance.java b/src/main/java/org/mastodon/mamut/treesimilarity/ZhangUnorderedTreeEditDistance.java
index 3ac9ed138..04d9affce 100644
--- a/src/main/java/org/mastodon/mamut/treesimilarity/ZhangUnorderedTreeEditDistance.java
+++ b/src/main/java/org/mastodon/mamut/treesimilarity/ZhangUnorderedTreeEditDistance.java
@@ -245,14 +245,14 @@ private NodeMapping< T > treeMapping( Tree< T > tree1, Tree< T > tree2 )
private NodeMapping< T > computeTreeMapping( Tree< T > tree1, Tree< T > tree2 )
{
- NodeMapping< T > attributeMapping = NodeMapping.singleton( attributeDistances.get( Pair.of( tree1, tree2 ) ), tree1, tree2 );
+ NodeMapping< T > attributeMapping = NodeMappings.singleton( attributeDistances.get( Pair.of( tree1, tree2 ) ), tree1, tree2 );
if ( tree1.isLeaf() && tree2.isLeaf() )
return attributeMapping;
// NB: the order of the following three lines is important, changing the order will result in a wrong distance.
NodeMapping< T > insertOperationCosts = insertOperationMapping( tree1, tree2 );
NodeMapping< T > deleteOperationCosts = deleteOperationMapping( tree1, tree2 );
- NodeMapping< T > changeCosts = NodeMapping.compose( attributeMapping, forestMapping( tree1, tree2 ) );
+ NodeMapping< T > changeCosts = NodeMappings.compose( attributeMapping, forestMapping( tree1, tree2 ) );
return findBestMapping( insertOperationCosts, deleteOperationCosts, changeCosts );
}
@@ -290,10 +290,10 @@ private NodeMapping< T > computeForestMapping( Tree< T > forest1, Tree< T > fore
throw new IllegalArgumentException( "The given trees are both leaves and thus they are both not forests." );
if ( forest1IsLeaf )
- return NodeMapping.empty( insertCosts.get( forest2 ).forestCost );
+ return NodeMappings.empty( insertCosts.get( forest2 ).forestCost );
if ( forest2IsLeaf )
- return NodeMapping.empty( deleteCosts.get( forest1 ).forestCost );
+ return NodeMappings.empty( deleteCosts.get( forest1 ).forestCost );
NodeMapping< T > forestInsertCosts = forestInsertMapping( forest1, forest2 );
NodeMapping< T > forestDeleteCosts = forestDeleteMapping( forest1, forest2 );
@@ -312,9 +312,9 @@ private NodeMapping< T > insertOperationMapping( Tree< T > tree1, Tree< T > tree
double insertCostTree2 = insertCosts.get( tree2 ).treeCost;
return findBestMapping( tree2.getChildren(), child ->
{
- NodeMapping< T > insertCosts = NodeMapping.empty( insertCostTree2 - this.insertCosts.get( child ).treeCost );
+ NodeMapping< T > insertCosts = NodeMappings.empty( insertCostTree2 - this.insertCosts.get( child ).treeCost );
NodeMapping< T > childMapping = treeMapping( tree1, child );
- return NodeMapping.compose( insertCosts, childMapping );
+ return NodeMappings.compose( insertCosts, childMapping );
} );
}
@@ -329,9 +329,9 @@ private NodeMapping< T > deleteOperationMapping( Tree< T > tree1, Tree< T > tree
double deleteCostTree1 = deleteCosts.get( tree1 ).treeCost;
return findBestMapping( tree1.getChildren(), child ->
{
- NodeMapping< T > deleteCosts = NodeMapping.empty( deleteCostTree1 - this.deleteCosts.get( child ).treeCost );
+ NodeMapping< T > deleteCosts = NodeMappings.empty( deleteCostTree1 - this.deleteCosts.get( child ).treeCost );
NodeMapping< T > childMapping = treeMapping( child, tree2 );
- return NodeMapping.compose( deleteCosts, childMapping );
+ return NodeMappings.compose( deleteCosts, childMapping );
} );
}
@@ -345,9 +345,9 @@ private NodeMapping< T > forestInsertMapping( Tree< T > forest1, Tree< T > fores
double insertCostForest2 = insertCosts.get( forest2 ).forestCost;
return findBestMapping( forest2.getChildren(), child ->
{
- NodeMapping< T > insertCosts = NodeMapping.empty( insertCostForest2 - this.insertCosts.get( child ).forestCost );
+ NodeMapping< T > insertCosts = NodeMappings.empty( insertCostForest2 - this.insertCosts.get( child ).forestCost );
NodeMapping< T > childMapping = forestMapping( forest1, child );
- return NodeMapping.compose( insertCosts, childMapping );
+ return NodeMappings.compose( insertCosts, childMapping );
} );
}
@@ -361,9 +361,9 @@ private NodeMapping< T > forestDeleteMapping( Tree< T > forest1, Tree< T > fores
double deleteCostForest1 = deleteCosts.get( forest1 ).forestCost;
return findBestMapping( forest1.getChildren(), child ->
{
- NodeMapping< T > deleteCosts = NodeMapping.empty( deleteCostForest1 - this.deleteCosts.get( child ).forestCost );
+ NodeMapping< T > deleteCosts = NodeMappings.empty( deleteCostForest1 - this.deleteCosts.get( child ).forestCost );
NodeMapping< T > childMapping = forestMapping( child, forest2 );
- return NodeMapping.compose( deleteCosts, childMapping );
+ return NodeMappings.compose( deleteCosts, childMapping );
} );
}
@@ -374,7 +374,7 @@ private NodeMapping< T > forestDeleteMapping( Tree< T > forest1, Tree< T > fores
*/
private NodeMapping< T > findBestMapping( Collection< Tree< T > > children, Function< Tree< T >, NodeMapping< T > > function )
{
- NodeMapping< T > best = NodeMapping.empty( Double.POSITIVE_INFINITY );
+ NodeMapping< T > best = NodeMappings.empty( Double.POSITIVE_INFINITY );
for ( Tree< T > child : children )
{
NodeMapping< T > nodeMapping = function.apply( child );
@@ -427,18 +427,18 @@ private NodeMapping< T > minCostMaxFlow( final Tree< T > forest1, final Tree< T
for ( Tree< T > child1 : childrenForest1 )
if ( isFlowEqualToOne( network.getFlow( child1, emptyTree2 ) ) )
- childMappings.add( NodeMapping.empty( deleteCosts.get( child1 ).treeCost ) );
+ childMappings.add( NodeMappings.empty( deleteCosts.get( child1 ).treeCost ) );
for ( Tree< T > child2 : childrenForest2 )
if ( isFlowEqualToOne( network.getFlow( emptyTree1, child2 ) ) )
- childMappings.add( NodeMapping.empty( insertCosts.get( child2 ).treeCost ) );
+ childMappings.add( NodeMappings.empty( insertCosts.get( child2 ).treeCost ) );
for ( Tree< T > child1 : childrenForest1 )
for ( Tree< T > child2 : childrenForest2 )
if ( isFlowEqualToOne( network.getFlow( child1, child2 ) ) )
childMappings.add( treeMapping( child1, child2 ) );
- return NodeMapping.compose( childMappings );
+ return NodeMappings.compose( childMappings );
}
/**