From f45fd7fa6403e5ae22a3c50d962b53f93a3af3fb Mon Sep 17 00:00:00 2001 From: Matthias Arzt Date: Wed, 19 Jul 2023 10:47:52 +0200 Subject: [PATCH] Make NodeMapping an interface --- .../mamut/treesimilarity/NodeMapping.java | 113 ++++----------- .../mamut/treesimilarity/NodeMappings.java | 130 ++++++++++++++++++ .../ZhangUnorderedTreeEditDistance.java | 32 ++--- 3 files changed, 172 insertions(+), 103 deletions(-) create mode 100644 src/main/java/org/mastodon/mamut/treesimilarity/NodeMappings.java 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 ); } /**