com.google.javascript.jscomp.graph.GraphColoring Maven / Gradle / Ivy
Show all versions of closure-compiler-unshaded Show documentation
/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp.graph;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Comparator.comparing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
/**
* Annotates the graph with a color in a way that no connected node will have
* the same color. Nodes of the same color can then be partitioned together and
* be represented by a super node. This class will merely annotate the nodes
* with a color using {@link GraphNode#setAnnotation(Annotation)} and provide
* a node to super node mapping with {@link #getPartitionSuperNode(Object)}. The
* given graph itself will not be modified.
*
* This algorithm is NOT deterministic by default. Passes that
* requires deterministic output should provide a {@code Comparator} in the
* constructor as a tie-breaker. This tie-break will be used when deciding
* which node should be colored first when multiple nodes have the same degree.
*
* @param Value type that the graph node stores.
* @param Value type that the graph edge stores.
*
*/
public abstract class GraphColoring {
// Maps a color (represented by an integer) to a variable. If, for example,
// the color 5 is mapped to "foo". Then any other variables colored with the
// color 5 will now use the name "foo".
protected N[] colorToNodeMap;
protected final AdjacencyGraph graph;
public GraphColoring(AdjacencyGraph graph) {
this.graph = graph;
}
/**
* Annotates the graph with {@link Color} objects using
* {@link GraphNode#setAnnotation(Annotation)}.
*
* @return The number of unique colors need.
*/
public abstract int color();
/**
* Using the coloring as partitions, finds the node that represents that
* partition as the super node. The first to retrieve its partition will
* become the super node.
*/
public N getPartitionSuperNode(N node) {
checkNotNull(colorToNodeMap, "No coloring founded. color() should be called first.");
Color color = graph.getNode(node).getAnnotation();
N headNode = colorToNodeMap[color.value];
if (headNode == null) {
colorToNodeMap[color.value] = node;
return node;
} else {
return headNode;
}
}
public AdjacencyGraph getGraph() {
return graph;
}
/** The color of a node */
public static class Color implements Annotation {
int value = 0;
Color(int value) {
this.value = value;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof Color)) {
return false;
} else {
return value == ((Color) other).value;
}
}
@Override
public int hashCode() {
return value;
}
}
/**
* Greedily assign nodes with high degree unique colors.
*/
public static class GreedyGraphColoring extends GraphColoring {
private final Comparator tieBreaker;
public GreedyGraphColoring(AdjacencyGraph graph) {
this(graph, null);
}
/**
* @param tieBreaker In case of a tie between two nodes of the same degree,
* this comparator will determine which node should be colored first.
*/
public GreedyGraphColoring(
AdjacencyGraph graph, Comparator tieBreaker) {
super(graph);
this.tieBreaker = tieBreaker;
}
@Override
public int color() {
List> worklist = new ArrayList<>(graph.getNodes());
// Sort nodes by degree.
Collections.sort(
worklist,
comparing(
GraphNode::getValue,
// TODO(b/28382956): Take better advantage of Java8 comparing() to simplify this
(leftProperty, rightProperty) -> {
int result = graph.getWeight(rightProperty) - graph.getWeight(leftProperty);
return result == 0 && tieBreaker != null
? tieBreaker.compare(leftProperty, rightProperty)
: result;
}));
// Idea: From the highest to lowest degree, assign any uncolored node with
// a unique color if none of its neighbors has been assigned that color.
int count = 0;
do {
Color color = new Color(count);
SubGraph subgraph = graph.newSubGraph();
for (Iterator> i = worklist.iterator(); i.hasNext();) {
GraphNode node = i.next();
if (subgraph.isIndependentOf(node.getValue())) {
subgraph.addNode(node.getValue());
node.setAnnotation(color);
i.remove();
}
}
count++;
} while (!worklist.isEmpty());
@SuppressWarnings("unchecked")
N[] map = (N[]) new Object[count];
colorToNodeMap = map;
return count;
}
}
}