View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht.graph;
21  
22  import com.google.common.base.Functions;
23  import com.google.common.collect.FluentIterable;
24  import com.google.common.collect.Maps;
25  import com.google.common.collect.Sets;
26  import org.apache.commons.lang3.tuple.Pair;
27  import org.slf4j.Logger;
28  import org.slf4j.LoggerFactory;
29  
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  /**
35   * Merges graphs to remove redundant nodes.  This takes graphs and merges them, pruning redundant
36   * nodes within the graphs and between graphs previously merged.  It remembers graphs it has
37   * previously seen to allow nodes to be reused across multiple graphs.
38   *
39   * @param <V> The vertex type of graphs to merge.
40   * @param <E> The edge type of graphs to merge.
41   * @since 0.7
42   * @author <a href="http://grouplens.org">GroupLens Research</a>
43   */
44  public class MergePool<V,E> {
45      // TODO Allow arbitrary equivalence relations over graph nodes so this class is less specialized.
46      private static final Logger logger = LoggerFactory.getLogger(MergePool.class);
47  
48      private final Set<DAGNode<V,E>> pool;
49  
50      private MergePool() {
51          pool = Sets.newLinkedHashSet();
52      }
53  
54      /**
55       * Create a merge pool that checks node labels for equality.
56       *
57       * @param <V> The node label type.
58       * @param <E> The edge label type.
59       * @return A new merge pool.
60       */
61      public static <V,E> MergePool<V,E> create() {
62          return new MergePool<V, E>();
63      }
64  
65      /**
66       * Merge and simplify a graph.  This will coalesce redundant nodes (equivalent labels and
67       * outgoing edge destinations), and will prefer to use nodes from graphs seen previously.
68       * This allows deduplication across multiple graphs.
69       *
70       * <p><strong>Noteo:</strong> edge labels are ignored for the purpose of merging.</p>
71       *
72       * @param graph The graph to simplify.
73       * @return The new simplified, merged graph.
74       */
75      public DAGNode<V,E> merge(DAGNode<V, E> graph) {
76          List<DAGNode<V, E>> sorted = graph.getSortedNodes();
77  
78          Map<Pair<V,Set<DAGNode<V,E>>>, DAGNode<V,E>> nodeTable = Maps.newHashMap();
79          for (DAGNode<V, E> node: pool) {
80              Pair<V, Set<DAGNode<V, E>>> key =
81                      Pair.of(node.getLabel(), node.getAdjacentNodes());
82              assert !nodeTable.containsKey(key);
83              nodeTable.put(key, node);
84          }
85  
86          // We want to map nodes to their previous merged versions
87          Map<DAGNode<V,E>, DAGNode<V,E>> mergedMap = Maps.newHashMap();
88          // Now start processing nodes
89          for (DAGNode<V, E> toMerge: sorted) {
90              V sat = toMerge.getLabel();
91              // Resolve the merged neighbors of this node.  They have already been
92              // merged, since we are going in topological order.
93              Set<DAGNode<V, E>> neighbors =
94                      FluentIterable.from(toMerge.getOutgoingEdges())
95                                    .transform(DAGEdge.<V,E>extractTail())
96                                    .transform(Functions.forMap(mergedMap))
97                                    .toSet();
98  
99              // See if we have already created an equivalent to this node
100             DAGNode<V, E> newNode = nodeTable.get(Pair.of(sat, neighbors));
101             if (newNode == null) {
102                 // No, let's start building one
103                 DAGNodeBuilder<V,E> bld = DAGNode.newBuilder();
104 
105                 boolean changed = false;
106                 bld.setLabel(sat);
107                 logger.debug("Adding new node to merged graph for satisfaction: {}", sat);
108 
109                 for (DAGEdge<V, E> edge: toMerge.getOutgoingEdges()) {
110                     // create a new edge with the merged tail and same label
111                     DAGNode<V, E> filtered = mergedMap.get(edge.getTail());
112                     bld.addEdge(filtered, edge.getLabel());
113                     // have we made a change to this node?
114                     changed |= !filtered.equals(edge.getTail());
115                 }
116 
117                 if (changed) {
118                     // one of the node's neighbors has been replaced with merged version
119                     // so use the new node
120                     newNode = bld.build();
121                 } else {
122                     // no edges were changed, leave the node unmodified
123                     newNode = toMerge;
124                 }
125                 nodeTable.put(Pair.of(sat, neighbors), newNode);
126             } else {
127                 logger.debug("Node already in merged graph for satisfaction: {}", toMerge.getLabel());
128             }
129 
130             // update merge map so future equivalent nodes get replaced with this one
131             mergedMap.put(toMerge, newNode);
132         }
133 
134         // now let's find our return value - what did we merge the graph root to?
135         DAGNode<V, E> newRoot = mergedMap.get(graph);
136         // remember all its nodes for future merge operations
137         pool.addAll(newRoot.getReachableNodes());
138         // and we're done
139         return newRoot;
140     }
141 }