All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.graph.StandardUnionFind Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * 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.checkArgument;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Iterators.filter;
import static com.google.common.collect.Multimaps.asMap;

import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.SetMultimap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.jspecify.nullness.Nullable;

/**
 * A Union-Find implementation.
 *
 * 

This class implements Union-Find algorithm with rank and path compression. * *

See algorithmist for more detail. * * @param element type */ @GwtCompatible public class StandardUnionFind implements Serializable, UnionFind { /** All values with the same root node are in the same equivalence set. */ private final Map> elmap = new LinkedHashMap<>(); /** Creates an empty UnionFind structure. */ public StandardUnionFind() { } /** * Creates an UnionFind structure being a copy of other structure. * The created structure is optimal in a sense that the paths to * the root from any element will have a length of at most 1. * * @param other structure to be copied */ public StandardUnionFind(UnionFind other) { for (E elem : other.elements()) { union(other.find(elem), elem); } } @Override public void add(@Nullable E e) { union(e, e); } public void addAll(Iterable es) { for (E e : es) { this.add(e); } } @CanIgnoreReturnValue @Override public E union(@Nullable E a, @Nullable E b) { Node nodeA = findRootOrCreateNode(a); Node nodeB = findRootOrCreateNode(b); if (nodeA == nodeB) { return nodeA.element; } // If possible, prefer nodeA over nodeB, to preserve insertion order. if (nodeA.rank >= nodeB.rank) { nodeB.parent = nodeA; nodeA.size += nodeB.size; if (nodeA.rank == nodeB.rank) { nodeA.rank++; } return nodeA.element; } nodeA.parent = nodeB; nodeB.size += nodeA.size; E temp = nodeB.element; nodeB.element = nodeA.element; nodeA.element = temp; return nodeB.element; } @Override public E find(@Nullable E e) { checkArgument(elmap.containsKey(e), "Element does not exist: %s", e); return findRoot(elmap.get(e)).element; } @Override public boolean areEquivalent(@Nullable E a, @Nullable E b) { E aRep = find(a); E bRep = find(b); return aRep == bRep; } @Override public Set elements() { return Collections.unmodifiableSet(elmap.keySet()); } @Override public ImmutableList> allEquivalenceClasses() { SetMultimap, E> groupsTmp = MultimapBuilder.linkedHashKeys().linkedHashSetValues().build(); for (Node elem : elmap.values()) { groupsTmp.put(findRoot(elem), elem.element); } ImmutableList.Builder> result = ImmutableList.builder(); for (Set group : asMap(groupsTmp).values()) { result.add(ImmutableSet.copyOf(group)); } return result.build(); } /** * Return the reprsentative elements of all the equivalence classes. * *

This is a "snapshot" view of the representatives at the time the method was called. */ public ImmutableSet allRepresentatives() { return this.elmap.values().stream() .filter((n) -> n == n.parent) .map((n) -> n.element) .collect(toImmutableSet()); } /** * If e is already in a non-trivial equivalence class, that is, a class with * more than two elements, then return the {@link Node} corresponding to the * representative element. Otherwise, if e sits in an equivalence class by * itself, then create a {@link Node}, put it into elmap and return it. */ private Node findRootOrCreateNode(E e) { Node node = elmap.get(e); if (node != null) { return findRoot(node); } node = new Node(e); elmap.put(e, node); return node; } /** * Given a {@link Node}, walk the parent field as far as possible, until * reaching the root, which is the {@link Node} for the current * representative of this equivalence class. To achieve low runtime * complexity, also compress the path, by making each node a direct child of * the root. */ private Node findRoot(Node node) { if (node.parent != node) { node.parent = findRoot(node.parent); } return node.parent; } @Override public Set findAll(final @Nullable E value) { checkArgument(elmap.containsKey(value), "Element does not exist: %s", value); final Predicate isSameRoot = new Predicate() { /** some node that's close to the root, or null */ Node nodeForValue = elmap.get(value); @Override public boolean apply(@Nullable Object b) { if (Objects.equal(value, b)) { return true; } Node nodeForB = elmap.get(b); if (nodeForB == null) { return false; } nodeForValue = findRoot(nodeForValue); return findRoot(nodeForB) == nodeForValue; } }; return new AbstractSet() { @Override public boolean contains(Object o) { return isSameRoot.apply(o); } @Override public Iterator iterator() { return filter(elmap.keySet().iterator(), isSameRoot); } @Override public int size() { return findRoot(elmap.get(value)).size; } }; } /** The internal node representation. */ private static class Node { /** The parent node of this element. */ Node parent; /** The element represented by this node. */ E element; /** A bound on the depth of the subtree rooted to this node. */ int rank = 0; /** * If this node is the root of a tree, this is the number of elements in the * tree. Otherwise, it's undefined. */ int size = 1; Node(E element) { this.parent = this; this.element = element; } } }