org.grouplens.grapht.graph.MergePool.java Source code

Java tutorial

Introduction

Here is the source code for org.grouplens.grapht.graph.MergePool.java

Source

/*
 * Grapht, an open source dependency injector.
 * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
 * Copyright 2010-2014 Regents of the University of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.grouplens.grapht.graph;

import com.google.common.base.Functions;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Merges graphs to remove redundant nodes.  This takes graphs and merges them, pruning redundant
 * nodes within the graphs and between graphs previously merged.  It remembers graphs it has
 * previously seen to allow nodes to be reused across multiple graphs.
 *
 * @param <V> The vertex type of graphs to merge.
 * @param <E> The edge type of graphs to merge.
 * @since 0.7
 * @author <a href="http://grouplens.org">GroupLens Research</a>
 */
public class MergePool<V, E> {
    // TODO Allow arbitrary equivalence relations over graph nodes so this class is less specialized.
    private static final Logger logger = LoggerFactory.getLogger(MergePool.class);

    private final Set<DAGNode<V, E>> pool;

    private MergePool() {
        pool = Sets.newLinkedHashSet();
    }

    /**
     * Create a merge pool that checks node labels for equality.
     *
     * @param <V> The node label type.
     * @param <E> The edge label type.
     * @return A new merge pool.
     */
    public static <V, E> MergePool<V, E> create() {
        return new MergePool<V, E>();
    }

    /**
     * Merge and simplify a graph.  This will coalesce redundant nodes (equivalent labels and
     * outgoing edge destinations), and will prefer to use nodes from graphs seen previously.
     * This allows deduplication across multiple graphs.
     *
     * <p><strong>Noteo:</strong> edge labels are ignored for the purpose of merging.</p>
     *
     * @param graph The graph to simplify.
     * @return The new simplified, merged graph.
     */
    public DAGNode<V, E> merge(DAGNode<V, E> graph) {
        List<DAGNode<V, E>> sorted = graph.getSortedNodes();

        Map<Pair<V, Set<DAGNode<V, E>>>, DAGNode<V, E>> nodeTable = Maps.newHashMap();
        for (DAGNode<V, E> node : pool) {
            Pair<V, Set<DAGNode<V, E>>> key = Pair.of(node.getLabel(), node.getAdjacentNodes());
            assert !nodeTable.containsKey(key);
            nodeTable.put(key, node);
        }

        // We want to map nodes to their previous merged versions
        Map<DAGNode<V, E>, DAGNode<V, E>> mergedMap = Maps.newHashMap();
        // Now start processing nodes
        for (DAGNode<V, E> toMerge : sorted) {
            V sat = toMerge.getLabel();
            // Resolve the merged neighbors of this node.  They have already been
            // merged, since we are going in topological order.
            Set<DAGNode<V, E>> neighbors = FluentIterable.from(toMerge.getOutgoingEdges())
                    .transform(DAGEdge.<V, E>extractTail()).transform(Functions.forMap(mergedMap)).toSet();

            // See if we have already created an equivalent to this node
            DAGNode<V, E> newNode = nodeTable.get(Pair.of(sat, neighbors));
            if (newNode == null) {
                // No, let's start building one
                DAGNodeBuilder<V, E> bld = DAGNode.newBuilder();

                boolean changed = false;
                bld.setLabel(sat);
                logger.debug("Adding new node to merged graph for satisfaction: {}", sat);

                for (DAGEdge<V, E> edge : toMerge.getOutgoingEdges()) {
                    // create a new edge with the merged tail and same label
                    DAGNode<V, E> filtered = mergedMap.get(edge.getTail());
                    bld.addEdge(filtered, edge.getLabel());
                    // have we made a change to this node?
                    changed |= !filtered.equals(edge.getTail());
                }

                if (changed) {
                    // one of the node's neighbors has been replaced with merged version
                    // so use the new node
                    newNode = bld.build();
                } else {
                    // no edges were changed, leave the node unmodified
                    newNode = toMerge;
                }
                nodeTable.put(Pair.of(sat, neighbors), newNode);
            } else {
                logger.debug("Node already in merged graph for satisfaction: {}", toMerge.getLabel());
            }

            // update merge map so future equivalent nodes get replaced with this one
            mergedMap.put(toMerge, newNode);
        }

        // now let's find our return value - what did we merge the graph root to?
        DAGNode<V, E> newRoot = mergedMap.get(graph);
        // remember all its nodes for future merge operations
        pool.addAll(newRoot.getReachableNodes());
        // and we're done
        return newRoot;
    }
}