From 7d95d5f991bfe624bd9618f1e3e7983db220e710 Mon Sep 17 00:00:00 2001 From: btrekkie Date: Wed, 6 Mar 2019 16:36:08 -0500 Subject: [PATCH 01/29] Initial commit --- LICENSE | 21 +++++++++++++++++++++ README.md | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..b9f0f2eb5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 btrekkie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..80934f520 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# dynamic-connectivity +Data structure for dynamic connectivity in undirected graphs. The ConnGraph class supports adding and removing edges and determining whether two vertices are connected (whether there is a path between them) in polylogarithmic time. From a60eac5b6c8c8da02d440a9116637b55009ba442 Mon Sep 17 00:00:00 2001 From: William Jacobs Date: Wed, 6 Mar 2019 16:46:45 -0500 Subject: [PATCH 02/29] Initial commit This adds the initial contents of the repository. --- .classpath | 38 + .gitignore | 15 + .project | 23 + .settings/org.eclipse.jdt.core.prefs | 14 + .settings/org.eclipse.m2e.core.prefs | 4 + README.md | 51 +- pom.xml | 55 + .../btrekkie/connectivity/Augmentation.java | 39 + .../btrekkie/connectivity/ConnEdge.java | 61 + .../btrekkie/connectivity/ConnGraph.java | 1183 +++++++++++++++++ .../btrekkie/connectivity/ConnVertex.java | 41 + .../btrekkie/connectivity/EulerTourEdge.java | 30 + .../btrekkie/connectivity/EulerTourNode.java | 112 ++ .../connectivity/EulerTourVertex.java | 43 + .../btrekkie/connectivity/VertexInfo.java | 32 + .../connectivity/test/ConnGraphTest.java | 992 ++++++++++++++ .../btrekkie/connectivity/test/SumAndMax.java | 39 + ...nnectivity-0.1.0-jar-with-dependencies.jar | Bin 0 -> 35737 bytes target/dynamic-connectivity-0.1.0.jar | Bin 0 -> 16535 bytes 19 files changed, 2770 insertions(+), 2 deletions(-) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 pom.xml create mode 100644 src/main/java/com/github/btrekkie/connectivity/Augmentation.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/ConnEdge.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/ConnGraph.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/ConnVertex.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java create mode 100644 src/main/java/com/github/btrekkie/connectivity/VertexInfo.java create mode 100644 src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java create mode 100644 src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java create mode 100644 target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar create mode 100644 target/dynamic-connectivity-0.1.0.jar diff --git a/.classpath b/.classpath new file mode 100644 index 000000000..e912f9f2b --- /dev/null +++ b/.classpath @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..118c59c78 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.DS_Store +target/** +!target/dynamic-connectivity*.jar diff --git a/.project b/.project new file mode 100644 index 000000000..aee6738ae --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + DynamicConnectivity + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..552c39646 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,14 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 000000000..f897a7f1c --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/README.md b/README.md index 80934f520..8a334d363 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ -# dynamic-connectivity -Data structure for dynamic connectivity in undirected graphs. The ConnGraph class supports adding and removing edges and determining whether two vertices are connected (whether there is a path between them) in polylogarithmic time. +# Description +This provides the `ConnGraph` class, which implements an undirected graph with +dynamic connectivity. It supports adding and removing edges and determining +whether two vertices are connected - whether there is a path between them. +Adding and removing edges take O(log2N) amortized time with high +probability, while checking whether two vertices are connected takes O(log N) +time with high probability. Check the source code to see the full API and the +Javadoc documentation. + +# Features +* Efficiently add and remove edges and determine whether vertices are connected. +* A vertex can appear in multiple graphs, with a different set of adjacent + vertices in each graph. +* `ConnGraph` supports arbitrary vertex augmentation. Given a vertex V, + `ConnGraph` can quickly report the result of combining the augmentations of + all of the vertices in the connected component containing V, using a combining + function provided to the constructor. For example, if a `ConnGraph` represents + a game map, then given the location of the player, we can quickly determine + the amount of gold the player can access, or the strongest monster that can + reach him. Retrieving the combined augmentation for a connected component + takes O(log N) time with high probability. +* Compatible with Java 6.0 and above. + +# Limitations +* `ConnGraph` does not directly support augmenting edges. However, this can be + accomplished by imputing each edge's augmentation to an adjacent vertex. For + example, if each edge contains a certain amount of gold, then we can augment + each vertex with the amount of gold in the adjacent edges. We can then + calculate the amount of gold in a connected component by retrieving the + component's augmentation and dividing by two. A more general approach would be + to store the edges adjacent to each vertex in an augmented self-balancing + binary search tree (see + [RedBlackNode](https://github.com/btrekkie/RedBlackNode)), and to use this to + assign an augmentation to each vertex. +* Careful attention has been paid to the asymptotic running time of each method. + However, beyond this, no special effort has been made to optimize performance. + +# Example usage +```java +ConnGraph graph = new ConnGraph(); +ConnVertex vertex1 = new ConnVertex(); +ConnVertex vertex2 = new ConnVertex(); +ConnVertex vertex3 = new ConnVertex(); +graph.addEdge(vertex1, vertex2); +graph.addEdge(vertex2, vertex3); +graph.connected(vertex1, vertex3); // Returns true +graph.removeEdge(vertex1, vertex2); +graph.connected(vertex1, vertex3); // Returns false +``` diff --git a/pom.xml b/pom.xml new file mode 100644 index 000000000..604e31212 --- /dev/null +++ b/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + com.github.btrekkie.connectivity + dynamic-connectivity + 0.1.0 + dynamic-connectivity + Data structure for dynamic connectivity in undirected graphs + + + + maven-assembly-plugin + + + package + + single + + + + + + jar-with-dependencies + + + + + maven-compiler-plugin + 3.8.0 + + 1.6 + 1.6 + + + + + + + jitpack.io + https://jitpack.io + + + + + com.github.btrekkie + RedBlackNode + 1.0.0 + + + junit + junit + 4.12 + test + + + \ No newline at end of file diff --git a/src/main/java/com/github/btrekkie/connectivity/Augmentation.java b/src/main/java/com/github/btrekkie/connectivity/Augmentation.java new file mode 100644 index 000000000..1d2a443c8 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/Augmentation.java @@ -0,0 +1,39 @@ +package com.github.btrekkie.connectivity; + +/** + * A combining function for taking the augmentations associated with a set of ConnVertices and reducing them to a single + * result. The function is a binary operation for combining two values into one. For example, given vertices with + * augmentations A1, A2, A3, and A4, the combined result may be obtained by computing + * combine(combine(combine(A1, A2), A3), A4). In order for an augmentation result to be meaningful, the combining + * function must be commutative, meaning combine(x, y) is equivalent to combine(y, x), and associative, meaning + * combine(x, combine(y, z)) is equivalent to combine(combine(x, y), z). + * + * If a ConnGraph represents a game map, then one example of an augmentation would be the amount of gold accessible from + * a certain location. Each vertex would be augmented with the amount of gold in that location, and the combining + * function would add the two amounts of gold passed in as arguments. Another example would be the strongest monster + * that can reach a particular location. Each vertex with at least one monster would be augmented with a pointer to the + * strongest monster at that location, and the combining function would return the stronger of the two monsters passed + * in as arguments. A third example would be the number of locations accessible from a given vertex. Each vertex would + * be augmented with the number 1, and the combining function would add the two numbers of vertices passed in as + * arguments. + * + * ConnGraph treats two augmentation values X and Y as interchangeable if they are equal, as in + * X != null ? X.equals(Y) : Y == null. The same holds for two combined augmentation values, and for one combined + * augmentation value and one augmentation value. + * + * See the comments for ConnGraph. + */ +public interface Augmentation { + /** + * Returns the result of combining the specified values into one. Each argument is either the augmentation + * information associated with a vertex, or the result of a previous call to "combine". + * + * Note that a value of null is never passed in to indicate the absence of augmentation information. The fact that + * ConnGraph.getVertexAugmentation, for example, may return null when there is no associated augmentation might lead + * you to believe that a null argument indicates the absence of augmentation information, but again, it does not. A + * null argument can only mean that a vertex is explicitly associated with null augmentation information, due to a + * prior call to ConnGraph.setVertexAugmentation(vertex, null), or that the "combine" method previously returned + * null. + */ + public Object combine(Object value1, Object value2); +} diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java new file mode 100644 index 000000000..691aa6ed7 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java @@ -0,0 +1,61 @@ +package com.github.btrekkie.connectivity; + +/** + * Represents an edge in a ConnGraph, at the level of the edge (i.e. at the lowest level i for which G_i contains the + * edge). Every graph edge has exactly one corresponding ConnEdge object, regardless of the number of levels it appears + * in. See the comments for the implementation of ConnGraph. + * + * ConnEdges are stored in the linked lists suggested by EulerTourVertex.graphListHead and + * EulerTourVertex.forestListHead. Each ConnEdge is in two linked lists, so care must be taken when traversing the + * linked lists. prev1 and next1 are the links for the list starting at vertex1.graphListHead or vertex1.forestListHead, + * while prev2 and next2 are the links for vertex2. But the vertex1 and vertex2 fields of a given edge are different + * from the vertex1 and vertex2 fields of the linked edges. For example, the edge after next1 is not necessarily + * next1.next1. It depends on whether next1.vertex1 is the same as vertex1. If next1.vertex1 == vertex1, then the edge + * after next1 is next1.next1, but otherwise, it is next1.next2. + */ +class ConnEdge { + /** The edge's first endpoint (at the same level as the edge). */ + public EulerTourVertex vertex1; + + /** The edge's second endpoint (at the same level as the edge). */ + public EulerTourVertex vertex2; + + /** + * The EulerTourEdge object describing the edge's presence in an Euler tour tree, at the same level as the edge, or + * null if the edge is not in the Euler tour forest F_i. + */ + public EulerTourEdge eulerTourEdge; + + /** + * The edge preceding this in a linked list of same-level edges adjacent to vertex1, if any. The edge is either part + * of a list of non-forest edges starting with vertex1.graphListHead, or part of a list of forest edges starting + * with vertex1.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge prev1; + + /** + * The edge succeeding this in a linked list of same-level edges adjacent to vertex1, if any. The edge is either + * part of a list of non-forest edges starting with vertex1.graphListHead, or part of a list of forest edges + * starting with vertex1.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge next1; + + /** + * The edge preceding this in a linked list of same-level edges adjacent to vertex2, if any. The edge is either part + * of a list of non-forest edges starting with vertex2.graphListHead, or part of a list of forest edges starting + * with vertex2.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge prev2; + + /** + * The edge succeeding this in a linked list of same-level edges adjacent to vertex2, if any. The edge is either + * part of a list of non-forest edges starting with vertex2.graphListHead, or part of a list of forest edges + * starting with vertex2.forestListHead. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge next2; + + public ConnEdge(EulerTourVertex vertex1, EulerTourVertex vertex2) { + this.vertex1 = vertex1; + this.vertex2 = vertex2; + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java new file mode 100644 index 000000000..4f16deb76 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -0,0 +1,1183 @@ +package com.github.btrekkie.connectivity; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Implements an undirected graph with dynamic connectivity. It supports adding and removing edges and determining + * whether two vertices are connected - whether there is a path between them. Adding and removing edges take O(log^2 N) + * amortized time with high probability, while checking whether two vertices are connected takes O(log N) time with high + * probability. It uses O(V log V + E) space, where V is the number of vertices and E is the number of edges. Note that + * a ConnVertex may appear in multiple ConnGraphs, with a different set of adjacent vertices in each graph. + * + * ConnGraph optionally supports arbitrary augmentation. Each vertex may have an associated augmentation, or value. + * Given a vertex V, ConnGraph can quickly report the result of combining the augmentations of all of the vertices in + * the connected component containing V, using a combining function provided to the constructor. For example, if a + * ConnGraph represents a game map, then given the location of the player, we can quickly determine the amount of gold + * the player can access, or the strongest monster that can reach him. Augmentation does not affect the running time or + * space of ConnGraph in terms of big O notation, assuming the augmentation function takes a constant amount of time and + * the augmentation takes a constant amount of space. Retrieving the combined augmentation for a connected component + * takes O(log N) time with high probability. (Although ConnGraph does not directly support augmenting edges, this can + * also be accomplished, by imputing each edge's augmentation to an adjacent vertex.) + * + * When a vertex no longer has any adjacent edges, and it has no augmentation information, ConnGraph stops keeping track + * of the vertex. This reduces the time and space bounds of the ConnGraph, and it enables the ConnVertex to be garbage + * collected. If you know you are finished with a vertex, and that vertex has an augmentation, then you should call + * removeVertexAugmentation on the vertex, so that the graph can release it. + * + * As a side note, it would be more proper if ConnGraph had a generic type parameter indicating the type of the + * augmentation values. However, it is expected that it is more common not to use augmentation, so by not using a type + * parameter, we make usage of the ConnGraph class more convenient and less confusing in the common case. + */ +/* ConnGraph is implemented using a data structure described in + * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.89.919&rep=rep1&type=pdf (Holm, de Lichtenberg, and Thorup + * (1998): Poly-Logarithmic Deterministic Fully-Dynamic Algorithms for Connectivity, Minimum Spanning Tree, 2-Edge, and + * Biconnectivity). However, ConnGraph does not include the optimization of using a B-tree in the top level, so queries + * take O(log N) time rather than O(log N / log log N) time. + * + * This implementation is actually based on a slightly modified description of the data structure given in + * http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-20-dynamic-graphs-ii/ . + * The description in the video differs from the data structure in the paper in that the levels are numbered in reverse + * order, the constraint on tree sizes is different, and the augmentation uses booleans in place of edges. In addition, + * the video defines subgraphs G_i. The change in the constraint on tree sizes is beneficial because it makes it easier + * to delete vertices. + * + * Note that the data structure described in the video is faulty. In the procedure for deleting an edge, it directs us + * to push down some edges. When we push an edge from level i to level i - 1, we would need to add the edge to + * F_{i - 1}, if the endpoints were not already connected in G_{i - 1}. However, this would violate the invariant that + * F_i be a superset of F_{i - 1}. To fix this problem, before searching for a replacement edge in level i, ConnGraph + * first pushes all level-i edges in the relevant tree down to level i - 1 and adds them to F_{i - 1}, as in the + * original paper. That way, when we subsequently push down edges, we can safely add them to G_{i - 1} without also + * adding them to F_{i - 1}. In order to do this efficiently, each vertex stores a second adjacency list, consisting of + * the level-i edges that are in F_i. In addition, we augment each Euler tour tree node with an a second boolean, + * indicating whether the subtree rooted at the node contains a canonical visit to a vertex with at least one level-i + * edge that is in F_i. + * + * The implementation of rerooting an Euler tour tree described in the video lecture appears to be incorrect as well. It + * breaks the references to the vertices' first and last visits. To fix this, we do not store references to the + * vertices' first and last visits. Instead, we have each vertex store a reference to an arbitrary visit to that vertex. + * We also maintain edge objects for each of the edges in the Euler tours. Each such edge stores a pointer to the two + * visits that precede the traversal of the edge in the Euler tour. These do not change when we perform a reroot. The + * remove edge operation then requires a pointer to the edge object, rather than a pointer to the vertices. Given the + * edge object, we can splice out the range of nodes between the two visits that precede the edge. + * + * Rather than explicitly giving each edge a level number, the level numbers are implicit through links from each level + * to the level below it. For purposes of analysis, the level number of the top level is equal to + * maxLogVertexCountSinceRebuild, the ceiling of log base 2 of the maximum number of vertices in the graph since the + * last rebuild operation. Once the ratio between the maximum number of vertices since the last rebuild and the current + * number of vertices becomes large enough, we rebuild the data structure. This ensures that the level number of the top + * level is O(log V). + * + * Most methods' time bounds are probabilistic. For example, "connected" takes O(log N) time with high probability. The + * reason they are probabilistic is that they involve hash lookups, using the vertexInfo and VertexInfo.edges hash maps. + * Given that each ConnVertex has a random hash code, it is easy to demonstrate that lookups take O(1) expected time. + * Furthermore, I claim that they take O(log N / log log N) time with high probability. This claim is sufficient to + * establish that all time bounds that are at least O(log N / log log N) if we exclude the hash lookup can be sustained + * if we add the qualifier "with high probability." + * + * This claim is based on information presented in + * https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-10-dictionaries/ . + * According to that video, in a hash map with chaining, if the hash function is totally random, then the longest chain + * length is O(log N / log log N) with high probability. A totally random hash function is a slightly different concept + * than having ConnVertex.hashCode() return a random value, due to the particular definition of "hash function" used in + * the video. Nevertheless, the analysis is the same. A random hashCode() implementation ultimately results in + * independently hashing each entry to a random bucket, which is equivalent to a totally random hash function. + * + * However, the claim depends on certain features of the implementation of HashMap, gleaned from reading the source + * code. In particular, it assumes that HashMap resolves collisions using chaining. (Newer versions of Java sometimes + * store the entries that hash to the same bucket in binary search trees rather than linked lists, but this can't hurt + * the asymptotic performance.) Note that the implementation of HashMap transforms the return value of hashCode(), by + * "spreading" the higher-order bits to lower-order positions. However, this transform is a permutation of the integers. + * If the input to a transform is selected uniformly at random, and the transform is a permutation, than the output also + * has a uniform random distribution. + */ +public class ConnGraph { + /** + * The difference between ceiling of log base 2 of the maximum number of vertices in the graph since the last call + * to rebuild() or clear() and ceiling of log base 2 of the current number of vertices, at or above which we call + * rebuild(). (There is special handling for 0 vertices.) + */ + private static final int REBUILD_CHANGE = 2; + + /** + * The maximum number of vertices we can store in a ConnGraph. This is limited by the fact that EulerTourNode.size + * is an int. Since the size of an Euler tour tree is one less than twice the number of vertices in the tree, the + * number of vertices may be at most (int)((((long)Integer.MAX_VALUE) + 1) / 2). + * + * Of course, we could simply change the "size" field to be a long. But more fundamentally, the number of vertices + * is limited by the fact that vertexInfo and VertexInfo.edges use HashMaps. Using a HashMap becomes problematic at + * around Integer.MAX_VALUE entries. HashMap buckets entries based on 32-bit hash codes, so in principle, it can + * only hash the entries to at most 2^32 buckets. In order to support a significantly greater limit on the number of + * vertices, we would need to use a more elaborate mapping approach. + */ + private static final int MAX_VERTEX_COUNT = 1 << 30; + + /** The augmentation function for the graph, if any. */ + private final Augmentation augmentation; + + /** + * A map from each vertex in this graph to information about the vertex in this graph. If a vertex has no adjacent + * edges and no associated augmentation, we remove it from vertexInfo, to save time and space. Lookups take O(1) + * expected time and O(log N / log log N) time with high probability, because vertexInfo is a HashMap, and + * ConnVertex.hashCode() returns a random integer. + */ + private Map vertexInfo = new HashMap(); + + /** + * Ceiling of log base 2 of the maximum number of vertices in this graph since the last rebuild. This is 0 if that + * number is 0. + */ + private int maxLogVertexCountSinceRebuild; + + /** + * The maximum number of entries in vertexInfo since the last time we copied that field to a new HashMap. We do this + * when the number of vertices drops sufficiently, in order to limit space usage. (The capacity of a HashMap is not + * automatically reduced as the number of entries decreases, so we have to limit space usage manually.) + */ + private int maxVertexInfoSize; + + /** Constructs a new ConnGraph with no augmentation. */ + public ConnGraph() { + augmentation = null; + } + + /** Constructs an augmented ConnGraph, using the specified function to combine augmentation values. */ + public ConnGraph(Augmentation augmentation) { + this.augmentation = augmentation; + } + + /** Equivalent implementation is contractual. */ + private void assertIsAugmented() { + if (augmentation == null) { + throw new RuntimeException( + "You may only call augmentation-related methods on ConnGraph if the graph is augmented, i.e. if an " + + "Augmentation was passed to the constructor"); + } + } + + /** + * Returns the VertexInfo containing information about the specified vertex in this graph. If the vertex is not in + * this graph (i.e. it does not have an entry in vertexInfo), this method adds it to the graph, and creates a + * VertexInfo object for it. + */ + private VertexInfo ensureInfo(ConnVertex vertex) { + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info; + } + + if (vertexInfo.size() == MAX_VERTEX_COUNT) { + throw new RuntimeException( + "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + + "storing more than ~2^30 vertices at a time."); + } + + EulerTourVertex eulerTourVertex = new EulerTourVertex(); + EulerTourNode node = new EulerTourNode(eulerTourVertex, augmentation); + eulerTourVertex.arbitraryVisit = node; + node.left = EulerTourNode.LEAF; + node.right = EulerTourNode.LEAF; + node.augment(); + + info = new VertexInfo(eulerTourVertex); + vertexInfo.put(vertex, info); + if (vertexInfo.size() > 1 << maxLogVertexCountSinceRebuild) { + maxLogVertexCountSinceRebuild++; + } + maxVertexInfoSize = Math.max(maxVertexInfoSize, vertexInfo.size()); + return info; + } + + /** + * Takes the specified vertex out of this graph. We should call this method as soon as a vertex does not have any + * adjacent edges and does not have any augmentation information. This method assumes that the vertex is currently + * in the graph. + */ + private void remove(ConnVertex vertex) { + vertexInfo.remove(vertex); + if (4 * vertexInfo.size() <= maxVertexInfoSize && maxVertexInfoSize > 12) { + // The capacity of a HashMap is not automatically reduced as the number of entries decreases. To avoid + // violating our O(V log V + E) space guarantee, we copy vertexInfo to a new HashMap, which will have a + // suitable capacity. + vertexInfo = new HashMap(vertexInfo); + maxVertexInfoSize = vertexInfo.size(); + } + if (vertexInfo.size() << REBUILD_CHANGE <= 1 << maxLogVertexCountSinceRebuild) { + rebuild(); + } + } + + /** + * Collapses an adjacency list (either graphListHead or forestListHead) for an EulerTourVertex into the adjacency + * list for an EulerTourVertex that represents the same underlying ConnVertex, but at a higher level. This has the + * effect of prepending the list for the lower level to the beginning of the list for the higher level, and + * replacing all links to the lower-level vertex in the ConnEdges with links to the higher-level vertex. + * @param head The first node in the list for the higher-level vertex. + * @param lowerHead The first node in the list for the lower-level vertex. + * @param vertex The higher-level vertex. + * @param lowerVertex The lower-level vertex. + * @return The head of the combined linked list. + */ + private ConnEdge collapseEdgeList( + ConnEdge head, ConnEdge lowerHead, EulerTourVertex vertex, EulerTourVertex lowerVertex) { + if (lowerHead == null) { + return head; + } + + ConnEdge prevLowerEdge = null; + ConnEdge lowerEdge = lowerHead; + while (lowerEdge != null) { + prevLowerEdge = lowerEdge; + if (lowerEdge.vertex1 == lowerVertex) { + lowerEdge.vertex1 = vertex; + lowerEdge = lowerEdge.next1; + } else { + lowerEdge.vertex2 = vertex; + lowerEdge = lowerEdge.next2; + } + } + + if (prevLowerEdge.vertex1 == vertex) { + prevLowerEdge.next1 = head; + } else { + prevLowerEdge.next2 = head; + } + if (head != null) { + if (head.vertex1 == vertex) { + head.prev1 = prevLowerEdge; + } else { + head.prev2 = prevLowerEdge; + } + } + return lowerHead; + } + + /** + * Equivalent implementation is contractual. + * + * This method is useful for when a node's lists (graphListHead or forestListHead) or a vertex's arbitrary visit + * change, as these affect the hasGraphEdge and hasForestEdge augmentations. + */ + private void augmentAncestorFlags(EulerTourNode node) { + for (EulerTourNode parent = node; parent != null; parent = parent.parent) { + if (!parent.augmentFlags()) { + break; + } + } + } + + /** + * Rebuilds the data structure so that the number of levels is at most the ceiling of log base 2 of the number of + * vertices in the graph (or zero in the case of zero vertices). The current implementation of rebuild() takes + * O(V + E) time, assuming a constant difference between maxLogVertexCountSinceRebuild and the result of the + * logarithm. + */ + private void rebuild() { + // Rebuild the graph by collapsing the top deleteCount + 1 levels into the top level + + if (vertexInfo.isEmpty()) { + maxLogVertexCountSinceRebuild = 0; + return; + } + int deleteCount = 0; + while (2 * vertexInfo.size() <= 1 << maxLogVertexCountSinceRebuild) { + maxLogVertexCountSinceRebuild--; + deleteCount++; + } + if (deleteCount == 0) { + return; + } + + for (VertexInfo info : vertexInfo.values()) { + EulerTourVertex vertex = info.vertex; + EulerTourVertex lowerVertex = vertex; + for (int i = 0; i < deleteCount; i++) { + lowerVertex = lowerVertex.lowerVertex; + if (lowerVertex == null) { + break; + } + + vertex.graphListHead = + collapseEdgeList(vertex.graphListHead, lowerVertex.graphListHead, vertex, lowerVertex); + if (lowerVertex.forestListHead != null) { + // Change the eulerTourEdge links + ConnEdge lowerEdge = lowerVertex.forestListHead; + while (lowerEdge != null) { + if (lowerEdge.vertex1 == lowerVertex) { + // We'll address this edge when we visit lowerEdge.vertex2 + lowerEdge = lowerEdge.next1; + } else { + EulerTourEdge edge = lowerEdge.eulerTourEdge.higherEdge; + for (int j = 0; j < i; j++) { + edge = edge.higherEdge; + } + lowerEdge.eulerTourEdge = edge; + lowerEdge = lowerEdge.next2; + } + } + + vertex.forestListHead = + collapseEdgeList(vertex.forestListHead, lowerVertex.forestListHead, vertex, lowerVertex); + } + } + + if (lowerVertex != null) { + lowerVertex = lowerVertex.lowerVertex; + } + vertex.lowerVertex = lowerVertex; + if (lowerVertex != null) { + lowerVertex.higherVertex = vertex; + } + augmentAncestorFlags(vertex.arbitraryVisit); + } + } + + /** + * Adds the specified edge to the graph adjacency list of edge.vertex1, as in EulerTourVertex.graphListHead. + * Assumes it is not currently in any lists, except possibly the graph adjacency list of edge.vertex2. + */ + private void addToGraphLinkedList1(ConnEdge edge) { + edge.prev1 = null; + edge.next1 = edge.vertex1.graphListHead; + if (edge.next1 != null) { + if (edge.next1.vertex1 == edge.vertex1) { + edge.next1.prev1 = edge; + } else { + edge.next1.prev2 = edge; + } + } + edge.vertex1.graphListHead = edge; + } + + /** + * Adds the specified edge to the graph adjacency list of edge.vertex2, as in EulerTourVertex.graphListHead. + * Assumes it is not currently in any lists, except possibly the graph adjacency list of edge.vertex1. + */ + private void addToGraphLinkedList2(ConnEdge edge) { + edge.prev2 = null; + edge.next2 = edge.vertex2.graphListHead; + if (edge.next2 != null) { + if (edge.next2.vertex1 == edge.vertex2) { + edge.next2.prev1 = edge; + } else { + edge.next2.prev2 = edge; + } + } + edge.vertex2.graphListHead = edge; + } + + /** + * Adds the specified edge to the graph adjacency lists of edge.vertex1 and edge.vertex2, as in + * EulerTourVertex.graphListHead. Assumes it is not currently in any lists. + */ + private void addToGraphLinkedLists(ConnEdge edge) { + addToGraphLinkedList1(edge); + addToGraphLinkedList2(edge); + } + + /** + * Adds the specified edge to the forest adjacency lists of edge.vertex1 and edge.vertex2, as in + * EulerTourVertex.forestListHead. Assumes it is not currently in any lists. + */ + private void addToForestLinkedLists(ConnEdge edge) { + edge.prev1 = null; + edge.next1 = edge.vertex1.forestListHead; + if (edge.next1 != null) { + if (edge.next1.vertex1 == edge.vertex1) { + edge.next1.prev1 = edge; + } else { + edge.next1.prev2 = edge; + } + } + edge.vertex1.forestListHead = edge; + + edge.prev2 = null; + edge.next2 = edge.vertex2.forestListHead; + if (edge.next2 != null) { + if (edge.next2.vertex1 == edge.vertex2) { + edge.next2.prev1 = edge; + } else { + edge.next2.prev2 = edge; + } + } + edge.vertex2.forestListHead = edge; + } + + /** + * Removes the specified edge from an adjacency list of edge.vertex1, as in graphListHead and forestListHead. + * Assumes it is initially in exactly one of the lists for edge.vertex1. + */ + private void removeFromLinkedList1(ConnEdge edge) { + if (edge.prev1 != null) { + if (edge.prev1.vertex1 == edge.vertex1) { + edge.prev1.next1 = edge.next1; + } else { + edge.prev1.next2 = edge.next1; + } + } else if (edge == edge.vertex1.graphListHead) { + edge.vertex1.graphListHead = edge.next1; + } else { + edge.vertex1.forestListHead = edge.next1; + } + if (edge.next1 != null) { + if (edge.next1.vertex1 == edge.vertex1) { + edge.next1.prev1 = edge.prev1; + } else { + edge.next1.prev2 = edge.prev1; + } + } + } + + /** + * Removes the specified edge from an adjacency list of edge.vertex2, as in graphListHead and forestListHead. + * Assumes it is initially in exactly one of the lists for edge.vertex2. + */ + private void removeFromLinkedList2(ConnEdge edge) { + if (edge.prev2 != null) { + if (edge.prev2.vertex1 == edge.vertex2) { + edge.prev2.next1 = edge.next2; + } else { + edge.prev2.next2 = edge.next2; + } + } else if (edge == edge.vertex2.graphListHead) { + edge.vertex2.graphListHead = edge.next2; + } else { + edge.vertex2.forestListHead = edge.next2; + } + if (edge.next2 != null) { + if (edge.next2.vertex1 == edge.vertex2) { + edge.next2.prev1 = edge.prev2; + } else { + edge.next2.prev2 = edge.prev2; + } + } + } + + /** + * Removes the specified edge from the adjacency lists of edge.vertex1 and edge.vertex2, as in graphListHead and + * forestListHead. Assumes it is initially in exactly one of the lists for edge.vertex1 and exactly one of the lists + * for edge.vertex2. + */ + private void removeFromLinkedLists(ConnEdge edge) { + removeFromLinkedList1(edge); + removeFromLinkedList2(edge); + } + + /** + * Add an edge between the specified vertices to the Euler tour forest F_i. Assumes that the edge's endpoints are + * initially in separate trees. Returns the created edge. + */ + private EulerTourEdge addForestEdge(EulerTourVertex vertex1, EulerTourVertex vertex2) { + // We need to be careful about where we split and where we add and remove nodes, so as to avoid breaking any + // EulerTourEdge.visit* fields + EulerTourNode root = vertex2.arbitraryVisit.root(); + EulerTourNode max = root.max(); + if (max.vertex != vertex2) { + // Reroot + EulerTourNode min = root.min(); + if (max.vertex.arbitraryVisit == max) { + max.vertex.arbitraryVisit = min; + augmentAncestorFlags(min); + augmentAncestorFlags(max); + } + root = max.remove(); + EulerTourNode[] splitRoots = root.split(vertex2.arbitraryVisit); + root = splitRoots[1].concatenate(splitRoots[0]); + EulerTourNode newNode = new EulerTourNode(vertex2, root.augmentationFunc); + newNode.left = EulerTourNode.LEAF; + newNode.right = EulerTourNode.LEAF; + newNode.isRed = true; + EulerTourNode parent = root.max(); + parent.right = newNode; + newNode.parent = parent; + root = newNode.fixInsertion(); + max = newNode; + } + + EulerTourNode[] splitRoots = vertex1.arbitraryVisit.root().split(vertex1.arbitraryVisit); + EulerTourNode before = splitRoots[0]; + EulerTourNode after = splitRoots[1]; + EulerTourNode newNode = new EulerTourNode(vertex1, root.augmentationFunc); + before.concatenate(root, newNode).concatenate(after); + return new EulerTourEdge(newNode, max); + } + + /** + * Adds the specified edge to the edge map for srcInfo (srcInfo.edges). Assumes that the edge is not currently in + * the map. + * @param edge The edge. + * @param srcInfo The source vertex's info. + * @param destVertex The destination vertex, i.e. the edge's key in srcInfo.edges. + */ + private void addToEdgeMap(ConnEdge edge, VertexInfo srcInfo, ConnVertex destVertex) { + srcInfo.edges.put(destVertex, edge); + if (srcInfo.edges.size() > srcInfo.maxEdgeCountSinceRebuild) { + srcInfo.maxEdgeCountSinceRebuild = srcInfo.edges.size(); + } + } + + /** + * Adds an edge between the specified vertices, if such an edge is not already present. Taken together with + * removeEdge, this method takes O(log^2 N) amortized time with high probability. + * @return Whether there was no edge between the vertices. + */ + public boolean addEdge(ConnVertex connVertex1, ConnVertex connVertex2) { + if (connVertex1 == connVertex2) { + throw new IllegalArgumentException("Self-loops are not allowed"); + } + if (vertexInfo.size() >= MAX_VERTEX_COUNT - 1) { + throw new RuntimeException( + "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + + "storing more than ~2^30 vertices at a time."); + } + VertexInfo info1 = ensureInfo(connVertex1); + if (info1.edges.containsKey(connVertex2)) { + return false; + } + VertexInfo info2 = ensureInfo(connVertex2); + + EulerTourVertex vertex1 = info1.vertex; + EulerTourVertex vertex2 = info2.vertex; + ConnEdge edge = new ConnEdge(vertex1, vertex2); + + if (vertex1.arbitraryVisit.root() == vertex2.arbitraryVisit.root()) { + addToGraphLinkedLists(edge); + } else { + addToForestLinkedLists(edge); + edge.eulerTourEdge = addForestEdge(vertex1, vertex2); + } + augmentAncestorFlags(vertex1.arbitraryVisit); + augmentAncestorFlags(vertex2.arbitraryVisit); + + addToEdgeMap(edge, info1, connVertex2); + addToEdgeMap(edge, info2, connVertex1); + return true; + } + + /** + * Returns vertex.lowerVertex. If this is null, ensureLowerVertex sets vertex.lowerVertex to a new vertex and + * returns it. + */ + private EulerTourVertex ensureLowerVertex(EulerTourVertex vertex) { + EulerTourVertex lowerVertex = vertex.lowerVertex; + if (lowerVertex == null) { + lowerVertex = new EulerTourVertex(); + EulerTourNode lowerNode = new EulerTourNode(lowerVertex, null); + lowerVertex.arbitraryVisit = lowerNode; + vertex.lowerVertex = lowerVertex; + lowerVertex.higherVertex = vertex; + + lowerNode.left = EulerTourNode.LEAF; + lowerNode.right = EulerTourNode.LEAF; + lowerNode.augment(); + } + return lowerVertex; + } + + /** + * Pushes all level-i forest edges in the tree rooted at the specified node down to level i - 1, and adds them to + * F_{i - 1}, where i is the level of the tree. + */ + private void pushForestEdges(EulerTourNode root) { + // Iterate over all of the nodes that have hasForestEdge == true + if (!root.hasForestEdge || root.size == 1) { + return; + } + EulerTourNode node; + for (node = root; node.left.hasForestEdge; node = node.left); + while (node != null) { + EulerTourVertex vertex = node.vertex; + ConnEdge edge = vertex.forestListHead; + if (edge != null) { + EulerTourVertex lowerVertex = ensureLowerVertex(vertex); + ConnEdge prevEdge = null; + while (edge != null) { + if (edge.vertex2 == vertex || edge.vertex2 == lowerVertex) { + // We address this edge when we visit edge.vertex1 + prevEdge = edge; + edge = edge.next2; + } else { + edge.vertex1 = lowerVertex; + edge.vertex2 = ensureLowerVertex(edge.vertex2); + EulerTourEdge lowerEdge = addForestEdge(edge.vertex1, edge.vertex2); + lowerEdge.higherEdge = edge.eulerTourEdge; + edge.eulerTourEdge = lowerEdge; + prevEdge = edge; + edge = edge.next1; + } + } + + // Prepend vertex.forestListHead to the beginning of lowerVertex.forestListHead + if (prevEdge.vertex1 == lowerVertex) { + prevEdge.next1 = lowerVertex.forestListHead; + } else { + prevEdge.next2 = lowerVertex.forestListHead; + } + if (lowerVertex.forestListHead != null) { + if (lowerVertex.forestListHead.vertex1 == lowerVertex) { + lowerVertex.forestListHead.prev1 = prevEdge; + } else { + lowerVertex.forestListHead.prev2 = prevEdge; + } + } + lowerVertex.forestListHead = vertex.forestListHead; + vertex.forestListHead = null; + augmentAncestorFlags(lowerVertex.arbitraryVisit); + } + + // Iterate to the next node with hasForestEdge == true, clearing hasForestEdge as we go + if (node.right.hasForestEdge) { + for (node = node.right; node.left.hasForestEdge; node = node.left); + } else { + node.hasForestEdge = false; + while (node.parent != null && node.parent.right == node) { + node = node.parent; + node.hasForestEdge = false; + } + node = node.parent; + } + } + } + + /** + * Searches for a level-i edge connecting a vertex in the tree rooted at the specified node to a vertex in another + * tree, where i is the level of the tree. This is a "replacement" edge because it replaces the edge that was + * previously connecting the two trees. We push any level-i edges we encounter that do not connect to another tree + * down to level i - 1, adding them to G_{i - 1}. This method assumes that root.hasForestEdge is false. + * @param root The root of the tree. + * @return The replacement edge, or null if there is no replacement edge. + */ + private ConnEdge findReplacementEdge(EulerTourNode root) { + // Iterate over all of the nodes that have hasGraphEdge == true + if (!root.hasGraphEdge) { + return null; + } + EulerTourNode node; + for (node = root; node.left.hasGraphEdge; node = node.left); + while (node != null) { + EulerTourVertex vertex = node.vertex; + ConnEdge edge = vertex.graphListHead; + if (edge != null) { + ConnEdge replacementEdge = null; + ConnEdge prevEdge = null; + while (edge != null) { + EulerTourVertex adjVertex; + ConnEdge nextEdge; + if (edge.vertex1 == vertex) { + adjVertex = edge.vertex2; + nextEdge = edge.next1; + } else { + adjVertex = edge.vertex1; + nextEdge = edge.next2; + } + + if (adjVertex.arbitraryVisit.root() != root) { + replacementEdge = edge; + break; + } + + // Remove the edge from the adjacency list of adjVertex. We will remove it from the adjacency list + // of "vertex" later. + if (edge.vertex1 == adjVertex) { + removeFromLinkedList1(edge); + } else { + removeFromLinkedList2(edge); + } + augmentAncestorFlags(adjVertex.arbitraryVisit); + + // Push the edge down to level i - 1 + edge.vertex1 = ensureLowerVertex(edge.vertex1); + edge.vertex2 = ensureLowerVertex(edge.vertex2); + + // Add the edge to the adjacency list of adjVertex.lowerVertex. We will add it to the adjacency list + // of lowerVertex later. + if (edge.vertex1 != vertex.lowerVertex) { + addToGraphLinkedList1(edge); + } else { + addToGraphLinkedList2(edge); + } + augmentAncestorFlags(adjVertex.lowerVertex.arbitraryVisit); + + prevEdge = edge; + edge = nextEdge; + } + + // Prepend the linked list up to prevEdge to the beginning of vertex.lowerVertex.graphListHead + if (prevEdge != null) { + EulerTourVertex lowerVertex = vertex.lowerVertex; + if (prevEdge.vertex1 == lowerVertex) { + prevEdge.next1 = lowerVertex.graphListHead; + } else { + prevEdge.next2 = lowerVertex.graphListHead; + } + if (lowerVertex.graphListHead != null) { + if (lowerVertex.graphListHead.vertex1 == lowerVertex) { + lowerVertex.graphListHead.prev1 = prevEdge; + } else { + lowerVertex.graphListHead.prev2 = prevEdge; + } + } + lowerVertex.graphListHead = vertex.graphListHead; + augmentAncestorFlags(lowerVertex.arbitraryVisit); + } + vertex.graphListHead = edge; + if (edge == null) { + augmentAncestorFlags(vertex.arbitraryVisit); + } else if (edge.vertex1 == vertex) { + edge.prev1 = null; + } else { + edge.prev2 = null; + } + + if (replacementEdge != null) { + return replacementEdge; + } + } + + // Iterate to the next node with hasGraphEdge == true. Note that nodes' hasGraphEdge fields can change as we + // push down edges. + if (node.right.hasGraphEdge) { + for (node = node.right; node.left.hasGraphEdge; node = node.left); + } else { + while (node.parent != null && (node.parent.right == node || !node.parent.hasGraphEdge)) { + node = node.parent; + } + node = node.parent; + } + } + return null; + } + + /** + * Removes the edge from srcInfo to destVertex from the edge map for srcInfo (srcInfo.edges), if it is present. + * Returns the edge that we removed, if any. + */ + private ConnEdge removeFromEdgeMap(VertexInfo srcInfo, ConnVertex destVertex) { + ConnEdge edge = srcInfo.edges.remove(destVertex); + if (edge != null && 4 * srcInfo.edges.size() <= srcInfo.maxEdgeCountSinceRebuild && + srcInfo.maxEdgeCountSinceRebuild > 6) { + // The capacity of a HashMap is not automatically reduced as the number of entries decreases. To avoid + // violating our O(V log V + E) space guarantee, we copy srcInfo.edges to a new HashMap, which will have a + // suitable capacity. + srcInfo.edges = new HashMap(srcInfo.edges); + srcInfo.maxEdgeCountSinceRebuild = srcInfo.edges.size(); + } + return edge; + } + + /** + * Removes the edge between the specified vertices, if there is such an edge. Taken together with addEdge, this + * method takes O(log^2 N) amortized time with high probability. + * @return Whether there was an edge between the vertices. + */ + public boolean removeEdge(ConnVertex vertex1, ConnVertex vertex2) { + if (vertex1 == vertex2) { + throw new IllegalArgumentException("Self-loops are not allowed"); + } + + VertexInfo info1 = vertexInfo.get(vertex1); + if (info1 == null) { + return false; + } + ConnEdge edge = removeFromEdgeMap(info1, vertex2); + if (edge == null) { + return false; + } + VertexInfo info2 = vertexInfo.get(vertex2); + removeFromEdgeMap(info2, vertex1); + + removeFromLinkedLists(edge); + augmentAncestorFlags(edge.vertex1.arbitraryVisit); + augmentAncestorFlags(edge.vertex2.arbitraryVisit); + + if (edge.eulerTourEdge != null) { + // Remove the edge from all of the Euler tour trees that contain it + for (EulerTourEdge levelEdge = edge.eulerTourEdge; levelEdge != null; levelEdge = levelEdge.higherEdge) { + EulerTourNode firstNode; + EulerTourNode secondNode; + if (levelEdge.visit1.compareTo(levelEdge.visit2) < 0) { + firstNode = levelEdge.visit1; + secondNode = levelEdge.visit2; + } else { + firstNode = levelEdge.visit2; + secondNode = levelEdge.visit1; + } + + if (firstNode.vertex.arbitraryVisit == firstNode) { + EulerTourNode successor = secondNode.successor(); + firstNode.vertex.arbitraryVisit = successor; + augmentAncestorFlags(firstNode); + augmentAncestorFlags(successor); + } + + EulerTourNode root = firstNode.root(); + EulerTourNode[] firstSplitRoots = root.split(firstNode); + EulerTourNode before = firstSplitRoots[0]; + EulerTourNode[] secondSplitRoots = firstSplitRoots[1].split(secondNode.successor()); + before.concatenate(secondSplitRoots[1]); + firstNode.removeWithoutGettingRoot(); + } + edge.eulerTourEdge = null; + + // Search for a replacement edge + ConnEdge replacementEdge = null; + EulerTourVertex levelVertex1 = edge.vertex1; + EulerTourVertex levelVertex2 = edge.vertex2; + while (levelVertex1 != null) { + EulerTourNode root1 = levelVertex1.arbitraryVisit.root(); + EulerTourNode root2 = levelVertex2.arbitraryVisit.root(); + EulerTourNode root; + if (root1.size < root2.size) { + root = root1; + } else { + root = root2; + } + + pushForestEdges(root); + replacementEdge = findReplacementEdge(root); + if (replacementEdge != null) { + break; + } + + // To save space, get rid of trees with one node + if (root1.size == 1 && levelVertex1.higherVertex != null) { + levelVertex1.higherVertex.lowerVertex = null; + } + if (root2.size == 1 && levelVertex2.higherVertex != null) { + levelVertex2.higherVertex.lowerVertex = null; + } + + levelVertex1 = levelVertex1.higherVertex; + levelVertex2 = levelVertex2.higherVertex; + } + + if (replacementEdge != null) { + // Add the replacement edge to all of the forests at or above the current level + removeFromLinkedLists(replacementEdge); + addToForestLinkedLists(replacementEdge); + EulerTourVertex replacementVertex1 = replacementEdge.vertex1; + EulerTourVertex replacementVertex2 = replacementEdge.vertex2; + augmentAncestorFlags(replacementVertex1.arbitraryVisit); + augmentAncestorFlags(replacementVertex2.arbitraryVisit); + EulerTourEdge lowerEdge = null; + while (replacementVertex1 != null) { + EulerTourEdge levelEdge = addForestEdge(replacementVertex1, replacementVertex2); + if (lowerEdge == null) { + replacementEdge.eulerTourEdge = levelEdge; + } else { + lowerEdge.higherEdge = levelEdge; + } + + lowerEdge = levelEdge; + replacementVertex1 = replacementVertex1.higherVertex; + replacementVertex2 = replacementVertex2.higherVertex; + } + } + } + + if (info1.edges.isEmpty() && !info1.vertex.hasAugmentation) { + remove(vertex1); + } + if (info2.edges.isEmpty() && !info2.vertex.hasAugmentation) { + remove(vertex2); + } + return true; + } + + /** + * Returns whether the specified vertices are connected - whether there is a path between them. Returns true if + * vertex1 == vertex2. This method takes O(lg N) time with high probability. + */ + public boolean connected(ConnVertex vertex1, ConnVertex vertex2) { + if (vertex1 == vertex2) { + return true; + } + VertexInfo info1 = vertexInfo.get(vertex1); + if (info1 == null) { + return false; + } + VertexInfo info2 = vertexInfo.get(vertex2); + return info2 != null && info1.vertex.arbitraryVisit.root() == info2.vertex.arbitraryVisit.root(); + } + + /** Returns the vertices that are directly adjacent to the specified vertex. */ + public Collection adjacentVertices(ConnVertex vertex) { + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return new ArrayList(info.edges.keySet()); + } else { + return Collections.emptyList(); + } + } + + /** + * Sets the augmentation associated with the specified vertex. This method takes O(log N) time with high + * probability. + * + * Note that passing a null value for the second argument is not the same as removing the augmentation. For that, + * you need to call removeVertexAugmentation. + * + * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any + * associated augmentation. + */ + public Object setVertexAugmentation(ConnVertex connVertex, Object vertexAugmentation) { + assertIsAugmented(); + EulerTourVertex vertex = ensureInfo(connVertex).vertex; + Object oldAugmentation = vertex.augmentation; + if (!vertex.hasAugmentation || + (vertexAugmentation != null ? !vertexAugmentation.equals(oldAugmentation) : oldAugmentation != null)) { + vertex.augmentation = vertexAugmentation; + vertex.hasAugmentation = true; + for (EulerTourNode node = vertex.arbitraryVisit; node != null; node = node.parent) { + if (!node.augment()) { + break; + } + } + } + return oldAugmentation; + } + + /** + * Removes any augmentation associated with the specified vertex. This method takes O(log N) time with high + * probability. + * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any + * associated augmentation. + */ + public Object removeVertexAugmentation(ConnVertex connVertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(connVertex); + if (info == null) { + return null; + } + + EulerTourVertex vertex = info.vertex; + Object oldAugmentation = vertex.augmentation; + if (info.edges.isEmpty()) { + remove(connVertex); + } else if (vertex.hasAugmentation) { + vertex.augmentation = null; + vertex.hasAugmentation = false; + for (EulerTourNode node = vertex.arbitraryVisit; node != null; node = node.parent) { + if (!node.augment()) { + break; + } + } + } + return oldAugmentation; + } + + /** + * Returns the augmentation associated with the specified vertex. Returns null if it does not have any associated + * augmentation. At present, this method takes constant expected time. Contrast with getComponentAugmentation. + */ + public Object getVertexAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.augmentation; + } else { + return null; + } + } + + /** + * Returns the result of combining the augmentations associated with all of the vertices in the connected component + * containing the specified vertex. Returns null if none of those vertices has any associated augmentation. This + * method takes O(log N) time with high probability. + */ + public Object getComponentAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.arbitraryVisit.root().augmentation; + } else { + return null; + } + } + + /** + * Returns whether the specified vertex has any associated augmentation. At present, this method takes constant + * expected time. Contrast with componentHasAugmentation. + */ + public boolean vertexHasAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.hasAugmentation; + } else { + return false; + } + } + + /** + * Returns whether any of the vertices in the connected component containing the specified vertex has any associated + * augmentation. This method takes O(log N) time with high probability. + */ + public boolean componentHasAugmentation(ConnVertex vertex) { + assertIsAugmented(); + VertexInfo info = vertexInfo.get(vertex); + if (info != null) { + return info.vertex.arbitraryVisit.root().hasAugmentation; + } else { + return false; + } + } + + /** + * Clears this graph, by removing all edges and vertices, and removing all augmentation information from the + * vertices. + */ + public void clear() { + // Note that we construct a new HashMap rather than calling vertexInfo.clear() in order to ensure a reduction in + // space + vertexInfo = new HashMap(); + maxLogVertexCountSinceRebuild = 0; + maxVertexInfoSize = 0; + } + + /** + * Attempts to optimize the internal representation of the graph so that future updates will take less time. This + * method does not affect how long queries such as "connected" will take. You may find it beneficial to call + * optimize() when there is some downtime. Note that this method generally increases the amount of space the + * ConnGraph uses, but not beyond the bound of O(V log V + E). + */ + public void optimize() { + // The current implementation of optimize() takes O(V log^2 V + E log V log log V) time + + rebuild(); + + // Greedily push each forest edge as far down as possible - to the lowest level where the constraint on the + // size of connected components isn't violated + for (VertexInfo info : vertexInfo.values()) { + int level = maxLogVertexCountSinceRebuild; + EulerTourVertex vertex; + for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex) { + level--; + } + + while (vertex != null) { + EulerTourNode node = vertex.arbitraryVisit; + ConnEdge edge = vertex.forestListHead; + while (edge != null) { + if (vertex == edge.vertex2) { + // We'll address this edge when we visit edge.vertex1 + edge = edge.next2; + continue; + } + ConnEdge nextEdge = edge.next1; + + EulerTourVertex lowerVertex1 = vertex; + EulerTourVertex lowerVertex2 = edge.vertex2; + for (int lowerLevel = level - 1; lowerLevel > 0; lowerLevel--) { + int size = 0; + if (lowerVertex1.lowerVertex != null) { + size = lowerVertex1.lowerVertex.arbitraryVisit.root().size; + } else { + size = 1; + } + if (lowerVertex2.lowerVertex != null) { + size += lowerVertex2.lowerVertex.arbitraryVisit.root().size; + } else { + size++; + } + + // X EulerTourVertices = (2 * X - 1) EulerTourNodes + if (size > 2 * (1 << lowerLevel) - 1) { + break; + } + + lowerVertex1 = ensureLowerVertex(lowerVertex1); + lowerVertex2 = ensureLowerVertex(lowerVertex2); + EulerTourEdge lowerEdge = addForestEdge(lowerVertex1, lowerVertex2); + lowerEdge.higherEdge = edge.eulerTourEdge; + edge.eulerTourEdge = lowerEdge; + } + + if (lowerVertex1 != vertex) { + // We pushed the edge down at least one level + removeFromLinkedLists(edge); + augmentAncestorFlags(node); + augmentAncestorFlags(edge.vertex2.arbitraryVisit); + + edge.vertex1 = lowerVertex1; + edge.vertex2 = lowerVertex2; + addToForestLinkedLists(edge); + augmentAncestorFlags(lowerVertex1.arbitraryVisit); + augmentAncestorFlags(lowerVertex2.arbitraryVisit); + } + + edge = nextEdge; + } + + vertex = vertex.higherVertex; + level++; + } + } + + // Push each non-forest edge down to the lowest level where the endpoints are in the same connected component + for (VertexInfo info : vertexInfo.values()) { + EulerTourVertex vertex; + for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex); + while (vertex != null) { + EulerTourNode node = vertex.arbitraryVisit; + ConnEdge edge = vertex.graphListHead; + while (edge != null) { + if (vertex == edge.vertex2) { + // We'll address this edge when we visit edge.vertex1 + edge = edge.next2; + continue; + } + ConnEdge nextEdge = edge.next1; + + // Use binary search to identify the lowest level where the two vertices are in the same connected + // component + int maxLevelsDown = 0; + EulerTourVertex lowerVertex1 = vertex.lowerVertex; + EulerTourVertex lowerVertex2 = edge.vertex2.lowerVertex; + while (lowerVertex1 != null && lowerVertex2 != null) { + maxLevelsDown++; + lowerVertex1 = lowerVertex1.lowerVertex; + lowerVertex2 = lowerVertex2.lowerVertex; + } + EulerTourVertex levelVertex1 = vertex; + EulerTourVertex levelVertex2 = edge.vertex2; + while (maxLevelsDown > 0) { + int levelsDown = (maxLevelsDown + 1) / 2; + lowerVertex1 = levelVertex1; + lowerVertex2 = levelVertex2; + for (int i = 0; i < levelsDown; i++) { + lowerVertex1 = lowerVertex1.lowerVertex; + lowerVertex2 = lowerVertex2.lowerVertex; + } + + if (lowerVertex1.arbitraryVisit.root() != lowerVertex2.arbitraryVisit.root()) { + maxLevelsDown = levelsDown - 1; + } else { + levelVertex1 = lowerVertex1; + levelVertex2 = lowerVertex2; + maxLevelsDown -= levelsDown; + } + } + + if (levelVertex1 != vertex) { + removeFromLinkedLists(edge); + augmentAncestorFlags(node); + augmentAncestorFlags(edge.vertex2.arbitraryVisit); + + edge.vertex1 = levelVertex1; + edge.vertex2 = levelVertex2; + addToGraphLinkedLists(edge); + augmentAncestorFlags(levelVertex1.arbitraryVisit); + augmentAncestorFlags(levelVertex2.arbitraryVisit); + } + + edge = nextEdge; + } + vertex = vertex.higherVertex; + } + } + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java new file mode 100644 index 000000000..f32860958 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java @@ -0,0 +1,41 @@ +package com.github.btrekkie.connectivity; + +import java.util.Random; + +/** A vertex in a ConnGraph. See the comments for ConnGraph. */ +public class ConnVertex { + /** + * The thread-local random number generator we use by default to set the "hash" field. We could use + * ThreadLocalRandom instead, but ThreadLocalRandom isn't available in Java 6.0 and below. + */ + private static final ThreadLocal random = new ThreadLocal() { + @Override + protected Random initialValue() { + return new Random(); + } + }; + + /** + * A randomly generated integer to use as the return value of hashCode(). ConnGraph relies on random hash codes for + * its performance guarantees. + */ + private final int hash; + + public ConnVertex() { + hash = random.get().nextInt(); + } + + /** + * Constructs a new ConnVertex. + * @param random The random number generator to use to produce a random hash code. ConnGraph relies on random hash + * codes for its performance guarantees. + */ + public ConnVertex(Random random) { + hash = random.nextInt(); + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java new file mode 100644 index 000000000..367399361 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourEdge.java @@ -0,0 +1,30 @@ +package com.github.btrekkie.connectivity; + +/** + * The representation of a forest edge in some Euler tour forest F_i at some particular level i. Each forest edge has + * one EulerTourEdge object for each level it appears in. See the comments for the implementation of ConnGraph. + */ +class EulerTourEdge { + /** + * One of the two visits preceding the edge in the Euler tour, in addition to visit2. (The node is at the same level + * as the EulerTourEdge.) + */ + public final EulerTourNode visit1; + + /** + * One of the two visits preceding the edge in the Euler tour, in addition to visit1. (The node is at the same level + * as the EulerTourEdge.) + */ + public final EulerTourNode visit2; + + /** + * The representation of this edge in the next-higher level. higherEdge is null if this edge is in the highest + * level. + */ + public EulerTourEdge higherEdge; + + public EulerTourEdge(EulerTourNode visit1, EulerTourNode visit2) { + this.visit1 = visit1; + this.visit2 = visit2; + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java new file mode 100644 index 000000000..9d1f068f9 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java @@ -0,0 +1,112 @@ +package com.github.btrekkie.connectivity; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** + * A node in an Euler tour tree for ConnGraph (at some particular level i). See the comments for the implementation of + * ConnGraph. + */ +class EulerTourNode extends RedBlackNode { + /** The dummy leaf node. */ + public static final EulerTourNode LEAF = new EulerTourNode(null, null); + + /** The vertex this node visits. */ + public final EulerTourVertex vertex; + + /** The number of nodes in the subtree rooted at this node. */ + public int size; + + /** + * Whether the subtree rooted at this node contains a node "node" for which + * node.vertex.arbitraryNode == node && node.vertex.graphListHead != null. + */ + public boolean hasGraphEdge; + + /** + * Whether the subtree rooted at this node contains a node "node" for which + * node.vertex.arbitraryNode == node && node.vertex.forestListHead != null. + */ + public boolean hasForestEdge; + + /** + * The combining function for combining user-provided augmentations. augmentationFunc is null if this node is not in + * the highest level. + */ + public final Augmentation augmentationFunc; + + /** + * The combined augmentation for the subtree rooted at this node. This is the result of combining the augmentation + * values node.vertex.augmentation for all nodes "node" in the subtree rooted at this node for which + * node.vertex.arbitraryVisit == node, using augmentationFunc. This is null if hasAugmentation is false. + */ + public Object augmentation; + + /** + * Whether the subtree rooted at this node contains at least one augmentation value. This indicates whether there is + * some node "node" in the subtree rooted at this node for which node.vertex.hasAugmentation is true and + * node.vertex.arbitraryVisit == node. + */ + public boolean hasAugmentation; + + public EulerTourNode(EulerTourVertex vertex, Augmentation augmentationFunc) { + this.vertex = vertex; + this.augmentationFunc = augmentationFunc; + } + + /** Like augment(), but only updates the augmentation fields hasGraphEdge and hasForestEdge. */ + public boolean augmentFlags() { + boolean newHasGraphEdge = + left.hasGraphEdge || right.hasGraphEdge || (vertex.arbitraryVisit == this && vertex.graphListHead != null); + boolean newHasForestEdge = + left.hasForestEdge || right.hasForestEdge || + (vertex.arbitraryVisit == this && vertex.forestListHead != null); + if (newHasGraphEdge == hasGraphEdge && newHasForestEdge == hasForestEdge) { + return false; + } else { + hasGraphEdge = newHasGraphEdge; + hasForestEdge = newHasForestEdge; + return true; + } + } + + @Override + public boolean augment() { + int newSize = left.size + right.size + 1; + boolean augmentedFlags = augmentFlags(); + + Object newAugmentation = null; + boolean newHasAugmentation = false; + if (augmentationFunc != null) { + if (left.hasAugmentation) { + newAugmentation = left.augmentation; + newHasAugmentation = true; + } + if (vertex.hasAugmentation && vertex.arbitraryVisit == this) { + if (newHasAugmentation) { + newAugmentation = augmentationFunc.combine(newAugmentation, vertex.augmentation); + } else { + newAugmentation = vertex.augmentation; + newHasAugmentation = true; + } + } + if (right.hasAugmentation) { + if (newHasAugmentation) { + newAugmentation = augmentationFunc.combine(newAugmentation, right.augmentation); + } else { + newAugmentation = right.augmentation; + newHasAugmentation = true; + } + } + } + + if (newSize == size && !augmentedFlags && hasAugmentation == newHasAugmentation && + (newAugmentation != null ? newAugmentation.equals(augmentation) : augmentation == null)) { + return false; + } else { + size = newSize; + augmentation = newAugmentation; + hasAugmentation = newHasAugmentation; + return true; + } + } +} diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java new file mode 100644 index 000000000..2b02940b0 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java @@ -0,0 +1,43 @@ +package com.github.btrekkie.connectivity; + +/** + * The representation of a ConnVertex at some particular level i. Each vertex has one EulerTourVertex object for each + * level it appears in. Note that different vertices may appear in different numbers of levels, as EulerTourVertex + * objects are only created for lower levels as needed. See the comments for the implementation of ConnGraph. + */ +class EulerTourVertex { + /** + * The representation of this edge in the next-lower level. lowerVertex is null if this is the lowest-level + * representation of this vertex. + */ + public EulerTourVertex lowerVertex; + + /** + * The representation of this edge in the next-higher level. This is null if this vertex is in the highest level. + */ + public EulerTourVertex higherVertex; + + /** + * An arbitrarily selected visit to the vertex in the Euler tour tree that contains it (at the same level as this). + */ + public EulerTourNode arbitraryVisit; + + /** + * The first edge in the linked list of level-i edges that are adjacent to the vertex in G_i, but are not in the + * Euler tour forest F_i, where i is the level of the vertex. Note that this list excludes any edges that also + * appear in lower levels. + */ + public ConnEdge graphListHead; + + /** + * The first edge in the linked list of level-i edges adjacent to the vertex that are in F_i, where i is the level + * of the vertex. Note that this list excludes any edges that also appear in lower levels. + */ + public ConnEdge forestListHead; + + /** The augmentation associated with this vertex, if any. This is null instead if higherVertex != null. */ + public Object augmentation; + + /** Whether there is any augmentation associated with this vertex. This is false instead if higherVertex != null. */ + public boolean hasAugmentation; +} diff --git a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java new file mode 100644 index 000000000..1593a2040 --- /dev/null +++ b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java @@ -0,0 +1,32 @@ +package com.github.btrekkie.connectivity; + +import java.util.HashMap; +import java.util.Map; + +/** + * Describes a ConnVertex, with respect to a particular ConnGraph. There is exactly one VertexInfo object per vertex in + * a given graph, regardless of how many levels the vertex is in. See the comments for the implementation of ConnGraph. + */ +class VertexInfo { + /** The representation of the vertex in the highest level. */ + public EulerTourVertex vertex; + + /** + * A map from each ConnVertex adjacent to this vertex to the ConnEdge object for the edge connecting it to this + * vertex. Lookups take O(1) expected time and O(log N / log log N) time with high probability, because "edges" is a + * HashMap, and ConnVertex.hashCode() returns a random integer. + */ + public Map edges = new HashMap(); + + /** + * The maximum number of entries in "edges" since the last time we "rebuilt" that field. When the number of edges + * drops sufficiently, we rebuild "edges" by copying its contents to a new HashMap. We do this to ensure that + * "edges" uses O(K) space, where K is the number of vertices adjacent to this. (The capacity of a HashMap is not + * automatically reduced as the number of entries decreases, so we have to limit space usage manually.) + */ + public int maxEdgeCountSinceRebuild; + + public VertexInfo(EulerTourVertex vertex) { + this.vertex = vertex; + } +} diff --git a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java new file mode 100644 index 000000000..30feeae6a --- /dev/null +++ b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java @@ -0,0 +1,992 @@ +package com.github.btrekkie.connectivity.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.junit.Test; + +import com.github.btrekkie.connectivity.ConnGraph; +import com.github.btrekkie.connectivity.ConnVertex; + +/* Note that most of the ConnGraphTest test methods use the one-argument ConnVertex constructor, in order to make their + * behavior more predictable. That way, there are consistent test results, and test failures are easier to debug. + */ +public class ConnGraphTest { + /** Tests ConnectivityGraph on a small forest and a binary tree-like subgraph. */ + @Test + public void testForestAndBinaryTree() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex1 = new ConnVertex(random); + ConnVertex vertex2 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex1, vertex2)); + ConnVertex vertex3 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex3, vertex1)); + ConnVertex vertex4 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex1, vertex4)); + ConnVertex vertex5 = new ConnVertex(random); + ConnVertex vertex6 = new ConnVertex(random); + ConnVertex vertex7 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex6, vertex7)); + assertTrue(graph.addEdge(vertex6, vertex5)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertFalse(graph.addEdge(vertex1, vertex3)); + ConnVertex vertex8 = new ConnVertex(random); + ConnVertex vertex9 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex8, vertex9)); + ConnVertex vertex10 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex8, vertex10)); + assertFalse(graph.removeEdge(vertex7, vertex1)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.connected(vertex1, vertex1)); + assertTrue(graph.connected(vertex1, vertex2)); + assertTrue(graph.connected(vertex3, vertex6)); + assertTrue(graph.connected(vertex7, vertex4)); + assertTrue(graph.connected(vertex8, vertex9)); + assertTrue(graph.connected(vertex5, vertex2)); + assertTrue(graph.connected(vertex8, vertex10)); + assertTrue(graph.connected(vertex9, vertex10)); + assertFalse(graph.connected(vertex1, vertex8)); + assertFalse(graph.connected(vertex2, vertex10)); + assertTrue(graph.removeEdge(vertex4, vertex5)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex2, vertex4)); + assertTrue(graph.connected(vertex5, vertex6)); + assertTrue(graph.connected(vertex5, vertex7)); + assertTrue(graph.connected(vertex8, vertex9)); + assertTrue(graph.connected(vertex3, vertex3)); + assertFalse(graph.connected(vertex1, vertex5)); + assertFalse(graph.connected(vertex4, vertex7)); + assertFalse(graph.connected(vertex1, vertex8)); + assertFalse(graph.connected(vertex6, vertex9)); + + Set expectedAdjVertices = new HashSet(); + expectedAdjVertices.add(vertex2); + expectedAdjVertices.add(vertex3); + expectedAdjVertices.add(vertex4); + assertEquals(expectedAdjVertices, new HashSet(graph.adjacentVertices(vertex1))); + expectedAdjVertices.clear(); + expectedAdjVertices.add(vertex5); + expectedAdjVertices.add(vertex7); + assertEquals(expectedAdjVertices, new HashSet(graph.adjacentVertices(vertex6))); + assertEquals(Collections.singleton(vertex8), new HashSet(graph.adjacentVertices(vertex9))); + assertEquals(Collections.emptySet(), new HashSet(graph.adjacentVertices(new ConnVertex(random)))); + graph.optimize(); + + List vertices = new ArrayList(1000); + for (int i = 0; i < 1000; i++) { + vertices.add(new ConnVertex(random)); + } + for (int i = 0; i < 1000; i++) { + if (i > 0 && Integer.bitCount(i) <= 3) { + graph.addEdge(vertices.get(i), vertices.get((i - 1) / 2)); + } + } + for (int i = 0; i < 1000; i++) { + if (Integer.bitCount(i) > 3) { + graph.addEdge(vertices.get((i - 1) / 2), vertices.get(i)); + } + } + for (int i = 15; i < 31; i++) { + graph.removeEdge(vertices.get(i), vertices.get((i - 1) / 2)); + } + assertTrue(graph.connected(vertices.get(0), vertices.get(0))); + assertTrue(graph.connected(vertices.get(11), vertices.get(2))); + assertTrue(graph.connected(vertices.get(7), vertices.get(14))); + assertTrue(graph.connected(vertices.get(0), vertices.get(10))); + assertFalse(graph.connected(vertices.get(0), vertices.get(15))); + assertFalse(graph.connected(vertices.get(15), vertices.get(16))); + assertFalse(graph.connected(vertices.get(14), vertices.get(15))); + assertFalse(graph.connected(vertices.get(7), vertices.get(605))); + assertFalse(graph.connected(vertices.get(5), vertices.get(87))); + assertTrue(graph.connected(vertices.get(22), vertices.get(22))); + assertTrue(graph.connected(vertices.get(16), vertices.get(70))); + assertTrue(graph.connected(vertices.get(113), vertices.get(229))); + assertTrue(graph.connected(vertices.get(21), vertices.get(715))); + assertTrue(graph.connected(vertices.get(175), vertices.get(715))); + assertTrue(graph.connected(vertices.get(30), vertices.get(999))); + assertTrue(graph.connected(vertices.get(991), vertices.get(999))); + } + + /** Tests ConnectivityGraph on a small graph that has cycles. */ + @Test + public void testSmallCycles() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex1 = new ConnVertex(random); + ConnVertex vertex2 = new ConnVertex(random); + ConnVertex vertex3 = new ConnVertex(random); + ConnVertex vertex4 = new ConnVertex(random); + ConnVertex vertex5 = new ConnVertex(random); + assertTrue(graph.addEdge(vertex1, vertex2)); + assertTrue(graph.addEdge(vertex2, vertex3)); + assertTrue(graph.addEdge(vertex1, vertex3)); + assertTrue(graph.addEdge(vertex2, vertex4)); + assertTrue(graph.addEdge(vertex3, vertex4)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertTrue(graph.connected(vertex5, vertex1)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex4, vertex5)); + assertFalse(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex5, vertex1)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex1, vertex2)); + assertTrue(graph.removeEdge(vertex3, vertex4)); + assertTrue(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex2, vertex3)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex2, vertex4)); + assertFalse(graph.connected(vertex1, vertex4)); + } + + /** Tests ConnectivityGraph on a grid-based graph. */ + @Test + public void testGrid() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex = new ConnVertex(random); + assertTrue(graph.connected(vertex, vertex)); + + graph = new ConnGraph(SumAndMax.AUGMENTATION); + List> vertices = new ArrayList>(20); + for (int y = 0; y < 20; y++) { + List row = new ArrayList(20); + for (int x = 0; x < 20; x++) { + row.add(new ConnVertex(random)); + } + vertices.add(row); + } + for (int y = 0; y < 19; y++) { + for (int x = 0; x < 19; x++) { + assertTrue(graph.addEdge(vertices.get(y).get(x), vertices.get(y).get(x + 1))); + assertTrue(graph.addEdge(vertices.get(y).get(x), vertices.get(y + 1).get(x))); + } + } + graph.optimize(); + + assertTrue(graph.connected(vertices.get(0).get(0), vertices.get(15).get(12))); + assertTrue(graph.connected(vertices.get(0).get(0), vertices.get(18).get(19))); + assertFalse(graph.connected(vertices.get(0).get(0), vertices.get(19).get(19))); + assertFalse(graph.removeEdge(vertices.get(18).get(19), vertices.get(19).get(19))); + assertFalse(graph.removeEdge(vertices.get(0).get(0), vertices.get(2).get(2))); + + assertTrue(graph.removeEdge(vertices.get(12).get(8), vertices.get(11).get(8))); + assertTrue(graph.removeEdge(vertices.get(12).get(9), vertices.get(11).get(9))); + assertTrue(graph.removeEdge(vertices.get(12).get(8), vertices.get(12).get(7))); + assertTrue(graph.removeEdge(vertices.get(13).get(8), vertices.get(13).get(7))); + assertTrue(graph.removeEdge(vertices.get(13).get(8), vertices.get(14).get(8))); + assertTrue(graph.removeEdge(vertices.get(12).get(9), vertices.get(12).get(10))); + assertTrue(graph.removeEdge(vertices.get(13).get(9), vertices.get(13).get(10))); + assertTrue(graph.connected(vertices.get(2).get(1), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(12).get(8), vertices.get(13).get(9))); + assertTrue(graph.removeEdge(vertices.get(13).get(9), vertices.get(14).get(9))); + assertFalse(graph.connected(vertices.get(2).get(1), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(12).get(8), vertices.get(13).get(9))); + assertFalse(graph.connected(vertices.get(11).get(8), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(16).get(18), vertices.get(6).get(15))); + assertTrue(graph.removeEdge(vertices.get(12).get(9), vertices.get(12).get(8))); + assertTrue(graph.removeEdge(vertices.get(12).get(8), vertices.get(13).get(8))); + assertFalse(graph.connected(vertices.get(2).get(1), vertices.get(12).get(8))); + assertFalse(graph.connected(vertices.get(12).get(8), vertices.get(13).get(9))); + assertFalse(graph.connected(vertices.get(11).get(8), vertices.get(12).get(8))); + assertTrue(graph.connected(vertices.get(13).get(8), vertices.get(12).get(9))); + + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(5).get(15))); + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(7).get(15))); + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(6).get(14))); + assertTrue(graph.removeEdge(vertices.get(6).get(15), vertices.get(6).get(16))); + assertFalse(graph.removeEdge(vertices.get(6).get(15), vertices.get(5).get(15))); + assertFalse(graph.connected(vertices.get(16).get(18), vertices.get(6).get(15))); + assertFalse(graph.connected(vertices.get(7).get(15), vertices.get(6).get(15))); + graph.addEdge(vertices.get(6).get(15), vertices.get(7).get(15)); + assertTrue(graph.connected(vertices.get(16).get(18), vertices.get(6).get(15))); + + for (int y = 1; y < 19; y++) { + for (int x = 1; x < 19; x++) { + graph.removeEdge(vertices.get(y).get(x), vertices.get(y).get(x + 1)); + graph.removeEdge(vertices.get(y).get(x), vertices.get(y + 1).get(x)); + } + } + + assertTrue(graph.addEdge(vertices.get(5).get(6), vertices.get(0).get(7))); + assertTrue(graph.addEdge(vertices.get(12).get(8), vertices.get(5).get(6))); + assertTrue(graph.connected(vertices.get(5).get(6), vertices.get(14).get(0))); + assertTrue(graph.connected(vertices.get(12).get(8), vertices.get(0).get(17))); + assertFalse(graph.connected(vertices.get(3).get(5), vertices.get(0).get(9))); + assertFalse(graph.connected(vertices.get(14).get(2), vertices.get(11).get(18))); + + assertNull(graph.getVertexAugmentation(vertices.get(13).get(8))); + assertNull(graph.getVertexAugmentation(vertices.get(6).get(4))); + assertNull(graph.getComponentAugmentation(vertices.get(13).get(8))); + assertNull(graph.getComponentAugmentation(vertices.get(6).get(4))); + assertFalse(graph.vertexHasAugmentation(vertices.get(13).get(8))); + assertFalse(graph.vertexHasAugmentation(vertices.get(6).get(4))); + assertFalse(graph.componentHasAugmentation(vertices.get(13).get(8))); + assertFalse(graph.componentHasAugmentation(vertices.get(6).get(4))); + } + + /** Tests a graph with a hub-and-spokes subgraph and a clique subgraph. */ + @Test + public void testWheelAndClique() { + ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION); + Random random = new Random(6170); + ConnVertex hub = new ConnVertex(random); + List spokes1 = new ArrayList(10); + List spokes2 = new ArrayList(10); + for (int i = 0; i < 10; i++) { + ConnVertex spoke1 = new ConnVertex(random); + ConnVertex spoke2 = new ConnVertex(random); + assertTrue(graph.addEdge(spoke1, spoke2)); + assertNull(graph.setVertexAugmentation(spoke1, new SumAndMax(i, i))); + assertNull(graph.setVertexAugmentation(spoke2, new SumAndMax(i, i + 10))); + spokes1.add(spoke1); + spokes2.add(spoke2); + } + for (int i = 0; i < 10; i++) { + assertTrue(graph.addEdge(spokes1.get(i), hub)); + } + for (int i = 0; i < 10; i++) { + assertTrue(graph.addEdge(hub, spokes2.get(i))); + } + + List clique = new ArrayList(10); + for (int i = 0; i < 10; i++) { + ConnVertex vertex = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(vertex, new SumAndMax(i, i + 20))); + clique.add(vertex); + } + for (int i = 0; i < 10; i++) { + for (int j = i + 1; j < 10; j++) { + assertTrue(graph.addEdge(clique.get(i), clique.get(j))); + } + } + assertTrue(graph.addEdge(hub, clique.get(0))); + + assertTrue(graph.connected(spokes1.get(5), clique.get(3))); + assertTrue(graph.connected(spokes1.get(3), spokes2.get(8))); + assertTrue(graph.connected(spokes1.get(4), spokes2.get(4))); + assertTrue(graph.connected(clique.get(5), hub)); + SumAndMax expectedAugmentation = new SumAndMax(135, 29); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(spokes2.get(8))); + assertTrue(graph.componentHasAugmentation(spokes2.get(8))); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(hub)); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(clique.get(9))); + assertEquals(new SumAndMax(4, 4), graph.getVertexAugmentation(spokes1.get(4))); + assertTrue(graph.vertexHasAugmentation(spokes1.get(4))); + assertNull(graph.getVertexAugmentation(hub)); + assertFalse(graph.vertexHasAugmentation(hub)); + + assertTrue(graph.removeEdge(spokes1.get(5), hub)); + assertTrue(graph.connected(spokes1.get(5), clique.get(2))); + assertTrue(graph.connected(spokes1.get(5), spokes1.get(8))); + assertTrue(graph.connected(spokes1.get(5), spokes2.get(5))); + assertEquals(new SumAndMax(135, 29), graph.getComponentAugmentation(hub)); + assertTrue(graph.removeEdge(spokes2.get(5), hub)); + assertFalse(graph.connected(spokes1.get(5), clique.get(2))); + assertFalse(graph.connected(spokes1.get(5), spokes1.get(8))); + assertTrue(graph.connected(spokes1.get(5), spokes2.get(5))); + assertEquals(new SumAndMax(125, 29), graph.getComponentAugmentation(hub)); + assertTrue(graph.addEdge(spokes1.get(5), hub)); + assertTrue(graph.connected(spokes1.get(5), clique.get(2))); + assertTrue(graph.connected(spokes1.get(5), spokes1.get(8))); + assertTrue(graph.connected(spokes1.get(5), spokes2.get(5))); + assertEquals(new SumAndMax(135, 29), graph.getComponentAugmentation(hub)); + + assertTrue(graph.removeEdge(hub, clique.get(0))); + assertFalse(graph.connected(spokes1.get(3), clique.get(4))); + assertTrue(graph.connected(spokes2.get(7), hub)); + assertFalse(graph.connected(hub, clique.get(0))); + assertTrue(graph.connected(spokes2.get(9), spokes1.get(5))); + assertEquals(new SumAndMax(90, 19), graph.getComponentAugmentation(hub)); + assertEquals(new SumAndMax(90, 19), graph.getComponentAugmentation(spokes2.get(4))); + assertEquals(new SumAndMax(45, 29), graph.getComponentAugmentation(clique.get(1))); + + assertEquals(new SumAndMax(9, 29), graph.setVertexAugmentation(clique.get(9), new SumAndMax(-20, 4))); + for (int i = 0; i < 10; i++) { + assertEquals( + new SumAndMax(i, i + 10), graph.setVertexAugmentation(spokes2.get(i), new SumAndMax(i - 1, i))); + } + assertNull(graph.removeVertexAugmentation(hub)); + assertEquals(new SumAndMax(4, 4), graph.removeVertexAugmentation(spokes1.get(4))); + assertEquals(new SumAndMax(6, 7), graph.removeVertexAugmentation(spokes2.get(7))); + + assertEquals(new SumAndMax(70, 9), graph.getComponentAugmentation(hub)); + assertTrue(graph.componentHasAugmentation(hub)); + assertEquals(new SumAndMax(70, 9), graph.getComponentAugmentation(spokes1.get(6))); + assertEquals(new SumAndMax(16, 28), graph.getComponentAugmentation(clique.get(4))); + + assertTrue(graph.addEdge(hub, clique.get(1))); + expectedAugmentation = new SumAndMax(86, 28); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(hub)); + assertTrue(graph.componentHasAugmentation(hub)); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(spokes2.get(7))); + assertEquals(expectedAugmentation, graph.getComponentAugmentation(clique.get(3))); + + for (int i = 0; i < 10; i++) { + assertTrue(graph.removeEdge(hub, spokes1.get(i))); + if (i != 5) { + assertTrue(graph.removeEdge(hub, spokes2.get(i))); + } + } + assertFalse(graph.connected(hub, spokes1.get(8))); + assertFalse(graph.connected(hub, spokes2.get(4))); + assertTrue(graph.connected(hub, clique.get(5))); + + graph.clear(); + assertTrue(graph.addEdge(hub, spokes1.get(0))); + assertTrue(graph.addEdge(hub, spokes2.get(0))); + assertTrue(graph.addEdge(spokes1.get(0), spokes2.get(0))); + assertTrue(graph.connected(hub, spokes1.get(0))); + assertFalse(graph.connected(hub, spokes2.get(4))); + assertTrue(graph.connected(clique.get(5), clique.get(5))); + assertNull(graph.getComponentAugmentation(hub)); + assertNull(graph.getVertexAugmentation(spokes2.get(8))); + } + + /** + * Sets the matching between vertices.get(columnIndex) and vertices.get(columnIndex + 1) to the permutation + * suggested by newPermutation. See the comments for the implementation of testPermutations(). + * @param graph The graph. + * @param vertices The vertices. + * @param columnIndex The index of the column. + * @param oldPermutation The permutation for the current matching between vertices.get(columnIndex) and + * vertices.get(columnIndex + 1). setPermutation removes the edges in this matching. If there are currently no + * edges between those columns, then oldPermutation is null. + * @param newPermutation The permutation for the new matching. + * @return newPermutation. + */ + private int[] setPermutation( + ConnGraph graph, List> vertices, int columnIndex, + int[] oldPermutation, int[] newPermutation) { + List column1 = vertices.get(columnIndex); + List column2 = vertices.get(columnIndex + 1); + if (oldPermutation != null) { + for (int i = 0; i < oldPermutation.length; i++) { + assertTrue(graph.removeEdge(column1.get(i), column2.get(oldPermutation[i]))); + } + } + for (int i = 0; i < newPermutation.length; i++) { + assertTrue(graph.addEdge(column1.get(i), column2.get(newPermutation[i]))); + } + return newPermutation; + } + + /** + * Asserts that the specified permutation is the correct composite permutation for the specified column, i.e. that + * for all i, vertices.get(0).get(i) is in the same connected component as + * vertices.get(columnIndex + 1).get(expectedPermutation[i]). See the comments for the implementation of + * testPermutations(). + */ + private void checkPermutation( + ConnGraph graph, List> vertices, int columnIndex, int[] expectedPermutation) { + List firstColumn = vertices.get(0); + List column = vertices.get(columnIndex + 1); + for (int i = 0; i < expectedPermutation.length; i++) { + assertTrue(graph.connected(firstColumn.get(i), column.get(expectedPermutation[i]))); + } + } + + /** + * Asserts that the specified permutation differs from the correct composite permutation for the specified column in + * every position, i.e. that for all i, vertices.get(0).get(i) is in a different connected component from + * vertices.get(columnIndex + 1).get(wrongPermutation[i]). See the comments for the implementation of + * testPermutations(). + */ + private void checkWrongPermutation( + ConnGraph graph, List> vertices, int columnIndex, int[] wrongPermutation) { + List firstColumn = vertices.get(0); + List column = vertices.get(columnIndex + 1); + for (int i = 0; i < wrongPermutation.length; i++) { + assertFalse(graph.connected(firstColumn.get(i), column.get(wrongPermutation[i]))); + } + } + + /** + * Tests a graph in the style used to prove lower bounds on the performance of dynamic connectivity, as presented in + * https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-21-dynamic-connectivity-lower-bound/ . + */ + @Test + public void testPermutations() { + // The graph used in testPermutations() uses an 8 x 9 grid of vertices, such that vertices.get(i).get(j) is the + // vertex at row j, column i. There is a perfect matching between each pair of columns i and i + 1 - that is, + // there are eight non-adjacent edges from vertices in column i to vertices in column i + 1. These form a + // permutation, so that the element j of the permutation is the row number of the vertex in column i + 1 that is + // adjacent to the vertex at row j, column i. + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + List> vertices = new ArrayList>(9); + for (int i = 0; i < 9; i++) { + List column = new ArrayList(8); + for (int j = 0; j < 8; j++) { + column.add(new ConnVertex(random)); + } + vertices.add(column); + } + + int[] permutation0 = setPermutation(graph, vertices, 0, null, new int[]{2, 5, 0, 4, 7, 1, 3, 6}); + int[] permutation1 = setPermutation(graph, vertices, 1, null, new int[]{6, 5, 0, 7, 1, 2, 4, 3}); + int[] permutation2 = setPermutation(graph, vertices, 2, null, new int[]{2, 1, 7, 5, 6, 0, 4, 3}); + int[] permutation3 = setPermutation(graph, vertices, 3, null, new int[]{5, 2, 4, 6, 3, 0, 7, 1}); + int[] permutation4 = setPermutation(graph, vertices, 4, null, new int[]{5, 0, 2, 7, 4, 3, 1, 6}); + int[] permutation5 = setPermutation(graph, vertices, 5, null, new int[]{4, 7, 0, 1, 3, 6, 2, 5}); + int[] permutation6 = setPermutation(graph, vertices, 6, null, new int[]{4, 5, 3, 1, 7, 6, 2, 0}); + int[] permutation7 = setPermutation(graph, vertices, 7, null, new int[]{6, 7, 3, 0, 5, 1, 2, 4}); + + permutation0 = setPermutation(graph, vertices, 0, permutation0, new int[]{7, 5, 3, 0, 4, 2, 1, 6}); + checkWrongPermutation(graph, vertices, 0, new int[]{5, 3, 0, 4, 2, 1, 6, 7}); + checkPermutation(graph, vertices, 0, new int[]{7, 5, 3, 0, 4, 2, 1, 6}); + permutation4 = setPermutation(graph, vertices, 4, permutation4, new int[]{2, 7, 0, 6, 5, 4, 1, 3}); + checkWrongPermutation(graph, vertices, 4, new int[]{7, 1, 6, 0, 5, 4, 3, 2}); + checkPermutation(graph, vertices, 4, new int[]{2, 7, 1, 6, 0, 5, 4, 3}); + permutation2 = setPermutation(graph, vertices, 2, permutation2, new int[]{3, 5, 6, 1, 4, 2, 7, 0}); + checkWrongPermutation(graph, vertices, 2, new int[]{6, 0, 7, 5, 3, 2, 4, 1}); + checkPermutation(graph, vertices, 2, new int[]{1, 6, 0, 7, 5, 3, 2, 4}); + permutation6 = setPermutation(graph, vertices, 6, permutation6, new int[]{4, 7, 1, 3, 6, 0, 5, 2}); + checkWrongPermutation(graph, vertices, 6, new int[]{7, 3, 0, 4, 2, 5, 1, 6}); + checkPermutation(graph, vertices, 6, new int[]{6, 7, 3, 0, 4, 2, 5, 1}); + permutation1 = setPermutation(graph, vertices, 1, permutation1, new int[]{2, 4, 0, 5, 6, 3, 7, 1}); + checkWrongPermutation(graph, vertices, 1, new int[]{3, 5, 2, 6, 0, 4, 7, 1}); + checkPermutation(graph, vertices, 1, new int[]{1, 3, 5, 2, 6, 0, 4, 7}); + permutation5 = setPermutation(graph, vertices, 5, permutation5, new int[]{5, 3, 2, 0, 7, 1, 6, 4}); + checkWrongPermutation(graph, vertices, 5, new int[]{5, 1, 0, 4, 3, 6, 7, 2}); + checkPermutation(graph, vertices, 5, new int[]{2, 5, 1, 0, 4, 3, 6, 7}); + permutation3 = setPermutation(graph, vertices, 3, permutation3, new int[]{1, 7, 3, 0, 4, 5, 6, 2}); + checkWrongPermutation(graph, vertices, 3, new int[]{7, 3, 6, 2, 0, 4, 1, 5}); + checkPermutation(graph, vertices, 3, new int[]{5, 7, 3, 6, 2, 0, 4, 1}); + permutation7 = setPermutation(graph, vertices, 7, permutation7, new int[]{4, 7, 5, 6, 2, 0, 1, 3}); + checkWrongPermutation(graph, vertices, 7, new int[]{2, 0, 6, 4, 7, 3, 1, 5}); + checkPermutation(graph, vertices, 7, new int[]{5, 2, 0, 6, 4, 7, 3, 1}); + } + + /** Tests a graph based on the United States. */ + @Test + public void testUnitedStates() { + ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION); + Random random = new Random(6170); + ConnVertex alabama = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(alabama, new SumAndMax(7, 1819))); + ConnVertex alaska = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(alaska, new SumAndMax(1, 1959))); + ConnVertex arizona = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(arizona, new SumAndMax(9, 1912))); + ConnVertex arkansas = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(arkansas, new SumAndMax(4, 1836))); + ConnVertex california = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(california, new SumAndMax(53, 1850))); + ConnVertex colorado = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(colorado, new SumAndMax(7, 1876))); + ConnVertex connecticut = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(connecticut, new SumAndMax(5, 1788))); + ConnVertex delaware = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(delaware, new SumAndMax(1, 1787))); + ConnVertex florida = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(florida, new SumAndMax(27, 1845))); + ConnVertex georgia = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(georgia, new SumAndMax(14, 1788))); + ConnVertex hawaii = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(hawaii, new SumAndMax(2, 1959))); + ConnVertex idaho = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(idaho, new SumAndMax(2, 1890))); + ConnVertex illinois = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(illinois, new SumAndMax(18, 1818))); + ConnVertex indiana = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(indiana, new SumAndMax(9, 1816))); + ConnVertex iowa = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(iowa, new SumAndMax(4, 1846))); + ConnVertex kansas = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(kansas, new SumAndMax(4, 1861))); + ConnVertex kentucky = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(kentucky, new SumAndMax(6, 1792))); + ConnVertex louisiana = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(louisiana, new SumAndMax(6, 1812))); + ConnVertex maine = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(maine, new SumAndMax(2, 1820))); + ConnVertex maryland = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(maryland, new SumAndMax(8, 1788))); + ConnVertex massachusetts = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(massachusetts, new SumAndMax(9, 1788))); + ConnVertex michigan = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(michigan, new SumAndMax(14, 1837))); + ConnVertex minnesota = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(minnesota, new SumAndMax(8, 1858))); + ConnVertex mississippi = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(mississippi, new SumAndMax(4, 1817))); + ConnVertex missouri = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(missouri, new SumAndMax(8, 1821))); + ConnVertex montana = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(montana, new SumAndMax(1, 1889))); + ConnVertex nebraska = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(nebraska, new SumAndMax(3, 1867))); + ConnVertex nevada = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(nevada, new SumAndMax(4, 1864))); + ConnVertex newHampshire = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newHampshire, new SumAndMax(2, 1788))); + ConnVertex newJersey = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newJersey, new SumAndMax(12, 1787))); + ConnVertex newMexico = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newMexico, new SumAndMax(3, 1912))); + ConnVertex newYork = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(newYork, new SumAndMax(27, 1788))); + ConnVertex northCarolina = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(northCarolina, new SumAndMax(13, 1789))); + ConnVertex northDakota = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(northDakota, new SumAndMax(1, 1889))); + ConnVertex ohio = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(ohio, new SumAndMax(16, 1803))); + ConnVertex oklahoma = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(oklahoma, new SumAndMax(5, 1907))); + ConnVertex oregon = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(oregon, new SumAndMax(5, 1859))); + ConnVertex pennsylvania = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(pennsylvania, new SumAndMax(18, 1787))); + ConnVertex rhodeIsland = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(rhodeIsland, new SumAndMax(2, 1790))); + ConnVertex southCarolina = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(southCarolina, new SumAndMax(7, 1788))); + ConnVertex southDakota = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(southDakota, new SumAndMax(1, 1889))); + ConnVertex tennessee = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(tennessee, new SumAndMax(9, 1796))); + ConnVertex texas = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(texas, new SumAndMax(36, 1845))); + ConnVertex utah = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(utah, new SumAndMax(4, 1896))); + ConnVertex vermont = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(vermont, new SumAndMax(1, 1791))); + ConnVertex virginia = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(virginia, new SumAndMax(11, 1788))); + ConnVertex washington = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(washington, new SumAndMax(10, 1889))); + ConnVertex westVirginia = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(westVirginia, new SumAndMax(3, 1863))); + ConnVertex wisconsin = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(wisconsin, new SumAndMax(8, 1848))); + ConnVertex wyoming = new ConnVertex(random); + assertNull(graph.setVertexAugmentation(wyoming, new SumAndMax(1, 1890))); + + assertTrue(graph.addEdge(alabama, florida)); + assertTrue(graph.addEdge(alabama, georgia)); + assertTrue(graph.addEdge(alabama, mississippi)); + assertTrue(graph.addEdge(alabama, tennessee)); + assertTrue(graph.addEdge(arizona, california)); + assertTrue(graph.addEdge(arizona, colorado)); + assertTrue(graph.addEdge(arizona, nevada)); + assertTrue(graph.addEdge(arizona, newMexico)); + assertTrue(graph.addEdge(arizona, utah)); + assertTrue(graph.addEdge(arkansas, louisiana)); + assertTrue(graph.addEdge(arkansas, mississippi)); + assertTrue(graph.addEdge(arkansas, missouri)); + assertTrue(graph.addEdge(arkansas, oklahoma)); + assertTrue(graph.addEdge(arkansas, tennessee)); + assertTrue(graph.addEdge(arkansas, texas)); + assertTrue(graph.addEdge(california, nevada)); + assertTrue(graph.addEdge(california, oregon)); + assertTrue(graph.addEdge(colorado, kansas)); + assertTrue(graph.addEdge(colorado, nebraska)); + assertTrue(graph.addEdge(colorado, newMexico)); + assertTrue(graph.addEdge(colorado, oklahoma)); + assertTrue(graph.addEdge(colorado, utah)); + assertTrue(graph.addEdge(colorado, wyoming)); + assertTrue(graph.addEdge(connecticut, massachusetts)); + assertTrue(graph.addEdge(connecticut, newYork)); + assertTrue(graph.addEdge(connecticut, rhodeIsland)); + assertTrue(graph.addEdge(delaware, maryland)); + assertTrue(graph.addEdge(delaware, newJersey)); + assertTrue(graph.addEdge(delaware, pennsylvania)); + assertTrue(graph.addEdge(florida, georgia)); + assertTrue(graph.addEdge(georgia, northCarolina)); + assertTrue(graph.addEdge(georgia, southCarolina)); + assertTrue(graph.addEdge(georgia, tennessee)); + assertTrue(graph.addEdge(idaho, montana)); + assertTrue(graph.addEdge(idaho, nevada)); + assertTrue(graph.addEdge(idaho, oregon)); + assertTrue(graph.addEdge(idaho, utah)); + assertTrue(graph.addEdge(idaho, washington)); + assertTrue(graph.addEdge(idaho, wyoming)); + assertTrue(graph.addEdge(illinois, indiana)); + assertTrue(graph.addEdge(illinois, iowa)); + assertTrue(graph.addEdge(illinois, kentucky)); + assertTrue(graph.addEdge(illinois, missouri)); + assertTrue(graph.addEdge(illinois, wisconsin)); + assertTrue(graph.addEdge(indiana, kentucky)); + assertTrue(graph.addEdge(indiana, michigan)); + assertTrue(graph.addEdge(indiana, ohio)); + assertTrue(graph.addEdge(iowa, minnesota)); + assertTrue(graph.addEdge(iowa, missouri)); + assertTrue(graph.addEdge(iowa, nebraska)); + assertTrue(graph.addEdge(iowa, southDakota)); + assertTrue(graph.addEdge(iowa, wisconsin)); + assertTrue(graph.addEdge(kansas, missouri)); + assertTrue(graph.addEdge(kansas, nebraska)); + assertTrue(graph.addEdge(kansas, oklahoma)); + assertTrue(graph.addEdge(kentucky, missouri)); + assertTrue(graph.addEdge(kentucky, ohio)); + assertTrue(graph.addEdge(kentucky, tennessee)); + assertTrue(graph.addEdge(kentucky, virginia)); + assertTrue(graph.addEdge(kentucky, westVirginia)); + assertTrue(graph.addEdge(louisiana, mississippi)); + assertTrue(graph.addEdge(louisiana, texas)); + assertTrue(graph.addEdge(maine, newHampshire)); + assertTrue(graph.addEdge(maryland, pennsylvania)); + assertTrue(graph.addEdge(maryland, virginia)); + assertTrue(graph.addEdge(maryland, westVirginia)); + assertTrue(graph.addEdge(massachusetts, newHampshire)); + assertTrue(graph.addEdge(massachusetts, newYork)); + assertTrue(graph.addEdge(massachusetts, rhodeIsland)); + assertTrue(graph.addEdge(massachusetts, vermont)); + assertTrue(graph.addEdge(michigan, ohio)); + assertTrue(graph.addEdge(michigan, wisconsin)); + assertTrue(graph.addEdge(minnesota, northDakota)); + assertTrue(graph.addEdge(minnesota, southDakota)); + assertTrue(graph.addEdge(minnesota, wisconsin)); + assertTrue(graph.addEdge(mississippi, tennessee)); + assertTrue(graph.addEdge(missouri, nebraska)); + assertTrue(graph.addEdge(missouri, oklahoma)); + assertTrue(graph.addEdge(missouri, tennessee)); + assertTrue(graph.addEdge(montana, northDakota)); + assertTrue(graph.addEdge(montana, southDakota)); + assertTrue(graph.addEdge(montana, wyoming)); + assertTrue(graph.addEdge(nebraska, southDakota)); + assertTrue(graph.addEdge(nebraska, wyoming)); + assertTrue(graph.addEdge(nevada, oregon)); + assertTrue(graph.addEdge(nevada, utah)); + assertTrue(graph.addEdge(newHampshire, vermont)); + assertTrue(graph.addEdge(newJersey, newYork)); + assertTrue(graph.addEdge(newJersey, pennsylvania)); + assertTrue(graph.addEdge(newMexico, oklahoma)); + assertTrue(graph.addEdge(newMexico, texas)); + assertTrue(graph.addEdge(newMexico, utah)); + assertTrue(graph.addEdge(newYork, pennsylvania)); + assertTrue(graph.addEdge(newYork, vermont)); + assertTrue(graph.addEdge(northCarolina, southCarolina)); + assertTrue(graph.addEdge(northCarolina, tennessee)); + assertTrue(graph.addEdge(northCarolina, virginia)); + assertTrue(graph.addEdge(northDakota, southDakota)); + assertTrue(graph.addEdge(ohio, pennsylvania)); + assertTrue(graph.addEdge(ohio, westVirginia)); + assertTrue(graph.addEdge(oklahoma, texas)); + assertTrue(graph.addEdge(oregon, washington)); + assertTrue(graph.addEdge(pennsylvania, westVirginia)); + assertTrue(graph.addEdge(southDakota, wyoming)); + assertTrue(graph.addEdge(tennessee, virginia)); + assertTrue(graph.addEdge(utah, wyoming)); + assertTrue(graph.addEdge(virginia, westVirginia)); + + assertTrue(graph.connected(florida, washington)); + assertTrue(graph.connected(rhodeIsland, michigan)); + assertTrue(graph.connected(delaware, texas)); + assertFalse(graph.connected(alaska, newYork)); + assertFalse(graph.connected(hawaii, idaho)); + assertEquals(new SumAndMax(432, 1912), graph.getComponentAugmentation(newJersey)); + assertEquals(new SumAndMax(2, 1959), graph.getComponentAugmentation(hawaii)); + + // 2186: Aliens attack, split nation in two using lasers + assertTrue(graph.removeEdge(northDakota, minnesota)); + assertTrue(graph.removeEdge(southDakota, minnesota)); + assertTrue(graph.removeEdge(southDakota, iowa)); + assertTrue(graph.removeEdge(nebraska, iowa)); + assertTrue(graph.removeEdge(nebraska, missouri)); + assertTrue(graph.removeEdge(kansas, missouri)); + assertTrue(graph.removeEdge(oklahoma, missouri)); + assertTrue(graph.removeEdge(oklahoma, arkansas)); + assertTrue(graph.removeEdge(texas, arkansas)); + assertTrue(graph.connected(california, massachusetts)); + assertTrue(graph.connected(montana, virginia)); + assertTrue(graph.connected(idaho, southDakota)); + assertTrue(graph.connected(maine, tennessee)); + assertEquals(new SumAndMax(432, 1912), graph.getComponentAugmentation(vermont)); + assertTrue(graph.removeEdge(texas, louisiana)); + assertFalse(graph.connected(california, massachusetts)); + assertFalse(graph.connected(montana, virginia)); + assertTrue(graph.connected(idaho, southDakota)); + assertTrue(graph.connected(maine, tennessee)); + assertEquals(new SumAndMax(149, 1912), graph.getComponentAugmentation(wyoming)); + assertEquals(new SumAndMax(283, 1863), graph.getComponentAugmentation(vermont)); + + // 2254: California breaks off into ocean, secedes + assertTrue(graph.removeEdge(california, oregon)); + assertTrue(graph.removeEdge(california, nevada)); + assertTrue(graph.removeEdge(california, arizona)); + assertEquals(new SumAndMax(53, 1850), graph.removeVertexAugmentation(california)); + assertFalse(graph.connected(california, utah)); + assertFalse(graph.connected(california, oregon)); + assertNull(graph.getComponentAugmentation(california)); + assertEquals(new SumAndMax(96, 1912), graph.getComponentAugmentation(washington)); + assertEquals(new SumAndMax(283, 1863), graph.getComponentAugmentation(vermont)); + + // 2367: Nuclear armageddon + assertEquals(new SumAndMax(7, 1819), graph.removeVertexAugmentation(alabama)); + assertTrue(graph.removeEdge(alabama, florida)); + assertTrue(graph.removeEdge(alabama, georgia)); + assertTrue(graph.removeEdge(alabama, mississippi)); + assertTrue(graph.removeEdge(alabama, tennessee)); + assertEquals(new SumAndMax(1, 1959), graph.removeVertexAugmentation(alaska)); + assertEquals(new SumAndMax(9, 1912), graph.removeVertexAugmentation(arizona)); + assertTrue(graph.removeEdge(arizona, colorado)); + assertTrue(graph.removeEdge(arizona, nevada)); + assertTrue(graph.removeEdge(arizona, newMexico)); + assertTrue(graph.removeEdge(arizona, utah)); + assertEquals(new SumAndMax(4, 1836), graph.removeVertexAugmentation(arkansas)); + assertTrue(graph.removeEdge(arkansas, louisiana)); + assertTrue(graph.removeEdge(arkansas, mississippi)); + assertTrue(graph.removeEdge(arkansas, missouri)); + assertTrue(graph.removeEdge(arkansas, tennessee)); + assertEquals(new SumAndMax(7, 1876), graph.removeVertexAugmentation(colorado)); + assertTrue(graph.removeEdge(colorado, kansas)); + assertTrue(graph.removeEdge(colorado, nebraska)); + assertTrue(graph.removeEdge(colorado, newMexico)); + assertTrue(graph.removeEdge(colorado, oklahoma)); + assertTrue(graph.removeEdge(colorado, utah)); + assertTrue(graph.removeEdge(colorado, wyoming)); + assertEquals(new SumAndMax(5, 1788), graph.removeVertexAugmentation(connecticut)); + assertTrue(graph.removeEdge(connecticut, massachusetts)); + assertTrue(graph.removeEdge(connecticut, newYork)); + assertTrue(graph.removeEdge(connecticut, rhodeIsland)); + assertEquals(new SumAndMax(1, 1787), graph.removeVertexAugmentation(delaware)); + assertTrue(graph.removeEdge(delaware, maryland)); + assertTrue(graph.removeEdge(delaware, newJersey)); + assertTrue(graph.removeEdge(delaware, pennsylvania)); + assertEquals(new SumAndMax(27, 1845), graph.removeVertexAugmentation(florida)); + assertTrue(graph.removeEdge(florida, georgia)); + assertEquals(new SumAndMax(14, 1788), graph.removeVertexAugmentation(georgia)); + assertTrue(graph.removeEdge(georgia, northCarolina)); + assertTrue(graph.removeEdge(georgia, southCarolina)); + assertTrue(graph.removeEdge(georgia, tennessee)); + assertEquals(new SumAndMax(2, 1959), graph.removeVertexAugmentation(hawaii)); + assertEquals(new SumAndMax(2, 1890), graph.removeVertexAugmentation(idaho)); + assertTrue(graph.removeEdge(idaho, montana)); + assertTrue(graph.removeEdge(idaho, nevada)); + assertTrue(graph.removeEdge(idaho, oregon)); + assertTrue(graph.removeEdge(idaho, utah)); + assertTrue(graph.removeEdge(idaho, washington)); + assertTrue(graph.removeEdge(idaho, wyoming)); + assertEquals(new SumAndMax(18, 1818), graph.removeVertexAugmentation(illinois)); + assertTrue(graph.removeEdge(illinois, indiana)); + assertTrue(graph.removeEdge(illinois, iowa)); + assertTrue(graph.removeEdge(illinois, kentucky)); + assertTrue(graph.removeEdge(illinois, missouri)); + assertTrue(graph.removeEdge(illinois, wisconsin)); + assertEquals(new SumAndMax(9, 1816), graph.removeVertexAugmentation(indiana)); + assertTrue(graph.removeEdge(indiana, kentucky)); + assertTrue(graph.removeEdge(indiana, michigan)); + assertTrue(graph.removeEdge(indiana, ohio)); + assertEquals(new SumAndMax(4, 1846), graph.removeVertexAugmentation(iowa)); + assertTrue(graph.removeEdge(iowa, minnesota)); + assertTrue(graph.removeEdge(iowa, missouri)); + assertTrue(graph.removeEdge(iowa, wisconsin)); + assertEquals(new SumAndMax(4, 1861), graph.removeVertexAugmentation(kansas)); + assertTrue(graph.removeEdge(kansas, nebraska)); + assertTrue(graph.removeEdge(kansas, oklahoma)); + assertEquals(new SumAndMax(6, 1792), graph.removeVertexAugmentation(kentucky)); + assertTrue(graph.removeEdge(kentucky, missouri)); + assertTrue(graph.removeEdge(kentucky, ohio)); + assertTrue(graph.removeEdge(kentucky, tennessee)); + assertTrue(graph.removeEdge(kentucky, virginia)); + assertTrue(graph.removeEdge(kentucky, westVirginia)); + assertEquals(new SumAndMax(6, 1812), graph.removeVertexAugmentation(louisiana)); + assertTrue(graph.removeEdge(louisiana, mississippi)); + assertEquals(new SumAndMax(2, 1820), graph.removeVertexAugmentation(maine)); + assertTrue(graph.removeEdge(maine, newHampshire)); + assertEquals(new SumAndMax(8, 1788), graph.removeVertexAugmentation(maryland)); + assertTrue(graph.removeEdge(maryland, pennsylvania)); + assertTrue(graph.removeEdge(maryland, virginia)); + assertTrue(graph.removeEdge(maryland, westVirginia)); + assertEquals(new SumAndMax(9, 1788), graph.removeVertexAugmentation(massachusetts)); + assertTrue(graph.removeEdge(massachusetts, newHampshire)); + assertTrue(graph.removeEdge(massachusetts, newYork)); + assertTrue(graph.removeEdge(massachusetts, rhodeIsland)); + assertTrue(graph.removeEdge(massachusetts, vermont)); + assertEquals(new SumAndMax(14, 1837), graph.removeVertexAugmentation(michigan)); + assertTrue(graph.removeEdge(michigan, ohio)); + assertTrue(graph.removeEdge(michigan, wisconsin)); + assertEquals(new SumAndMax(8, 1858), graph.removeVertexAugmentation(minnesota)); + assertTrue(graph.removeEdge(minnesota, wisconsin)); + assertEquals(new SumAndMax(4, 1817), graph.removeVertexAugmentation(mississippi)); + assertTrue(graph.removeEdge(mississippi, tennessee)); + assertEquals(new SumAndMax(8, 1821), graph.removeVertexAugmentation(missouri)); + assertTrue(graph.removeEdge(missouri, tennessee)); + assertEquals(new SumAndMax(1, 1889), graph.removeVertexAugmentation(montana)); + assertTrue(graph.removeEdge(montana, northDakota)); + assertTrue(graph.removeEdge(montana, southDakota)); + assertTrue(graph.removeEdge(montana, wyoming)); + assertEquals(new SumAndMax(3, 1867), graph.removeVertexAugmentation(nebraska)); + assertTrue(graph.removeEdge(nebraska, southDakota)); + assertTrue(graph.removeEdge(nebraska, wyoming)); + assertEquals(new SumAndMax(4, 1864), graph.removeVertexAugmentation(nevada)); + assertTrue(graph.removeEdge(nevada, oregon)); + assertTrue(graph.removeEdge(nevada, utah)); + assertEquals(new SumAndMax(2, 1788), graph.removeVertexAugmentation(newHampshire)); + assertTrue(graph.removeEdge(newHampshire, vermont)); + assertEquals(new SumAndMax(12, 1787), graph.removeVertexAugmentation(newJersey)); + assertTrue(graph.removeEdge(newJersey, newYork)); + assertTrue(graph.removeEdge(newJersey, pennsylvania)); + assertEquals(new SumAndMax(3, 1912), graph.removeVertexAugmentation(newMexico)); + assertTrue(graph.removeEdge(newMexico, oklahoma)); + assertTrue(graph.removeEdge(newMexico, texas)); + assertTrue(graph.removeEdge(newMexico, utah)); + assertEquals(new SumAndMax(27, 1788), graph.removeVertexAugmentation(newYork)); + assertTrue(graph.removeEdge(newYork, pennsylvania)); + assertTrue(graph.removeEdge(newYork, vermont)); + assertEquals(new SumAndMax(13, 1789), graph.removeVertexAugmentation(northCarolina)); + assertTrue(graph.removeEdge(northCarolina, southCarolina)); + assertTrue(graph.removeEdge(northCarolina, tennessee)); + assertTrue(graph.removeEdge(northCarolina, virginia)); + assertEquals(new SumAndMax(1, 1889), graph.removeVertexAugmentation(northDakota)); + assertTrue(graph.removeEdge(northDakota, southDakota)); + assertEquals(new SumAndMax(16, 1803), graph.removeVertexAugmentation(ohio)); + assertTrue(graph.removeEdge(ohio, pennsylvania)); + assertTrue(graph.removeEdge(ohio, westVirginia)); + assertEquals(new SumAndMax(5, 1907), graph.removeVertexAugmentation(oklahoma)); + assertTrue(graph.removeEdge(oklahoma, texas)); + assertEquals(new SumAndMax(5, 1859), graph.removeVertexAugmentation(oregon)); + assertTrue(graph.removeEdge(oregon, washington)); + assertEquals(new SumAndMax(18, 1787), graph.removeVertexAugmentation(pennsylvania)); + assertTrue(graph.removeEdge(pennsylvania, westVirginia)); + assertEquals(new SumAndMax(2, 1790), graph.removeVertexAugmentation(rhodeIsland)); + assertEquals(new SumAndMax(7, 1788), graph.removeVertexAugmentation(southCarolina)); + assertEquals(new SumAndMax(1, 1889), graph.removeVertexAugmentation(southDakota)); + assertTrue(graph.removeEdge(southDakota, wyoming)); + assertEquals(new SumAndMax(9, 1796), graph.removeVertexAugmentation(tennessee)); + assertTrue(graph.removeEdge(tennessee, virginia)); + assertEquals(new SumAndMax(36, 1845), graph.removeVertexAugmentation(texas)); + assertEquals(new SumAndMax(4, 1896), graph.removeVertexAugmentation(utah)); + assertTrue(graph.removeEdge(utah, wyoming)); + assertEquals(new SumAndMax(1, 1791), graph.removeVertexAugmentation(vermont)); + assertEquals(new SumAndMax(11, 1788), graph.removeVertexAugmentation(virginia)); + assertTrue(graph.removeEdge(virginia, westVirginia)); + assertEquals(new SumAndMax(10, 1889), graph.removeVertexAugmentation(washington)); + assertEquals(new SumAndMax(3, 1863), graph.removeVertexAugmentation(westVirginia)); + assertEquals(new SumAndMax(8, 1848), graph.removeVertexAugmentation(wisconsin)); + assertEquals(new SumAndMax(1, 1890), graph.removeVertexAugmentation(wyoming)); + + assertFalse(graph.connected(georgia, newMexico)); + assertFalse(graph.connected(wisconsin, michigan)); + assertFalse(graph.connected(ohio, kentucky)); + assertFalse(graph.connected(alaska, connecticut)); + assertNull(graph.getComponentAugmentation(southDakota)); + assertNull(graph.getComponentAugmentation(arkansas)); + } + + /** Tests ConnectivityGraph on the graph for a dodecahedron. */ + @Test + public void testDodecahedron() { + ConnGraph graph = new ConnGraph(); + Random random = new Random(6170); + ConnVertex vertex1 = new ConnVertex(random); + ConnVertex vertex2 = new ConnVertex(random); + ConnVertex vertex3 = new ConnVertex(random); + ConnVertex vertex4 = new ConnVertex(random); + ConnVertex vertex5 = new ConnVertex(random); + ConnVertex vertex6 = new ConnVertex(random); + ConnVertex vertex7 = new ConnVertex(random); + ConnVertex vertex8 = new ConnVertex(random); + ConnVertex vertex9 = new ConnVertex(random); + ConnVertex vertex10 = new ConnVertex(random); + ConnVertex vertex11 = new ConnVertex(random); + ConnVertex vertex12 = new ConnVertex(random); + ConnVertex vertex13 = new ConnVertex(random); + ConnVertex vertex14 = new ConnVertex(random); + ConnVertex vertex15 = new ConnVertex(random); + ConnVertex vertex16 = new ConnVertex(random); + ConnVertex vertex17 = new ConnVertex(random); + ConnVertex vertex18 = new ConnVertex(random); + ConnVertex vertex19 = new ConnVertex(random); + ConnVertex vertex20 = new ConnVertex(random); + + assertTrue(graph.addEdge(vertex1, vertex2)); + assertTrue(graph.addEdge(vertex1, vertex5)); + assertTrue(graph.addEdge(vertex1, vertex6)); + assertTrue(graph.addEdge(vertex2, vertex3)); + assertTrue(graph.addEdge(vertex2, vertex8)); + assertTrue(graph.addEdge(vertex3, vertex4)); + assertTrue(graph.addEdge(vertex3, vertex10)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertTrue(graph.addEdge(vertex4, vertex12)); + assertTrue(graph.addEdge(vertex5, vertex14)); + assertTrue(graph.addEdge(vertex6, vertex7)); + assertTrue(graph.addEdge(vertex6, vertex15)); + assertTrue(graph.addEdge(vertex7, vertex8)); + assertTrue(graph.addEdge(vertex7, vertex16)); + assertTrue(graph.addEdge(vertex8, vertex9)); + assertTrue(graph.addEdge(vertex9, vertex10)); + assertTrue(graph.addEdge(vertex9, vertex17)); + assertTrue(graph.addEdge(vertex10, vertex11)); + assertTrue(graph.addEdge(vertex11, vertex12)); + assertTrue(graph.addEdge(vertex11, vertex18)); + assertTrue(graph.addEdge(vertex12, vertex13)); + assertTrue(graph.addEdge(vertex13, vertex14)); + assertTrue(graph.addEdge(vertex13, vertex19)); + assertTrue(graph.addEdge(vertex14, vertex15)); + assertTrue(graph.addEdge(vertex15, vertex20)); + assertTrue(graph.addEdge(vertex16, vertex17)); + assertTrue(graph.addEdge(vertex16, vertex20)); + assertTrue(graph.addEdge(vertex17, vertex18)); + assertTrue(graph.addEdge(vertex18, vertex19)); + assertTrue(graph.addEdge(vertex19, vertex20)); + graph.optimize(); + + assertTrue(graph.connected(vertex1, vertex17)); + assertTrue(graph.connected(vertex7, vertex15)); + + assertTrue(graph.removeEdge(vertex5, vertex14)); + assertTrue(graph.removeEdge(vertex6, vertex15)); + assertTrue(graph.removeEdge(vertex7, vertex16)); + assertTrue(graph.removeEdge(vertex12, vertex13)); + assertTrue(graph.removeEdge(vertex16, vertex17)); + assertTrue(graph.connected(vertex1, vertex14)); + assertTrue(graph.connected(vertex4, vertex20)); + assertTrue(graph.connected(vertex14, vertex16)); + + assertTrue(graph.removeEdge(vertex18, vertex19)); + assertFalse(graph.connected(vertex1, vertex14)); + assertFalse(graph.connected(vertex4, vertex20)); + assertTrue(graph.connected(vertex14, vertex16)); + + graph.clear(); + graph.optimize(); + assertTrue(graph.connected(vertex7, vertex7)); + assertFalse(graph.connected(vertex1, vertex2)); + } + + /** Tests the zero-argument ConnVertex constructor. */ + @Test + public void testDefaultConnVertexConstructor() { + ConnGraph graph = new ConnGraph(); + ConnVertex vertex1 = new ConnVertex(); + ConnVertex vertex2 = new ConnVertex(); + ConnVertex vertex3 = new ConnVertex(); + ConnVertex vertex4 = new ConnVertex(); + ConnVertex vertex5 = new ConnVertex(); + ConnVertex vertex6 = new ConnVertex(); + assertTrue(graph.addEdge(vertex1, vertex2)); + assertTrue(graph.addEdge(vertex2, vertex3)); + assertTrue(graph.addEdge(vertex1, vertex3)); + assertTrue(graph.addEdge(vertex4, vertex5)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex1, vertex4)); + + graph.optimize(); + assertTrue(graph.removeEdge(vertex1, vertex3)); + assertTrue(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex1, vertex4)); + assertTrue(graph.removeEdge(vertex1, vertex2)); + assertFalse(graph.connected(vertex1, vertex3)); + assertTrue(graph.connected(vertex4, vertex5)); + assertFalse(graph.connected(vertex1, vertex4)); + + assertEquals(Collections.singleton(vertex3), new HashSet(graph.adjacentVertices(vertex2))); + assertTrue(graph.adjacentVertices(vertex1).isEmpty()); + assertTrue(graph.adjacentVertices(vertex6).isEmpty()); + } +} diff --git a/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java b/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java new file mode 100644 index 000000000..393c3599f --- /dev/null +++ b/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java @@ -0,0 +1,39 @@ +package com.github.btrekkie.connectivity.test; + +import com.github.btrekkie.connectivity.Augmentation; + +/** Stores two values: a sum and a maximum. Used for testing augmentation in ConnGraph. */ +class SumAndMax { + /** An Augmentation that combines two SumAndMaxes into one. */ + public static final Augmentation AUGMENTATION = new Augmentation() { + @Override + public Object combine(Object value1, Object value2) { + SumAndMax sumAndMax1 = (SumAndMax)value1; + SumAndMax sumAndMax2 = (SumAndMax)value2; + return new SumAndMax(sumAndMax1.sum + sumAndMax2.sum, Math.max(sumAndMax1.max, sumAndMax2.max)); + } + }; + + public final int sum; + + public final int max; + + public SumAndMax(int sum, int max) { + this.sum = sum; + this.max = max; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SumAndMax)) { + return false; + } + SumAndMax sumAndMax = (SumAndMax)obj; + return sum == sumAndMax.sum && max == sumAndMax.max; + } + + @Override + public int hashCode() { + return 31 * sum + max; + } +} diff --git a/target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar b/target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar new file mode 100644 index 0000000000000000000000000000000000000000..509855f6bb783ba03e2a619bf56212574162cada GIT binary patch literal 35737 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 J7R%4y{vSezlB@s# literal 0 HcmV?d00001 From 1536209eb3ee3a83f90b1f7213154186adcb81e0 Mon Sep 17 00:00:00 2001 From: William Jacobs Date: Wed, 6 Mar 2019 22:24:50 -0500 Subject: [PATCH 03/29] Optimization: don't push forest edges if there are no non-forest edges This optimizes the process of searching for a replacement edge to refrain from pushing forest edges down when a tree does not have any same-level non-forest edges. If a tree doesn't have any such edges, then there definitely isn't a replacement edge at that level, so we can avoid doing extra work. Interestingly, with this optimization, ConnGraph reduces to a single Euler tour forest if the graph is a forest, with addEdge and removeEdge taking O(log N) (non-amortized) time with high probability. --- pom.xml | 2 +- .../btrekkie/connectivity/ConnGraph.java | 25 +++++++++++------- target/dynamic-connectivity-0.1.0.jar | Bin 16535 -> 0 bytes ...nectivity-0.1.1-jar-with-dependencies.jar} | Bin 35737 -> 36024 bytes target/dynamic-connectivity-0.1.1.jar | Bin 0 -> 16831 bytes 5 files changed, 16 insertions(+), 11 deletions(-) delete mode 100644 target/dynamic-connectivity-0.1.0.jar rename target/{dynamic-connectivity-0.1.0-jar-with-dependencies.jar => dynamic-connectivity-0.1.1-jar-with-dependencies.jar} (60%) create mode 100644 target/dynamic-connectivity-0.1.1.jar diff --git a/pom.xml b/pom.xml index 604e31212..f3c88c5c5 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.btrekkie.connectivity dynamic-connectivity - 0.1.0 + 0.1.1 dynamic-connectivity Data structure for dynamic connectivity in undirected graphs diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index 4f16deb76..c60b7db2f 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -829,17 +829,22 @@ public class ConnGraph { while (levelVertex1 != null) { EulerTourNode root1 = levelVertex1.arbitraryVisit.root(); EulerTourNode root2 = levelVertex2.arbitraryVisit.root(); - EulerTourNode root; - if (root1.size < root2.size) { - root = root1; - } else { - root = root2; - } - pushForestEdges(root); - replacementEdge = findReplacementEdge(root); - if (replacementEdge != null) { - break; + // Optimization: if hasGraphEdge is false for one of the roots, then there definitely isn't a + // replacement edge at this level + if (root1.hasGraphEdge && root2.hasGraphEdge) { + EulerTourNode root; + if (root1.size < root2.size) { + root = root1; + } else { + root = root2; + } + + pushForestEdges(root); + replacementEdge = findReplacementEdge(root); + if (replacementEdge != null) { + break; + } } // To save space, get rid of trees with one node diff --git a/target/dynamic-connectivity-0.1.0.jar b/target/dynamic-connectivity-0.1.0.jar deleted file mode 100644 index d097c59ba8a28edf7d4bdb8c293c34229c53caf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16535 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 J7R%4y{vSezlB@s# diff --git a/target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar b/target/dynamic-connectivity-0.1.1-jar-with-dependencies.jar similarity index 60% rename from target/dynamic-connectivity-0.1.0-jar-with-dependencies.jar rename to target/dynamic-connectivity-0.1.1-jar-with-dependencies.jar index 509855f6bb783ba03e2a619bf56212574162cada..9a757a0386524358af8dc6c47543f13f8bdc9a26 100644 GIT binary patch delta 13614 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 delta 13358 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 Date: Sat, 16 Mar 2019 14:42:36 -0400 Subject: [PATCH 04/29] Split removeEdge and optimize() into smaller methods This moves a bit of functionality from ConnGraph.removeEdge and ConnGraph.optimize() into new methods, in order to improve readability. --- pom.xml | 4 +- .../btrekkie/connectivity/ConnGraph.java | 92 ++++++++++-------- ...nnectivity-0.1.1-jar-with-dependencies.jar | Bin 36024 -> 0 bytes target/dynamic-connectivity-0.1.1.jar | Bin 16831 -> 0 bytes ...nnectivity-0.1.2-jar-with-dependencies.jar | Bin 0 -> 36143 bytes target/dynamic-connectivity-0.1.2.jar | Bin 0 -> 16934 bytes 6 files changed, 55 insertions(+), 41 deletions(-) delete mode 100644 target/dynamic-connectivity-0.1.1-jar-with-dependencies.jar delete mode 100644 target/dynamic-connectivity-0.1.1.jar create mode 100644 target/dynamic-connectivity-0.1.2-jar-with-dependencies.jar create mode 100644 target/dynamic-connectivity-0.1.2.jar diff --git a/pom.xml b/pom.xml index f3c88c5c5..61902a166 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.btrekkie.connectivity dynamic-connectivity - 0.1.1 + 0.1.2 dynamic-connectivity Data structure for dynamic connectivity in undirected graphs @@ -43,7 +43,7 @@ com.github.btrekkie RedBlackNode - 1.0.0 + 1.0.1 junit diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index c60b7db2f..2150dd9fb 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -75,8 +75,8 @@ import java.util.Map; * reason they are probabilistic is that they involve hash lookups, using the vertexInfo and VertexInfo.edges hash maps. * Given that each ConnVertex has a random hash code, it is easy to demonstrate that lookups take O(1) expected time. * Furthermore, I claim that they take O(log N / log log N) time with high probability. This claim is sufficient to - * establish that all time bounds that are at least O(log N / log log N) if we exclude the hash lookup can be sustained - * if we add the qualifier "with high probability." + * establish that all time bounds that are at least O(log N / log log N) if we exclude hash lookups can be sustained if + * we add the qualifier "with high probability." * * This claim is based on information presented in * https://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-10-dictionaries/ . @@ -258,7 +258,7 @@ public class ConnGraph { /** * Equivalent implementation is contractual. * - * This method is useful for when a node's lists (graphListHead or forestListHead) or a vertex's arbitrary visit + * This method is useful for when an EulerTourVertex's lists (graphListHead or forestListHead) or arbitrary visit * change, as these affect the hasGraphEdge and hasForestEdge augmentations. */ private void augmentAncestorFlags(EulerTourNode node) { @@ -505,6 +505,33 @@ public class ConnGraph { return new EulerTourEdge(newNode, max); } + /** Removes the specified edge from the Euler tour forest F_i. */ + private void removeForestEdge(EulerTourEdge edge) { + EulerTourNode firstNode; + EulerTourNode secondNode; + if (edge.visit1.compareTo(edge.visit2) < 0) { + firstNode = edge.visit1; + secondNode = edge.visit2; + } else { + firstNode = edge.visit2; + secondNode = edge.visit1; + } + + if (firstNode.vertex.arbitraryVisit == firstNode) { + EulerTourNode successor = secondNode.successor(); + firstNode.vertex.arbitraryVisit = successor; + augmentAncestorFlags(firstNode); + augmentAncestorFlags(successor); + } + + EulerTourNode root = firstNode.root(); + EulerTourNode[] firstSplitRoots = root.split(firstNode); + EulerTourNode before = firstSplitRoots[0]; + EulerTourNode[] secondSplitRoots = firstSplitRoots[1].split(secondNode.successor()); + before.concatenate(secondSplitRoots[1]); + firstNode.removeWithoutGettingRoot(); + } + /** * Adds the specified edge to the edge map for srcInfo (srcInfo.edges). Assumes that the edge is not currently in * the map. @@ -794,31 +821,8 @@ public class ConnGraph { augmentAncestorFlags(edge.vertex2.arbitraryVisit); if (edge.eulerTourEdge != null) { - // Remove the edge from all of the Euler tour trees that contain it for (EulerTourEdge levelEdge = edge.eulerTourEdge; levelEdge != null; levelEdge = levelEdge.higherEdge) { - EulerTourNode firstNode; - EulerTourNode secondNode; - if (levelEdge.visit1.compareTo(levelEdge.visit2) < 0) { - firstNode = levelEdge.visit1; - secondNode = levelEdge.visit2; - } else { - firstNode = levelEdge.visit2; - secondNode = levelEdge.visit1; - } - - if (firstNode.vertex.arbitraryVisit == firstNode) { - EulerTourNode successor = secondNode.successor(); - firstNode.vertex.arbitraryVisit = successor; - augmentAncestorFlags(firstNode); - augmentAncestorFlags(successor); - } - - EulerTourNode root = firstNode.root(); - EulerTourNode[] firstSplitRoots = root.split(firstNode); - EulerTourNode before = firstSplitRoots[0]; - EulerTourNode[] secondSplitRoots = firstSplitRoots[1].split(secondNode.successor()); - before.concatenate(secondSplitRoots[1]); - firstNode.removeWithoutGettingRoot(); + removeForestEdge(levelEdge); } edge.eulerTourEdge = null; @@ -1044,18 +1048,10 @@ public class ConnGraph { } /** - * Attempts to optimize the internal representation of the graph so that future updates will take less time. This - * method does not affect how long queries such as "connected" will take. You may find it beneficial to call - * optimize() when there is some downtime. Note that this method generally increases the amount of space the - * ConnGraph uses, but not beyond the bound of O(V log V + E). + * Pushes all forest edges as far down as possible, so that any further pushes would violate the constraint on the + * size of connected components. The current implementation of this method takes O(V log^2 V) time. */ - public void optimize() { - // The current implementation of optimize() takes O(V log^2 V + E log V log log V) time - - rebuild(); - - // Greedily push each forest edge as far down as possible - to the lowest level where the constraint on the - // size of connected components isn't violated + private void optimizeForestEdges() { for (VertexInfo info : vertexInfo.values()) { int level = maxLogVertexCountSinceRebuild; EulerTourVertex vertex; @@ -1121,8 +1117,13 @@ public class ConnGraph { level++; } } + } - // Push each non-forest edge down to the lowest level where the endpoints are in the same connected component + /** + * Pushes each non-forest edge down to the lowest level where the endpoints are in the same connected component. The + * current implementation of this method takes O(V log V + E log V log log V) time. + */ + private void optimizeGraphEdges() { for (VertexInfo info : vertexInfo.values()) { EulerTourVertex vertex; for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex); @@ -1185,4 +1186,17 @@ public class ConnGraph { } } } + + /** + * Attempts to optimize the internal representation of the graph so that future updates will take less time. This + * method does not affect how long queries such as "connected" will take. You may find it beneficial to call + * optimize() when there is some downtime. Note that this method generally increases the amount of space the + * ConnGraph uses, but not beyond the bound of O(V log V + E). + */ + public void optimize() { + // The current implementation of optimize() takes O(V log^2 V + E log V log log V) time + rebuild(); + optimizeForestEdges(); + optimizeGraphEdges(); + } } diff --git a/target/dynamic-connectivity-0.1.1-jar-with-dependencies.jar b/target/dynamic-connectivity-0.1.1-jar-with-dependencies.jar deleted file mode 100644 index 9a757a0386524358af8dc6c47543f13f8bdc9a26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36024 zcmb5U1GFeTlQz2Tvu)e9&$e;4ZQHhO+qP}nwr%^KcW}R%|E~Gh++Lkdx>J=@bt+j^ zPx40+7z7I7pUbeuSmxgf|M7tQn@bBT^3#aRh|vA{4>AYL0!SY3e@+^&RbvEG*27 z{x2N3{{zQgP9q&lGY7~23op_Cz-w=0sH1PGXJDaYZDaVa&LRFy^ZA9a1I)j}go*+H z!2drq@UPAl|B4~?R|?|)Ed28#?r3DM=V)V3YhbD8;83LU;e)({^0R$O+OUSBA1~nR zKXnh!mXXBGhx>~Vo)1X8RvZe<%(k8@Bdaz|+Qr0xuQFV$!nA51_>Z7hag|)&F^OVs z)6%lK*~jn4lW*X!MjjL6X9nWgs4u;nOphCmm&}i^Y>(?Lx*xAIC|{A!t%xUd%A#XE zI#%7)2qO0JEzwqv;Vn_M_c%0dZqhxwYwo^*5lOQ{aa`WMtrFtQ$9(JoMDNQ!+0N3z z1@3PctjzsmHS27Hik$nwht1E)0JPT+!Wswleo5TUOJSDm=Uo(BpQH??&CH`??24}8 z`HRkJtj$PUhio$M>G=%q4^)>O<1-xV?WbLy@96tmI@Na)wx0Og?mz}eJP21X#JFQ2 zbv6*82)?pvSS%WMA)I20(vHM!_9(?>(tZigocP678@P53M!&Z-me&>iQt=@%Vlyn7_G;3M0}0Ba z_+=hYuzLIEB~!_vbYZh3rg)-&G}C63(_TZ`1)2m zQ1ej67Hj8YaC7yKB!VK^>QgT#508%}`T!m5QUbJM+MO=_( zT8K%__%sS!`F*p5P&Mv$DIXHR_u6X7us_>k?eFwt_&A>FlI6f*1WPdC%$|o_`N2zy z;c1l8MFYvydqjR-K_^mqnXIXHD8E)57J8)%_-bt}{Myq#nnmV=QEMhl7#x7Qvceo0 zE3X&XIgX7`HKs%uo%4i;Wj06Tf7?+X0MU-W(xw|)?jNtbstzk#CL@fn52?F?#@fMU zNbAZEBh#@&*@Z*}gR8;co;YqGWu4htifZEE zKXE6K;FKdqmm%~ZD14RU-jU@Oj}qJs(~h-HcB}^*V{Ejo^-0t9GkOmo<=Fq3xK}w- z`e4>tdE`A&S5f_-2bRk!$P1EC&Ot0eGAMPEt1>HaKQ3Xye0gAiTzo|m_aR*=Td5F1 z5?@l({XIc(m4ei(aUiY~hJ6RO8bo)oRfFzL%lzFZBw>KYSZ&X~()vcj)^lKt7P>rx z{Om+)B+n!gHd0^NFX`gqEOi-G6Qp(2NQ$lY?3-hg!=WQ_ z1;m&wSKqmmO(y6gRwmpD$J`L>#j--m_7}G1-jG#vU7NzXTy+g_dbUGboEE2qdr4d& zH{(Pe9h!=lc1_=$h2!HgySoKWSH}h0;nOn5@iLb?spNGlCX4lRa~n(Uka!iH@oiG( zIlT_KdbyHPJ~0fX=G;9&38zn|?&yjCEB0Aj!MbfkwtRwe+_-)n174E|I8qcWDJB+9 z`XI>XBl$LfU({`G=nbrUGTPmfp7$AQZkva+8}wK6_H0SH<@m?H}_SV&>4#> z*)EnaKDlY5Gv+<}ag?ri?2ve((J>GAO@|om=BnV8ITQe4sJvY8TRv)pFhom42RwX5 zo!p{9gK01AT&Q&&Dvv&o`dD$eKr#GW@CTs6J1)OmAZ)zRdML|^_zvy6QvVa4z%4IX98mN zS$t=MtknRT{3Kl)qR#b)=Vc}X8y+_ghk{alzbqHH`AbslMuO-yc)BsG(+!zWN$bGo zgxo7a)0d+*9mavLc*55GdM(vnnVgI9}W^lftZ zD<{M)2T7L)6xBnnxJc`8jonc72ZSsGLzTi-$q}%K^jQd-b3%Ef28Y8|0ST3Vh6i!S z5jQLasX?dyj7VWVr`pceiXFAZ8n?z;q5;A2*AaY>tv&2T&-*K#px2*YOZ*@wGZ;op zuLDn90b&iH$S9*_3u6EaqgdXRrv31u(rhJtT=_eLqcmz_m=~tv2Sc_Kfo#c9BgW{q zu?M@~k+X)5*w(mWcMRD%h9a@!XZusi94F>Qp_E7}>ZZ3p%RBC4J_z;K5t6S{KO ziQb2CN4gDfqbJoFC4L7>8{u-7<_f11A(w#C1FDmpbiqpX#2&-jj#E`ZShAG16mHn` zdVI;~(A28m*E35>(7c);niA}N0skkh-u~Isdz$q-CR2-v$OO1;U~8OH*~JP>TS9M! z{CYI$IWm5x>~JFB=Njho0{ix+mOHkrD2I3n!@e)s=K1QKlb~iUQ zkgymYVL>cZ_BOCJFkxRI6H&!hU2`b3H95$G4R!v9)!UU=EIb2|Ki=ikL0zN%pF2(6 z04H+Y=qOhu%sK@lNNUSCt*q#tf_D3lu_04(zfN71o%~Z!ARpeLgW^^>8}j@pM##y7i_* z4xM7DdJ0n%IjO3`IM8Ze7Jbkkg?BI_f+ITQppA&Aj#_p=_>%2lKf}@Uz;&{HIQ<*X z3nmxOMzj{1NKajaXf>(<;gEj#oL%ySFmxAGS;z3K(;pHvfj~1f7s-v6p=h@Vg7ttJ zFa7w4FvpWaYJv(%``2C*M0*^ZUT{p$w2nD7itC&k1!XLhG0G%l;JCBhXtjE^FFFE* zsy-Brn4X4p28{p~@ zGBR+mvEPPcBs2K6WrZ~31XIjLcA1fZ=lOiIG#E);YJP?peH6yGt$G;OxqPx4GG*Kd z^oM<`nM~9i+B9f|Ol2ri^@9%0)oE^145~F)z%Wwy%Mc9v5~2=PqI@`Y)Mqeh>P3WS zgLSM7ms(h1lxyxj#}m*|mjPB_Po~#ie21rj6=x3y>;7o78R7B1^7E7|*I#3c!r73r zYw=P?v}O_N!;6(|lWqMZWlS?TW5_{d-8gE^XhkQ9D!LGwrWvo!V`mTQhZ^Je^JKBs zJ8{RlJn!j6=a|ODeG&%5L`cp{b4M5fVbQEWPxS%ez^?Mbm^{>3#M6qJ{hi?>wW2{` z^-$2jl+Ok=P*~7`4PR6Qv8Mi*Eo9~HW~+%tW^DtK?V7f~HV{|sw9VbcQIV|0Neh*v zRvWTM=g=3hD6uD@3)Az0EhTF>VTUjpB8DsTEU?eDrBK8R6}hKlsZ{^$#l#~-RT?UR~vPl3paGbWLbs%^w&kJTO{HkFALgE(h1;*_D#tw_Ucml7LR zv7 zi=6c*5TrOO|BW z<6N}631^i!JwfbGDlcJJ!ih*%d(vJ2=O+186L7g?n5VOw_47e-cWV<{2bM3mRv>+3 z%Nhal1#ttmrRmr^QUmZc`|(N}cLgcCEt>OX;n;4=M?HK4v9_9!jlwx6xsEdL*@!3e zLOqb8?e{yY8+Jz;BzbPm0jUq`&$h6MIyI3aiq=^=#U~`8;cF~5@aSwi4!?R2A9{`+ zbT@ITj@)^T9mEd4rpJas#*S|bTY-K<+Y5$*_+5P(T5SdWWkBGabGBPwUf!8ufZ z1&avl-0~eSr;XmjBb0uHS$=868WauD^%dLgpD1+s{gt|-0FT>ee4z`MKes&Xx4wIvTk#)d9TgKs*3CDlT1J0gqRZnUxNYZe2iG4?DxO>+RbI0_`OS z8Y;K9Ya4#|eeVi6RPQm04!tVlA-fc8o%7ZP3?(NF^i&$f^j{sMN?l6L6XR$X7xJr( z`fEvk^^%WJj@Jqq+yfs)*;b$N9iAXr+!`9Ws-Ef5q_XT)>VmG}TCyg1OwWT0`m3%k zavVud9@8QvQaMN<{cuXC0VE?yOSdpAQgv;8;-TgryUN{;2e+xH-(qJG=k{wxSw=DK z&w_GYMFsFrqgH3b^FziHHK84hLfaV*aztsTBdw4~TcorG(|p%I5vlCayhtkaCi|2! z80a5chXctahJu@Gm7%{#GP3=sirQ-(6%_o6Y~q!y!hNyE&DqQ4TNJnClpT_kRg>Bs zE4pu)w^)Fm5y*zHj>46mS~6~960lV#Q<^wuhK>HfPoMj7X)AU=ns-vvzKY`802qCd?aXr79mJ{kCW1VoNEYBip1f}^*k;W&2vatLmHp7qC+bElw$VZyXFF#svx zJ8i1STz#>&e9^k0<~=V?V}LsxvR(Nlw^Be*B;Vxy`2J{#x zZW?~t^dQ#8#~AAoIkt&SXLvx|%8B({%z5{HOlj#zHAq}BLPTbRbc^m$lhV(w#az2$hddPU}c#g|pts5JdW9OUvyWf=IN# zjbY4-_w6&Y@&%|H)(y8K8Uv48Q7q#|gn|@YlI}(pm4$XkLmIivsgxAQxzR9H5fKwh zFIiDBS~!&n}e5cL;CqyAqj{qHJQsk(V49O3+Q zi>DWwndV`uYv5xK#IrUQLLAi!AQ-cfQ;N|@?OA=u0}+&ohx?AOj$m`yak(Oiv#i!OeoZIUkx+$Usu?>Dkq;DzIIa6 zjo!1UX(KP|q-?Pnz7<0=pmiVdqiEajZQz33;-Gb3^uK7nb_3avV=nDa5SbI^kswQ{ zqOCM4?Fq?$3y9NHlN#888YrxsQHcsEw2+r-FQIP8Eg;BPUzt~#Ne?$&Z49XY>RB&_ z*t3uim9BHPs`Kw}+KVntzYMp4I*tZ%8f$lozzV$y9nlvD{Ts}hvajX#YIAt-*rXr{ z@sE|;Dq5Vc&y^3YlvN>YCsv0ymBl@ob5ol<%*SnF7MCXx8Ol~C)Q??WOT|G&#)?RM z){iS~EM}QYt1x1hso_QlH8vZvqiRu0u)CDU(cbkHZqh?h+VfJEF^0)bNpE_hp=LL? z7L&5XN%RGk#2Y!{(##H_DqJiQnl~00OJA@eCXA5#mCmoiC>Kj#B}(+&^TSDs;2`C{ zr8bE-S$&u{DY!&H({a90es2%(J~E)xqS6nv93BU8`KWkjBb#<-wYqFzOe zo>)l`E59=wg}`n>wPI@lwW98>?7Oa2iY8NlA!l`vKBB)*iME_0H+Pv>I4h_w3je%D%*;R9k zhMx9wH9~ro+V=O|KBneG_sNj#-1!jIE5=7I8qRAQbPZIT4uoZcKZcp`lYM_Lb=+l;}FkGDJ!A(ZfE=r+l(b9rAlWqz-fPV^Bf& zq4E@3O8N1Is>YQ;PsY_y%^gP8)(7ns&Km_@BnQq^x6uZOiEG6ToWZ%AAJ{;+t4QPXih`$ks3xwS}b{@Rc z_x@%4gq(#bj|tr>f`yI@-8nG-H7PfNimd)Z*`h~`MMb*lyfcXr?c8r9N(Wc~jLwfWC|mXo;s%+iB6W{~%t$%i846C*X>(?nB89}oJ$l2_(@R6qBks7e7=#P8 z1X<tEi^WCp`ay1?m$;288ne{8Ayqu)7=iAA z@TclJMm8u!SsY{qOuW|meQbF9l$|<*B5AP zIIv|iUGZHpuO8xphMYL8rDX#dPcLop+@Fy$WA>*8V=e;K<|$&rtI|Cw@cf_}XHm78 z$nLiXvQc7h0vdgLBfo<0TFSleod-?b-1t?YMT@?fj=X6~LPAX*a{?RejjWXl0NZqe z0c!ddebP>}n;ojG(vVr`wIr;vde2~tSC`{mV}Bl}ewvHbD4(#6tg4p(+KQJO_&4`zUZ)rDdkQtXqXnNQ{* zzh+2Wo%MXZQmGgciP5`+WRHoD6-4uhmvO`mKMPT7{Pbkh5$lLJ_3od{XS57xx2K{e zQBIXWMsi0{Rv0u=yOl=uECB79_tz18jd0U|+0aNm?I)qtd|OX{oTDIz7@xBZcREqNUM21o)kt)p_wL4 zC3CF2hw8i!>%w6Q3wgMS;E!6|sm3w+ey!=ODA$QO0k~Bre}fz|!Go%78~rv0a`LnX z)XZ>bEaRlK;0-bTn*kK#&d|6ZzPz4Jyk}e~4)dUL1-Ms9#{P!c`109A$}IGtVi2$d zDz_24>B$l3Hx%?1vnQee*UA|&j+fX(2Mvmmy|F@94!6+__B?VxkKdZn1GcL%s=;hx zHk0@~)(~fQq-G4tfR-eZ!qcpc8N(bB6eQOruNv;T?5r;Wb5n~#Dza*a5Q;DQ5@|$M z3`CNw3kQ@39Z4swi3#cf*<73?6aETP(Bm?UBO;Od3pEL)4aFu3lsYhzB1j61WS}}W zeUhvg%R2(?IIHHz!#-2-wv0?{*H~)SQqm_P>tOOs%$ zPe9MwQXb5^yXpEKaLMPWnup{L=wk%~Dajl_0ws~P3vdOMk%q>%a=owg98Z6&ZBsc8 z$M8-EI$SN5)ro+bYukbJinm$Y`4DNwZKLN*vm9rYutyIzNoIqHEna1c_m-pvv{qxb z*h6_vkEa4!VR>3{NAA2cd%^Eucmq>v(dZ5aKAq~}Y7Pq8J+RPiKIBTcX89p%+6vZa zQgRwKz2_a@NLr)ri{#Y=26N?X=1N;knbVnJPOwtTgmNHM#xmLJeBHiD&s)3w!kQZ< zO;<&dH>v`AGROt&+pj~G%xaXGiZec!yi$!;8R*%oUlfKi(4BIKJ2LhLXfU44+lvkz z6~xIX9g$WcctliI{>+cm0Pd8MnFUoFLY0TroIxS>!7>G>q(60iNDbLOF=SF@)%JQ5 zZ_$o_x^w7Rc6|IXOWmVCK26cD;+^CL^hx-w3ug=fip>wb9#{QaWUnV`&V}ojlkc1h zc$af*(2oqI3!1^T^{^Zp?4I^StcMzHe&inpxfY{&YbxvZr(p927_(j%_1h@Y&PKTc zxAsC%soOW{3hg2d7VF{us^i^#*^MGfE3^T0rL^JEKQp0W;H8HF=rJLuJs})HJn?fh zpUFVq1Ot&fh|4z5pAk={3Z&`&q@$!`lZz8w^}W=C=59j-nrX?+{f=8(>*MApXcp^yiBdr8GXqer{+St20sad)oGYN%1VUPN8Ql)&rrP9)#neSI=9n!} z2LYWgcSuKO?zpgoS=qil7;f7vF_*2`8h z_bPWAD|ZV~(w(R_Gh^Ue4g)^RJY#L9z~2_$@O?<}4mYOxCbzO<0CN6*;+qcF!%`b+ zJ%B0orpG(`odS2CWy1H~A)^6cq02MqCeytIA@D6~Frq1Lf(RFDilbBhngzb`^IyQ= zf53^IPEr%msxC8XmlA_mZZU5+I5-2t(m-^8CioE21*k99=J5@Z4g1B z`iCH3yRn1z1vmyOQ8^-3amv}MXS!C7(<5)w_zzwTr()N9t=1hkdV0Tw-2x<5B=vP; zo5_SLSh@EMF4HAT<=pJ)x;GvDuZ5REU0vt_q%5L^z2)6e5}AV_R}$_$b$>(4=>+L$ zS;!l@LB)c!bNg~c4tXz$PS{qa?-^G*Giag)S&zHF_`o&oavk9Z4V#w)&h5b;mM^$q z$Cwz8x$8uEx12mh3m*20JnwCrab{6JldzKJO+MP|CNjx}(OqX$3R9Sl2=XOcvIxX+ zn2Mif7?S1_#Hq1WG(rE%Xj`w19PWq_4ws}4@_7ZR`Gvw1f~7cVg+yP^d}hSt(HTVj zcyP{MYu_{-bxg>mCzx$5f<^(q7lC{SWp zGa6DY@dRs5+&~7eMJwuaaXJdk9z~0edXBiXwTu9lU4cvn0dwfru1kdF9^K7}#lDpv zyoWgJ1P6@PWzoYA#27!zgftLj`aVBcm@7HOIM-eYcacim)i}0k=L2sd?WM7<1v-l zrdW9pm49jxV!!nO%T`P6ZwyjWL`R*kS0mSp&hx$^&?DEXbco*<$Y2&+iIkB|pVV0~ zQI%?YHOHbmyFunFkocjLjKCzBAf6%}6Fasr(ajMb(+z$WfLYd^vyOr!*H0#1Na3Tt zx{Qq+sH*beCk;qo|HS(_sr#{yx4*1ua@&lYDXHaC2a7075s#i3@0nqCrm8-3KKpgl zm4N(}PI#U=w7%|QxMG$?=yIR4A0d9PiFi$WJ#^2DJ9sL%ZZpdmmW7A*2L0dd%0Lv+ zvx5)-07U%ny7vFyuKpcc>GFWoRa{zoc1Y&(a0W_)$LI59PpB^$=@OS z`Kd_rnBkf5*nY@9*_!U+{+{yL0oSch#qOv6qzTn?jiaLrI=yF1`Mw^Z0_?=}hpVwW z`VHiob@7AIi}m?Qs4K?4bgo_D2H=< z;9ttnSf}M136f#H3ro$FNjFwoi_(19B8w$0PK`z8WhO_~A@Vb7%rf|NbIWm-s%@U) z^%ew7WPegmEv?OeH-;U`w(vrrZK?Q73hs5f%*{hU@ zS8{0Qjh9x`g(IwkM!cP)Z@3Nhvs3u+)1nbq*Ml5;(4faNf6fL8_LA&*+4IxfOYb?FuO2j2zU3Z_ft{4qBWBuK><6S zx-uY9pY`%wfZDzS#n=t(h^GpzDh%?FGx3=CloIYzTI~OBLN(-abn;^<^|8VX3Id4& zwUS(i)M-e#5{HKOX{jPmuad5acTzryr#a&%`7(c*0n-v!v3ENuKja&AO4ry}$0(w8 zqB3OUudLwZiWc5XBG#k3l$alorED-%$&pyss;c?f-e{-dgJ^6+ioXTZ4{*J){(!VQ zvyfKDNP(TTa^Jjc(20a+H+L7Ds#fb>_XF9BudXS{7?S(#Hg#@kLuSLz#d!rDr_$;{ z@*+h`FJRXy`4as-dtfTMB+k}q@$N%fkwxxN55?x-N2IvGJOY|p zTpGY)CcHtpT>4pLrL|R-w#I|4Alw|`@oWK^maL`>9G?`4g=1!}wzN2xxj7?2dC5x}X&MlWVUeDhd>F1sCGI7Rv_pC`Zm37} z)VK#zx7r21Vc0Tn8LC05vD#Ii)$(CWl$#ruL{ss6Olr?#HK=7$p0s5%x@n@JJaA0F zBRJb$ao8{8{Jx{p{&e~1cI{3#Z(l%-rJJ zm5Z@Uzc|iqDj=uvTw{JginZCVXX3*N)bOz)^|M1`Z59u*klBnRWIP>BeT_+=ts+N? zd{uoRfvNT?%)t@kS16RdqMU(0Ucp^vzsq+FPA?NuPgN+Z;iOW_bmiwjy>b?a>R5fLKhb+8z|Y!s$4%`Y z!0_AOEfuQ|(31lwM0nP>4XA+8@M7$M+QPi%LcKX2xX?@1lyr;PZYedWYRulask5P? zlV(ZFx>ws8Ilr^2qq8hCfFNBsGE$d;yr*o{4#Z-78W?tvKai>B=00hw?4D%IK3*1N zgfV8~)gF7Dh#g8)v=;BVL^0q2u6TYfjgqWEZu(5l?LxTB#L2 z>)&H`{$(;w%2?xHAo|qs+O`}+ zx@Dx}A7Z+R*S5Pm0!WUECgL@X(RP+CMNMPRmJ}`yVT8$jt{vC+aZKY^Z3Fm3aX95f zt`Kmdw(Larkf{9I{mistyjqvWP>jvyG72tKZNRufTB0V-v8L%Xmxdivk*amgIp}=~ zuAKfKwmrRtH<0&Gh!Wq?#o@~gu~mdKbq0U(-PK^5wnthU1x4cCWG0D~j?V1Zm~f zhw`LcdDRsaXF}m#y}BBgS!vTycoF zWklS~sO^?VgV(;BU_IAHJ_(P>8Y8;^YS7w^!WzziK(7~E+Y+ySLuB=sK! z^dI+s;37$RM0Sd}VzSXBBkCe0ff*%#nP;@13JmIY%iX+!!C~7x@HAA`HKOm@>l^$M z(WenPkQlT@DYvB=KH$6zxCpCyG7dW`M0DHkA71dc3`?Epw=P!czeu`uFS_G~=+-{G zEAI)0Pz4lf0I7B417yzz$OP1TvI4UGCIkEo2C+qg)Fp!4HAd{EA@d)p8LXhG8G z`RD_!rM40uG|Vqi71uXzgpsu2kDjF7k)uuZejc+J2F#Y8>>K@+(|a@c-Kh*t{_zv31@cwbNbn#i!JC4RP9 z4~{$h%r!G}1nH3Wo9}v89C1^D*tdj27(wjiVdwz5#?`edwefT~Bczouq zCt-(*xJO0Y`zM#BOXl7qK>3k$?1m?4PX>Z-hgVOVOvPXl91H<&MCiLm7z#-k$Rmut zARO3#hmB2ZKSy|PTRAEkLe3|FybmH5s4%FTNg!d{9lCEX7hW;+N0V&)7K;J9B?6&U zGVuVH0TC{2&Q(BhSB(K|CZg`OqI6JFpRJQt-&Jz(E*d>1W)Q@amUN$r0kTWqLe+SBI z-9-XwJ1+$?mISjn;62+F-wS-chpDnIyo6++gfd|ohQKw<@~cZ)+WhtnK*~q#o_B6q zx@j?q8WqG|80;d~s{tS)tO-{rwx4O;~UebrfXiiYV@?4{)fvVvmf6bs%ffHY7yH zLWJXNPsY790T-8bQ~0TveFI{@o*hZPa+o|GXQ*)>dHyX&FP0rgaA-!3cDh&9jMj`wTJYp2hs-tYifI{uKor76;Z}s^!Itrg{^l9CGAmfUK$K=rUPC;aRGMgSPS?wL zD~eF4ohx}UX&*!G5MXEIu+X(>{zwMMPLms zR=9=1uT%=$UhX@5%%K60aM3wxT)_%g?1>>Hkm%pLLU+U*Atkly+yT%-cy%J57%LGc zwh6A;*aOh5DxWACLlJ6xu8W}lf_)oiU=ReGqGf2n1aK~LK*Pgrsr%bGSoknSp|3E& zfyU#K=-8+bO{7Hbf60r6`D&RFaiao7|NbJeS=Hb{)7N*?)){p>J)1{S*aox?yL1ei z$$AZw;;MGeX@9}WXp%=5rHbN0Ks}%c%!~Gevsr(nT!DiJlhhF*xckTvwip?Km2(PY z_D;+e{&98cunlx08q6qvQLA3Wuyp()aF@8fQH*9*q>S6c?ou195I_}SFKi!ntkRwt zVw1&~3dEswj;p@AWpXRNGByL@Od^zw0^LwHFPY2$ECs9Kz+X(uit2~~``R!=7X_-M z*F*B1{g0X5**4mBjD*{8VVoLve~bYYSr(}EsV1f$DVNu(P^@WUs2G|^_s8riD5}N> zxp#DJ*3*0h!rA_wVT8Ku9VYZqA{8NlP{8$RTn`G!p)0Sd>cVf)rsSg-vphFRUD)%%NR)2x3>CNiI`# z&tV^qMwb<0M@o1Di zXLez6M#|j|8Dm`_+JexJlh)j=Ah#*nR((*8IaF{u1VS17)p7Jlm?9B0y#f(QUOt(Z zaVZR`^b7h3G;GL$dx&dm3JgF!mVYWhQrlGu>2mgk@f@Ec|fo+g+4PdTFF@Wax@A?PPZZ~HshbV&v zTt>9Y9S!)azS>e_l35IrS=NVB53X*M0;|#ctIk1o``w7c2qc4#c)9EmhP3BeSZ|Jm z{53BIk?WAu%m7G6zcPE=b3@?cao{>W+(^Mg0qW$CTyflRB%5S$`S_w$!jwJSweXO`I#&UCecFw?{L5a8dZeHD-@_sG5s8IZ zI^)RSL@M%utr8%IcZAfk$V2zY2uUU3d-u%pi8*0^_fHwZ#ubh@@|CMm8xGs_qq$`# z6Hc&%*?~DgRF2%!)hVl3$Ike*&+MUS;pll!K;N^Ba=$6Qrdi4z;DRPj(w0LYd{~!W z(aNoUOE}LPK*m+;OXoU{PCG+%+n=Z|#v}xei1!~5@7cxQvWvTBGP`FIbITy&l0p7k zO~JoaW?8S~x8{ny2t3@$`bis6j_n;SWI4{zwGbIEHKHLbPju2DTna~TNIg!=ii#&H zQy}$D;fdkB{b4uOl2GyiEli*PhB9SNQfEcTn{Ul9Rh5bsO{H%OM%R!`?L;NH=S-Zq zfK0idSF0XzbOSa{pej%4>EnLWXt=Q~4Mwne4z%=FDSwc#B)*DXO3)h5Og0uF?Tmx8 zG3>`$JB^acDH7R5KCI_ctJPkG-(P90Zk*0Tv4dIUaiqySm#)ieA2Op`pjmR!CpRK= ziL=Q^c0@P(nw9FJJ5PL5Ilb5v^gK|L&PHU3%Nj`bEkqADN5U2Hx-OXEt#8moc4FMG z4tD~Li%fW%DV#0`TzVnbfibrBJ_DNyuE zJ|$fV5cS289l7fYwg;~VF>N)?5!}5}q6jl}!U}i4~8jqrfHbIs#-X1wNUM zNVMCZ`;9Vqbmfo4%iO(_RQ%6i%pq1P!4C8NQExLmXsdtWkd5qfA7&*>#m>m6bnIsd zW|@62_n4T{9M8;Uz%-}JL7a*^5k_${nq)djDzvbCC{~{({(}Jl^=kV{! zRW(@#HR;^dG(>7x*bYD=xtp2a5jS8(DDbYqHx!+U;J5>)E z?cQN;4Is{9thGM`T+LaRt8C~+#FUA{>_@u!_j+^^QYCV3Iw<|vD8u}e{vt^M17(?t zL8c1ArYb|H@&R@7zxu+;K}yQWrsKw&gu`d78+0e} zl{_NsmB@z6)AT#jNuK|FVD)PA@r4&GvH&}5#m68%jI}2BViXQ~mfs}+Zt_D}TtLWg zGi)hpnpiO=RkPtX2p}6|0Y0QHwwl-$q!r-E>H{Zzwk827bAeO! z$_Hv@BjUP(Ro*SLmJRZxsJ~ZSsHq_^@wjpk7LfX(4_nSAyewux=AzP^WA&6~oR79XeBv12l|e(U=mgR`*48f)FVcWRRy3C{mA1 z<%Cf>hmevN)eq+!w`9&q9u7tE=>8psz0FWOXj8pV!o{>%rMXz+(|?h3`_AV6wE(*a zo`nSyxLNcQZ0-}0aXJ4b-DrESVLm0}EH)F`QS#GfBDNRYhNm)O?Z_2n2g=lZDHG`d zko2fhaDa`2mPPQ9-(KnN$8|cO8bxI8nLjLhEVu|FEG@Q}%B@_J1l|fpgZRtJN$V~# zR0Zw%+guh*2(~_19<)=ry&w+>+>Fjdb;i1bs1KW{B zD|Gb2+9N%;j!czQcG;W2aLj4EE31ihTu`P}R1Vyoe&H&v#KCEVkS)1?if{R%Ek(IX zTlHdH~}k(Mvw==-lzKPzesIy#k2fC}hFHtcTYA3v% z(#FzV2)w~*5$`My-4pSsdgq0VUbPE>Iw>B_@sx*@Be(~a&mK`$i_qLls|9D2B@Te`Tyd5Xs~`!OZ;BZ}4v}9- zBgYnw9WBX2vHVwerT@SX+OBun7PK6ySujo3!QMk|SJX%j4=gQm6Pl2+ z!6Bpa;3D2jhz<%=M^Y%){T5w~v6KBbnHEIU8&pBz7eJxmC_LFD+RD|octsZ#j@@YP z$GK&}mpcLSjKqTr6Ms*b@M?y!+s=%l)h5rudbsB_z`Up;o;z?CGbfLn|`duN!Dlggu^TO86)@5|I^x60N0UhS<05h z%q&?fSI3E(e~C1 z!oS8fE^uqL>=kWCC7yY<_dpz<658VRfSf-hY*J*>OP56Vv+HH?ah%+A0MEDnUbinD z^7^ve)^T_(N+3n0y%f|6btp!fUt5$Z{MAkTm@V=9w5&-M;=Se>YJwa;n^JcwnNCb! zxTbcWJz8^uV0UgbXH>dZntF(w2V`DlbSsvp+NZnzv}@{K62zT}lRM7dOr-eLz()<| zGMh*}u?{Hg>O0^N6-Ibb;Gn6y`rv0qJ{s}5bH*Fr8r^p2+N~#S?ncNUgsU$+ny#>y zdZ%tMLRN7YdQcoi#I#IVjPufl!RK-n$*YrhNud`QGe)_Cq047>W!|SN z=7!C1{spB2d!5HhXe?;Qi!jpcpRf_F_>hnyiAgfJrP-l+t|zE@GlQca%zLk?q$O@S zZcLB!Cwj;w#-mR`4nnPqI#5Gz2!#*Dz(NwU#IyopuXmW`w7emN=TQ#HY^poZ6OYWS z>pK!Yx?ovOf7L@+WjeZPicDxzSPLq=24>Iw9By=Db@BecO5N_5fgy(^hMtSX|U4(fF@D%C;14?&Juh^QXI%s4s3Z;MLvA+^j5<|hp z4mqfurM3yZD?Mt3Bf)1Yokyc}RsyDdF${jDPW0JObwMK?;eAd1BIPI)#0bi<$R=wK zFHaa(9KAAv8h1p7aZe<*Zji`j>2*Un8ia}|&3+UVMGKyr=VZMLb;;IiUy>P^=nH3p zBey^sHjguE$|E{9C>3M^WbE#qivH~uA+NK(I|i>6(*i=G09e<4n0%UoRh^kwqenTPh9s>(}RD@JirGa0w@FWFCwmgkf8iwIDJHpUHs_xNu8e#RczO z?>>fPY zinzWUJ@B=aa($O}gFjis2X;Z(pl-az1m3y@_N9{Lu9@Q~cb}l^KTGZb(-B=hFS;M! z#fm*Mlq91F7mO6l70R8;uZD_r!JjFN_l2t_KK3V}pa5rOX`j@+vqmY1oH+x7DL6V9 zH+iwHDREPYLtK{na4(_Y4p9&zUuc-eng*Bu3RTV}r2LsD<{7^4fh^|utG)U0mIgd&p@PDjr`Zi7&o#I#ASk^0x49&{21@2YnB$!}aoo$jo{2m9TsERm7@wugkL(hu~l= zsF|&l)^VWN%d!9Cde~SETUYwAl~3i)@5`877d_ABCY%@L$1`?=Y$H9H(38fLmQ2XL7M>H|@>|B=qe77cwPpqp z>e}Ewfb~6LfbIb-@reqixePj?*L0YWohK+p-AJevluwyG7Q2ZGE}_`F+}n9#0qIRN zGcSF8CZ|RX0bQ}#V|Fo;muNVKI1O9UH z+m=FbR3iKs02# zj@$AvX%TB2{8QK*)gVWjLFvl#;d=X`%%?>#B%Oc@KzIv`hpk71c6^K)6@%^5<6ghh zmI(VsFsIr@^MlO70j+3~MOBi$z{*aOYLmZtqFEZF9Er6 z9l}qn>L@8mG8OWW?0dhn8@4ad zo$5Ux90g+EP_WcT3o%%nAhvVR}L1>-kRF4fX}$ec^VJ{^QIsZe#(4$-wuMD z!a0DRGwv`ACd0z|J~J8N1c(3ujtcR7y)6MBUkcW=vV7FW;T_7SZ?t=UrrH%IATM6h zG=WhM(q{I>^CoV7%z%`vQ9k2Hw^T(w#<4&&j26b5_jZgsdM);JnY0-_LC7bpe3ceh z#<{7D+cWXafL>nP&gRXPr5bpRHq=X#4Q_JdAVG3(Q&5k zNN}wp@coVCSnpbtoDzmv89IwpCW0ep=NRfX3(J-tX+L)W7|88=oWQtf#40}!XVP1L z>rTI`&DY<{2mo+f@z-vT`&P!k+YL<5%F@z6&)&q*#NOpked~vLC!Y>)!kgLGw=dm4 zG3RkGvM{i;*ReOTviyxf$97}&l@k~k7#~>D5tv92cy;?p{rP${CTyDEIWHz>_V{>I z{(1YUt!;KYX0&EBM$}_q`g&!yEv7eS^oiXO7+Db5yR5f{|2eI<&EDfRk35T25ZEz` zR3zD@I1CaQopXPBJ_#=+i8l;Vz3V<-V5rzKnT6Z#Lv;BZV6mZ|zP7%$A;3GqEHY)Z z1#WU+$B#q3U*7$({Tk?h)l#`t!MyIhD!BMiplp@Wdiy}Ke^9R-EZ7yHE^U4 zJWnr>LY!epmf#n>=ls`MCY#k1sWsB7awtArBY-p)>oDg+XhvoiqD`Z2bgQ?9hOKK{ znOXp<%uHD{vn$rRuDsBN5eI`okpn)dmZ%?A;&q9(Qy25|n9iHtrDxOE7&RO$dmC{w zzqretW%5WU#GdfKLmpMfcJ~zFi zDvEU8Y)*8j7=vKQiU)g{&{bN|x%d#0(wF7b^KJsyKb<{f!9ZBSOc5FK(Iqch=9n4= z5G_Dg>%)^LtIc4qqkBu0EtkE;S`B{oWnR%pwzI(?hb(^L8khnZd19x0CjyeK#8fIZ zXfYT~fogA)w|N+gd4G~rRn)qo4?T=1Vk4jN_@3ykY0vL}Z`8kq>!X_Jb!>5c={8hsm@*3DzS;;DU8|frfA*; z2sO^C9_;ccnK&gTpcrj`iDSu1jvFx|dz#Xm6Q~ndAIN5<6)-A712?0M zv2@PMUI%CW>cDbAJLWc&+1nglS1(Djd&tN1_}FFXoE?pkoZR*VOAdK20+f@gY8=k? zU)vG@sI_KrZZbh8q-beP$tzcC8S5Ms>xM1U5iN>BC&FhH{Pz438Td^Vfrpd0&C;<{29-fBP1IRD-!KzB1n+-;+ zAr7OYa!*34(#f?u9{U>KUTkO{Q}B~42Bn%BoF?H6Ewz{RR<|)9kA<-YPhEwf#=Enu zf{z;3KGoI|K_*DE0b6rZ!>-thQTp{CYuY|)Vo?^=klh3)Zwzzu=u10CT?Zb5PpeQU zBa~1n6P76=_vX4f`hz-@W#eIo-%q0W+u{lMJ-w@yEt?1*z;jS=%~Y}5$_|NlXNB(V zu-4#!$fhU=l>%(0pTbz6Tpa0zjdPYBWv_#Gg5a}Z6jM}4LTc1vU2b5!qAjF%p-z*Y zjhHjIh(;dgAr(_-PcT8=IY2{|xEo(FCU2mOa-^OMvt>u$@hw+V`sj*T3@RqpRbp-K zLa(Uy(N+a^CxtVf$3%`Sm%WA(ovO%6x&9OS7BLQ_ua-1MxW5ZF&i)QzA|zG}9!3a? zA2@#vu*(;8#%-?ClCtc8Iie-l+Qz*eRQn6<_d1gYQWRgo*|B5!)rk;3No{RQsov6) zPl%}Q8b}r@Cxw`pXY*AXL_3ossxfiN;%vm?OhW_vJ<4breh%XF%>DQss8m7lO8=xtQ`dAHs`Hgn5g9j06LnEg?_r+eQA^UC02d}G{s66PeCW;H;V*M!X zT?Jx3TPwF^6u3EuoRAxms4q9Mj7jbB!2Lbaa#;_~=cML6q$9st>e0ah0|hgpqXQ60L@8*ADt*8TgJdrn}-aR2)K-WXW1 zyOFdIV+jdk1&^k(nosOxvJ-)KFD5k~=_L8WmuTvqSzSY<8+>u77&mQUDDffLPehO_ zxGA=bAk?a(nx?xIno@wODccywU$TqR$<_5XmgrLmr@v!Cd?hqFOu!&tDNn^=% zVNz*gS(NRLk;j6*@OCxJG3%~2X-O*{0lOcK@vhAin3oKz8%f8Uq4w4zt-hBt;hqNqjpD`@ll_DbMqvJhIdafu7K@C zd{2aZrwdv!UCxcd9fu26V`paFAGprsOTdN+#9R>)*F~8B^092KKZmWT3acbMP;4%o zhWmUsYgr-zkaQg;WdKqcGPG64VShrGqLmioR#12FodQxd!^X#Y+9enC@!eL z06R>-6E)O@Kh1WhH;GL?CHrR{+3h&zsRJgepjChk^st)lrU z1>9H~0|mNg4-dOc6*G!DkoCmB2P~b*`mT&047W@me@V4;t+#~kCE9Ar^g*qc*d-eg|ezO7YvB=9=Yo%%iJbIo}Ic}idClGu`w``6dRqFpuR z(fJsHhi4^-vklNpEpqhm&}8G(;Z6tzzfq9Qp~xT&XVnrMwLAVI^!jMl;?JKfPlDoJ zmb=$3Se9EJ6d2$w{g(rF*UFi!Wm*fJ&w)U6EG8bAh@??p3jL`rkV4rbOV=?&Vt39v z!{lBmme)Q3KP`1L#T38k)a>NY4?pEyHTfeL>rmt0ut(QN@^xMCBa^;cQ^nqZ6{-=* zz{4W4@>lm`Tb_7&?XvW6j%LTamIltwS^ZY>{4Ja!JE4n({LUPP-C=`a$MHiWIr(m? z4itq^BW{P3c~BALfL2=J^O>N6NwqN>px}4;I6WE4nq;Nw&YS7!sT@h`+T5KiRb}HX z(>^(z(keO&EkcZ9e&vk~k0s!v&F_5`CtHtrFb??Fk}Tg2j<-sA84z?8pzt<}Q=txd z_8FPfYuS|%!YeiriwO!ye!&*dKmBYE}I#l#>mv_4hA_I`|DDQXn(*e6#r*vb~x zcpg1(iM7OozlR_j6+9%C{hBF#^I5Ne(6i7R*)umC7)j;MR|8!2j_zChw039>$?`XE zUp1Rj)nF2iOzr9`@{7r;ndV^ObU@s%9k$8w*eaB-j0andLkY)GbBaySrRayHaxb#o zV(11_#xxcxn09$Bc}8XGnN2|X7sybBaF__9%;pf513S?z!z>#L<_g`a36g4QQnWB9 z;Lihc$pUhI8lj7G-ofqGfeS-r&V2D?@Y+Vgh^95J~@>YvTFrXrrJ~}~a32bJoR>Iyn*y2kfXF3rs-N4}vU*?9Ci3(=A1WIi8C**sMQKoN z6?g(GhatXP4fDp-%om{}-aOB4XkDQmN>j-)v$p|Xl~@VWL_^oF@ti&N(WG3V={eEH zJhw%WV4fv7g}gq^p*DvIb90{>f4EDuykpqOc|W^3ub?QCSutJ{XgH)sO*$_X zQBfQ_em$)IT?lD|6wZY&CcSM1hrnw?T#|WbCmUmReN!u9ZCe44w&_%&^f8KIB&}?w#y}#R3vo%)>isP^kvoJ`Clb(#?qY1RvDGtPE&c zN~BbIWUc{6vF%b{3KIiwHZigNQmZ^4mhYWDq>*^V10X>TvbOWg(G_&E&cW&LdB+KS zPUoe;j4`2&=ctFC1a;iyzYr=`1*33>dsvP0b)ALteD4BPbJ9570x+6A+M-C%PiMG) zRoOcpa(S@{k)g-{n<(eKgZScX(QX$r(V0b{151PfcG!irY-#Tt>)st?WbBOQwwHTx z_j@T@ySbXV0cvh+miaAaK8RTRGl6zcd0Txubb^X{M$^cyp7%|xgs|;(-EE`Y=W+pC zD5wES$*WpAoSxyK=dLN|6rz?6(Y144iu^ZFM2;J9M^)dVW6;zQN-nklwX<)RIZPE>^}O9k`1(wVjj;kfa;8hMIQyZJ z(wvgj&r(2ADSB-#?5J4`r6CXNfpEXO^Ncp>vkqlsm5Xzxz!RWiDU1pxC$E(I=+ZqH z_ytqKxfBgzW_H};z{cmhq7Losdyw$~u4(|Si-^ceIM1=xmw?D?R9)k#LGjw#WPYOY zhIP|&L7N6pC9{=H+tQM7MRiMsWSqBmmXnnupOvO>U4)JJsA#8KgXh}#uQzq9t*p&S z-Rb7<&AXaLX*@YX44H7bsz98qfbhTKR$-{YXg?i)*+}S2&pX`q4|T61LBMKSNv~m8 z17b`kSmUU34q>^dl}YNC+8~D5rD;-TZ_lW#zgJDIVmO4YJ$@5Tn}fM|SyQ$71D9^j z^c!=Et1*x@e-qvo1*^<$r8sk31HUq9eb&)sa7&Z^=6CD!c+OE{F>HBE&hTtlJc2r& z>dx(_gEA6%RCRn0-KnzA7jC=55fI<_3H*(bT+h ze$k1Oe(z~Wy{5)sH(LArDY5%+?zi|9nU5SVVTSC)Ta1aWPZ>QFkLf_SCc{HOw+t<$ zYpc)E3E-VauaUyXIw*Da1&ebBVBE& z*B~_ZA=5;Fm_tYKioUbZ~D5;L*Ln(StydY{NADfWfsXp!!|WeK77_=#aK7 z(L+EE%-MN+&I!W5P@)GLj=$5)lEuuExntq_ETnfXgdW;?*#u*P$5|l4JihqO_@j%- zr@gHil1#cxZ2H*oJQaGc;o_tR8PfDZLK7M>x=?R?k@TcmEn}uOSSw~jYcS0jbnP18 z_7a9K{yXM^T01tD>_G;qfb8aqkDG~4soprXNzWmVZK~iXxKwAT;x#P|Ri(5Xm^T;u z#9g2E1BFV=MVoolrfJID76+)kkczf!?9Z(QcR4~NwQ^e@&?`pUkclz{eHI@XYK=<2 z@~I>Er%U&azdYboQdd&G>SYr>Wh^Nr)G#3HVL@iHM2R?Jkzs6D1%V( zLsM)cw1aw>!1>fA<=%$g%O~2ZBZ=15AjGh;cuzGgRo5KD1~@mp-ZUhy;*Bzz8Zz0L z)Q%k+rY;JmE{>5JGO>*^HuRY@&|&BpDpA*xB`=~IHY6mM>lsoR80r;{9b3hWN;)Ks zk&`clt_@F|$>B;hEa0gQj8p_hGUi23h{sIW50W)A*tS8k@K=g+Rd}4deGfoS&A5CX zWkDr%-T`U!sWqp-nd^V3)cH_p@PUcWXHDmWRa6j`fjz=_ZfdocB~Oy20L5y*s5=|k zxyAy&Qz4JF0lW{{46I`h<|Z00YG-oqogbhOeyzhZ?uVV|70-%U{k5fVXt6m)rtHtW z(pAxR z5&h`f(JRmG%ji_x$qR%%A|UO8SmH^S7V`rM%xTx-6JlmN1`cq;hNtDdu&R2CK_ma; z^2S%Bi%0|iO@1xIiNQ|zyH|J z$XGV#g(+;Ys_2c0b2t0arpxlTZ2*GF`V${H@(pETj&f#oF;!6s+ww-pNpA*41%l&g zndxtgj&Xg7sj;4X5#mr#Xg%zxn&sZ@fsga>SmDRM3`U%HPR73Yd0rbq<~uD?Jp;w= zS0+gcQ3rlAx_y3kxX)*v7da`6Q#b->8pk6<=zxx$=XLD#;cS6Vt19&cL^JpE zCe#DG&_0>Fh|moWgRNJ;P!ux>rAF6yy#0YHB`If1oj zgrsrroabAHfkDyJODwpRQAlA`HPi(OEFS?!R$>Dz1Y6uQo9z!VP-Ah4vgqlsWC#hi z*@3otS;Ld5RCT08`E`@gvXn9%5o~$0$@q@=(ZkUCnN$8PtfyNdL{VK>6No?yZD+)< z-4U}MR5$LpXl0Ihnvr>X)n#n?}s4*KXgZy@>4RfnG@Kgg}_L$!E9fRhtliq@qyJdZjvt$RTN+ z>c(y4q18X3u$17BOQ%l??c`E%fs|CFWj@lVI`k;d0UDH`7?#jc2i7|UABHC#`qm$k zn@fAx4`=Lz|`bA@p;;DBhVUXx$ zV-FJ|(6Q{c@(#G#!A@`P8+xY}N)6>o<-t~@RXF0U_)Nuoj|v=5-~!tVSlqxIu)zZq z{pP|veT`p0X~Y%b;DoZrj=G^vJvzVZ4Rzdm3GlY5n?TrJ1%TVWFVC{{LgbZ?ZC_LZ z3A8SUH+ug^=s=}#?h5pwjZGF3>3qm-62uZ35=A{4+3+4z23(1!enK9!LO%8JS{DB{ zi6c#?>s@QN6nsy#EIn7gws<>uuyofzH#GGn<+@bSv-m-u{v;SXT>>yoB8+Yo*7Dj+ zc73yvLJ5K!7!^pCP8~TnP%9+h+vz4*6cJ=BWH@t3%dIp)@R-|eTj-sHrCr%tu-KCo zec#w|`b+uo76%vK8CckvPdj@lHeP{l1PHdq<}nN~jVo4#7vkwNj_GhFMk(=S=8o{y zFAvF>rFO)P-=kHAhLJnTiil4jYzZ2uir=+OTpfF?g?dWvXl?oEyvt1%Ww{kE=#fnv z#@%MM5GSZA-Do@bsEa~P^2v1b_P+1)B zEH}6Ap%B_x%Icn-!7w&eZ0L` z)rD{;7n5ML4sFuRws^jXbPKV2eTp3}G2welik-SBOt8u(#A5v-6qFl2Wn*hVsTVcI`cgK26w7fQp->jag9t5QhH!n^4(vM#6H zcS?-Dwr6yb@%ShZ_RPoLd*0&8k6;>(oRCv~+N!1#Q!_XoHhe~mk2bO`laKinTV$@2 znMHgeX;1OYvEFrW!7ez9Ek~BmUUXd6vT5+{(zx0py{WO(ySD7vGI3pw!Jeq9w6? zUNKA#Mmxn7cCRFmT8ksh{}7H`Au>nQg0Fokf}sTLXbzgbq|mzkfwQ3qXWj9J{#2>; zf@3bsXpXZj&ofAA*7J#%Bk=3om9r4m7p>XQGH11mVAjE5Cs3`D9{xjvVR^26^BZsn z1+K#PHxS?@N+x~}gz+U>Q~EcoWb0PO9S>L(ik*c~uD*DOFtb_T^aUX>txCp3Tmo0? zCG{$=>P!2$0PY4omS6m^YfYcNM{?YZ*(|kXgH7INSV)fpEV1_uQr?EwB(X#P)h+cD zsV^fi<6E4jlLcQTlWF^R9HE5On8ZES0S82^F+Jv3HRZWZK}%0cCKvvViMksP;4%9r z``WESBjkf1k&0>6xc1`$d|mGXAjr z79Tg3enq8vBzz*R&v76yN5axMb&INJ4)K8>;Q2%~j^~&iQ`@fVk9*1fKR+U6grjF z;`auj;W05*NS-*NsC&M%q7{csoXr{TVRlM!h30b#e$vA>2tA<~Gxr%dBO4>XHag9Z zdMS_EfgS=nhvZ1{&6lkC(#2=Ku`ObIawvAK&&+`qjE&YkDxy)vx*|)uZ3<5Kz^U|l zSmg$2cbEWmhg#H2dVl;LOsl79^(UdI3{NduX2Ij4jzq``!wIRAo%#y*GkZE2h_79Q`Uy6Z@e; z;hxUjs6iZpxX`Eq&V8WkN`kPRbOGy9f;5~oqUX&taV!0cae9!s?zt%GCSh4Gf}}|3 zxkU@868PaX^uwrPGhsIN-Za78f@pnVq;*S4qDNsS_Q^D{6^w>cQ2pjDeI$j;;my}g zG?5v!yE9k)?^(^&`76;w_Z!9cB(~sP2b4w6&~P6Xlm#;}knCrRz7!sW#c795!Qt#7&{Q=oYPmDs({zFnht>)cp7UiG>Yjqg=Eg+ zPIb1_a_zQ=rMKdsv*(g`|&siS!A=zSA_PH^kx^s9B^Cj9- zLs-nhLpBPcLO;<^kyAv8Y8@4sg}r=h$zX0UtAD6mCiWy_zS$vkukNQ~H253w66 ztYHy~2Bng77yxL85nKMEHbuCH%4&P+0J&%m9;p{H-lIuA$;s``w@xywItlS@erTz` zw{qOtTD@~-yHk()+JU=c?K7gvE*PYgz5^9DpVEs%Erf}V@oM3;9>$t)xD8)7>J}0w za^iPl^DvFw2u2HQvfR>{+zi!;W5gA4WCkGoc;Tb3S%Ua8O~BZCbeG|0U#!#vPy3-r z=q<(Fwo-TH;AMLV)l4sGR-#jQ(+&u_eau1nalOr@zYEr-%%6XFmU&-3_lRJGZ?*G~ zd1=``UK6*-;orpFvPlBotLS&Mr%#z2 zyQ2igkXkq2a$_EFj*(b`W;Bt~VN689kUElxs9zMfv;|w;#T}ief^_sA)JW^e;{EhSRO2R zp14Z^$aCRw#6Ih_YD3G3`hPbnq|R}6Bck@?$zfcGk{(++5JX3~wYS!)0e=ZIt z=!E5?#Wll?O_#PM-pHaJ7|os~)S)4YQzIW~GMe`!o|t^5re>kYN$IHXoXy2|`hkvM ze6KL^NH)fL4j%b>ECZ{S@j>;{1E#Jl{wvxtH7*=@E4Jt1s)zRo$iR&Z4*K(v5pBylHI35ZJ`68oY8o|=hIr6f13@a_Kj4*YL`ljwc zx@4cb1atYXL$5e?WtGOPL+K(QKN}W*Ma`JgFN7^F4p)iGjJM*LzO=!Mp2FDMV^qFJ z#wigBa~3R?)Vk!jM{eA9!!};ry#-=uqGY?DNotl|Z+YT;_jPAtcKx#G-H@LzLS`@A zg5|rm5ns&nw7ZY*j;@Bp#2M$2%Ak#^SikdlyC6TLJW~%IN9uY;FhS4^k#|GVd0mZt z+)ehi4rckFQ;RycE6qQqQ^XXg*Us`@DH-1;+y%NJDc^Yk(|=%eYQZw1?{>__c?dJI zXZJ&!Zo$Bv-5omYS8j$xKB+yWytM%>ooORWcjVoQ=79WgT`#OcDLtH$dJF?m{O@S zDbhslnx!SA*+yLE-PmM*G0MVy8JbRc$&q~31%063b;K5dnNDebOrmwI-*DK-7SjwA%ia%A z@_uf^>;ucf9dK-?j}78tj6fdpVZxm}$-N0Ing4B59s@6xlx2;_!Rn9$Np$b_x%Ef| zrVyvh_}BeshMLZui7jGO>)ycps0)*%ypXe$+`f^jjH zb#W;sh=eaU(4UQ`Woi>hY!DA`(_Fle)=g@LRq`3&b-H(#n zv!aVr6%l$M$x-bA9frK&HgSo$8rA~m1jaWo3uDN}uOv@2ZbH1)NoC3+PthwlF zBk54^bsn&gp6SzWA+J-6=Ss6S5Di`6w-l)hx$g;7j z*-LjcVTVE3%J~+0Ht@%8-Uyx7cZ{DZ&GoA{vj75n8zBwQAq7B0@=$HD(1M&|Wu*9t z+xB9&ePkAPqG#((mV)&9`iZbr7IaA{uD91|GNP8evywvQRQF!bQ;;*h>AMazhg-y9 z9cw3cf%oQ{f($2Fnx#)*<>|zFFvRIyesl415v`MN&#aTIweFNiKDu)lh5T;A9L#

M#^yV`-}H(bkm1nbrK})#*0ym$g%Ok=7GYFP>V~5)NjHF0#p70=|T)9!DN-> zvd?@wGZY%b^tY$RqT|N8#!0tg$EhT!<%|UMEf8ub;v`Y4H!=}|r@P-Zbv}wr>vr9vo@X(XU%aCwKlUVHOYi%yX*hTaj$?yv! zx&z?&FR*20;g6jjn>y|~o^4&7U~%IJ8K6S8Q>VH`N7=wRdyn3m6E&e2nhf)iCh&5) z`QmNd>nU#giu8_Dp&rV8K5z@p6#V8vO^C;5LW<0*NvjU|O2s@(^mORdFrhtw7t;J) zMM_wLB-<<++@%M$Vcw&!<>`A;R|}zj5C5EZIJ)nmX(XApG4leqNH!LTK^&?nU-YF6 z4UBL%47}2k0=v)FwMhm%;S8kAxs`9u7 z1N5>1z#D+&ws(I@boApnPH#8Q)&0iBF9Q4DM&nP(jua%M)f5n4C|zKMwY++BJ|e=_ z6N>N+SLT24ljb$%tMV&AR@)$SA%Nlap`tP_-3m*UG+MD2Q!+OvR-Mj*=e$#wX^aB?Op&2bvk2VS!>>N-)l{@dVRSx0{~QJ$PLJ*N?5!mpvO-R zbYJoB`AmtGtC;C=C=V1A5l0qFz2JK^+^{J^df;b-+agI4 zVv^%q2<<=TrbkLqj)nkPpef|{DYM{(Lfe@~kZdUk)av5a+3H4Ie$gL)nwz+Bq(}fu z0b_RBl%x?YKTecB|FVFVh7d`{K76^`#2ZYbZ8jG3AqJA}@cz^QE!;TAGb}HGLwg`o z7G7oQh&J*te#0v^NA@tKYx1*s`)h6$O~!Uv7(g;YRbVd-2w|FkhT7w{PB$7cnx zv@qOp?%W(920o>0H8_gWU_X3X%^sI&X&+Q8FBnnSnf+r} zebPOwC+H+&=DKb^(X(?rs3-5rU7y7NS+eU9wVes2sMUsC`@M~Gb;MU2jMzH>PIo_( zm9D;!jx*#(Y@&RrNq*EmCwSfhPGShVnd? zdQ+5mtND2zm{Xd(h%4UkgO2N)%-Flj{-s--5*xVF#gXovv&X7m>Xiy@T(4=f_|z6Y5SKz)pOBt}0SNX8H9F=5EC& z@%y_iS8y&FMb2)@ryv9LoM6-YcUI9-@Ol<}&s_47<;jR+qoA=XkL1ut2doum7@OlO zdAfHkm%r#J)`K{URNh7?+qV&l@qaZ!{bdRi{a<60z*!7&@YP2@C%z%RZW&cr_z)kG z9BD80$@28RZ@tx1u2_RcGPpA5@7xh}kCj7&eW#C;(N>%3S|6XU?t!vGbKx0LStXen zG>Y377%j?s_3``<{CGZMQW(ijjCmeI+@SUk!;6MC!e2s;(PY}p?0K7gTh@!Am47~0 z&_@qA)z(-O1MZ2fXuK}6GY~6pN>QVYq0gxeIH1M^t0`*^>2wA}J7DV)g4}ymyCyk^ zT*+&F!TbbwV9?s7=k)3QSUn_Qz`XViR#EZB#r|$&7zYhvrT)ho2ENnnS>3@~<4$nf z1)3^q)qD90%`PahKt&uK#p-k_qQ|`x3@8|V; zHXr3FVSV~#0Aq2}g^zd(*0;A{{iCVsZ!r?FG_?9-sw(#WZK|4^V3Kg3>~X6sNCC=0 z+7Tq94Wz;wS$mWxcVuo}E6Y_10tf!^?zw1x99>vyekA?T;gsd})YG%w1E5HoDFU>| zdkd;8WA^z=gZW8lAP6b>fSf3^Ds;YQHEZ|R&3o}x7n@#G#nbyJHA*Fpqv?)9%7A;F zg>Gq+v9x(e^$jgIZ?3l#yh#YF8P2-Gjom}u2^9|GIK?r>BN>~l=Rm$s8s%e#rqbtp zmyNqb2SxM2jr8jiM8?kE(-y^;HiXCAEgHa%f=BatF)icPZMUx;w``Er0bh|s_@hO1 zz*GYEzj+czu=5$VY?FRIoXHW{juP?GVen^04r@&4#@rq2%7aJg`a<;HveiD%e8jG4 zd832=LW;KFF=pJ2S?%arYz8jI0{$cj!Nf4HFdrWut~C!|v{NQd6P^#7?sAZ5F!H0w zH#}LLjH#Fr{TaknSUg^5BMRHyJ#=%|ChH!SrDxT!c z4`C7|xYv-)*$dFsY-`eD@7^NFsQmYJ@w=Bl-2G=) zKLYn_me8NB5@G+TtDn>8UkmbUYQ>-EUl9H&`tNHkzheKI!SE+`{aXwCXO;BZ2d!a`IqMNORADz@qbM~@e{x5t+oE$o&OvDPjT|!RnLz?{F;s6rxQ@3 z|Fx4}>*?<$-oLr{(an%W{`7?UUGV*Tm-=_(ACm8%#(e*7{8lIbvjF@*;!{QuqAzXalcZbd&d^SQsMiGTVe75}%|{|6QLmjeFq`BPi` z)Aiqre1Fz0)SIsP?;7x5)R8~oev?=JD*F9d67hcm_wN$mUmg4^xBcmW?rr4yAMf|a zy6?}0n_t0y6^H%=-<0}?;D3^e{)+zVBK1%7CcA%#{?{ezUmg6qPWsb9g2VsH!LQGV z-(vggQrS=VOQ(MT|2GR~zZT%v<)5D(yxzq2|6_3f@bLGe*pCIKUtgwvdWiG<-+1`* otJSZi`1Kv&rw<;lf8gWSHv(xfAmASm*l!=dx2Y($&yRoq2a~uJjsO4v diff --git a/target/dynamic-connectivity-0.1.1.jar b/target/dynamic-connectivity-0.1.1.jar deleted file mode 100644 index fed9bc68fee9b8ab157321333c948734b3932b1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16831 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 hz%L(-o`1u~?@t8MVqoAuA+XIPahT{ePQRI*0%O literal 0 HcmV?d00001 diff --git a/target/dynamic-connectivity-0.1.2.jar b/target/dynamic-connectivity-0.1.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..c82ff9e749bbe93e4343bd6519299a411f20b9c5 GIT binary patch literal 16934 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 Date: Mon, 18 Mar 2019 13:21:26 -0400 Subject: [PATCH 05/29] Fixed size calculation in optimizeForestEdges() This fixes the code in ConnGraph.optimizeForestEdges() that computes the resulting size after combining two Euler tour trees into one. Technically, the old behavior was still correct, but only by accident; this change is still kind of a bug fix. --- pom.xml | 2 +- .../btrekkie/connectivity/ConnGraph.java | 13 +++++++------ target/dynamic-connectivity-0.1.2.jar | Bin 16934 -> 0 bytes ...nectivity-0.1.3-jar-with-dependencies.jar} | Bin 36143 -> 36152 bytes target/dynamic-connectivity-0.1.3.jar | Bin 0 -> 16943 bytes 5 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 target/dynamic-connectivity-0.1.2.jar rename target/{dynamic-connectivity-0.1.2-jar-with-dependencies.jar => dynamic-connectivity-0.1.3-jar-with-dependencies.jar} (66%) create mode 100644 target/dynamic-connectivity-0.1.3.jar diff --git a/pom.xml b/pom.xml index 61902a166..8c90f5273 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.btrekkie.connectivity dynamic-connectivity - 0.1.2 + 0.1.3 dynamic-connectivity Data structure for dynamic connectivity in undirected graphs diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index 2150dd9fb..d9225011e 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -1073,20 +1073,21 @@ public class ConnGraph { EulerTourVertex lowerVertex1 = vertex; EulerTourVertex lowerVertex2 = edge.vertex2; for (int lowerLevel = level - 1; lowerLevel > 0; lowerLevel--) { - int size = 0; + // Compute the total size if we combine the Euler tour trees + int combinedSize = 1; if (lowerVertex1.lowerVertex != null) { - size = lowerVertex1.lowerVertex.arbitraryVisit.root().size; + combinedSize += lowerVertex1.lowerVertex.arbitraryVisit.root().size; } else { - size = 1; + combinedSize++; } if (lowerVertex2.lowerVertex != null) { - size += lowerVertex2.lowerVertex.arbitraryVisit.root().size; + combinedSize += lowerVertex2.lowerVertex.arbitraryVisit.root().size; } else { - size++; + combinedSize++; } // X EulerTourVertices = (2 * X - 1) EulerTourNodes - if (size > 2 * (1 << lowerLevel) - 1) { + if (combinedSize > 2 * (1 << lowerLevel) - 1) { break; } diff --git a/target/dynamic-connectivity-0.1.2.jar b/target/dynamic-connectivity-0.1.2.jar deleted file mode 100644 index c82ff9e749bbe93e4343bd6519299a411f20b9c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16934 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 F^gpjmQfL4G delta 10182 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 diff --git a/target/dynamic-connectivity-0.1.3.jar b/target/dynamic-connectivity-0.1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..cf2ff3a839315282b61eb2acb68df503a457b8d6 GIT binary patch literal 16943 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 Date: Sat, 23 Mar 2019 19:56:42 -0400 Subject: [PATCH 06/29] Added optimization_ideas.txt This adds optimization_ideas.txt, a text file containing my thoughts on how to make ConnGraph faster. --- README.md | 2 + optimization_ideas.txt | 114 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 optimization_ideas.txt diff --git a/README.md b/README.md index 8a334d363..e6bd52389 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ Javadoc documentation. assign an augmentation to each vertex. * Careful attention has been paid to the asymptotic running time of each method. However, beyond this, no special effort has been made to optimize performance. + (A big obstacle to optimizing `ConnGraph` is a lack of access to samples from + real-world usage.) # Example usage ```java diff --git a/optimization_ideas.txt b/optimization_ideas.txt new file mode 100644 index 000000000..af60f3573 --- /dev/null +++ b/optimization_ideas.txt @@ -0,0 +1,114 @@ +Thoughts concerning optimization: + +- I should not optimize until I have access to real-world input samples. Any + optimizations are bound to result in increased code complexity, which is not + worth it unless I can demonstrate a non-trivial reduction in running time in a + real-world setting. +- I should profile the program and see if there are any quick wins. +- Most obviously, I could implement the optimization of using a B-tree in the + top level, and see what effect this has on performance. It would definitely + improve the query time, and hopefully it wouldn't affect the update time too + much. +- If I do implement the B-tree optimization, I should also store a binary search + tree representation of the same forest in the top level, for the user-defined + augmentation. That way, updates will require O(log N) calls to + Augmentation.combine, rather than O(log^2 N / log log N) calls. We have no + control over how long Augmentation.combine takes, so we should minimize calls + to that method. It will take a bit of care to ensure that fetching the + augmentation for a connected component takes O(log N / log log N) time, + however. +- Alternatively, in each B-tree node, we could store a binary search tree that + combines the augmentation information for the items in that node. This might + even benefit ConnGraphs that do not have user-supplied augmentation. +- If I don't implement the B-tree optimization, there are a couple of small + optimizations I could try. First, I could move the field + EulerTourNode.augmentationFunc to EulerTourVertex, in order to save a little + bit of space. Second, I could create EulerTourNode and EulerTourVertex + subclasses specifically for the top level, and offload the fields related to + user augmentation to those subclasses. These changes seem inelegant, but it + might be worth checking the effect they have on performance. +- I looked at the heuristics recommended in + http://people.csail.mit.edu/karger/Papers/impconn.pdf (Iyer, et al. (2001): An + Experimental Study of Poly-Logarithmic Fully-Dynamic Connectivity Algorithms). + The first heuristic is: before pushing down all of the same-level forest + edges, which is an expensive operation, sample O(log N) same-level non-forest + edges to see if we can get lucky and find a replacement edge without pushing + anything. The second heuristic is to refrain from pushing edges in + sufficiently small components. + + The first heuristic seems reasonable. However, I don't get the second + heuristic. The concept seems to be basically the same as the first - don't + push down any edges if we can cheaply find a replacement edge. However, the + execution is cruder. Rather than limiting the number of edges to search, which + is closely related to the cost of the search, the second heuristic is based on + the number of vertices, which is not as closely related. +- I have a few ideas for implementing this first heuristic, which could be + attempted and their effects on performance measured. The first is that to + sample the graph edges in a semi-random order, I could augment each Euler tour + node with the sum across all canonical visits to vertices of the number of + adjacent same-level graph edges. Then, to obtain a sample, we select each + vertex with a probability proportional to this adjacency number. This is + fairly straightforward: at each node, we decide to go left, go right, or use + the current vertex in proportion to the augmentation. + + This is not exactly random, because after we select a vertex, we choose an + arbitrary adjacent edge. However, it seems close enough, and it's not easy to + do better. +- After sampling an edge, we should remove it from the adjacency list, to avoid + repeatedly sampling the same edge. We should then store it in an array of + edges we sampled, so that later, we can either re-add the edges to the + adjacency lists or push all of them down as necessary. +- If the sampling budget (i.e. the number of edges we intend to sample) is at + least the number of same-level graph edges, then we should forgo pushing down + any edges, regardless of whether we find a replacement edge. +- We don't need to sample edges from the smaller of the two post-cut trees. We + can sample them from the larger one if it has fewer same-level graph edges. + This increases our chances of finding a replacement edge if there is one. As + long as we don't push down any forest edges in the larger tree, we're safe. +- With an extra augmentation, we can determine whether there is probably a + replacement edge. This helps us because if there is probably no replacement + edge, then we can save some time by skipping edge sampling entirely. (If the + sampling budget is at least the number of same-level graph edges, then we + should also refrain from pushing down any edges, as in a previously mentioned + optimization.) + + Assign each ConnEdge a random integer ID. Store the XOR of the IDs of all + adjacent same-level graph edges in each of the vertices. Augment the Euler + tour trees with the XOR of those values for all canonical visits to vertices. + The XOR stored in a post-cut tree's root node is equal to the XOR of all of + the replacement edges' IDs, because each non-replacement edge is in two + adjacency lists and cancels itself out, while each replacement edge is in one + adjacency list. Thus, the XOR is 0 if there is no replacement edge, and it is + non-zero with probability 1 - 1 / 2^32 if there is at least one replacement + edge. +- If one of the post-cut trees has a same-level forest edge and the other does + not, and the difference in the number of same-level graph edges is not that + large, we should favor the one that does not, because it's expensive to push + forest edges. Also, there's no need to sample if we pick a tree that has no + same-level forest edges. +- I wonder if there's a good way to estimate the cost of pushing down a given + set of forest edges. For example, is there a strong correlation between the + number of forest edges and the cost of pushing them? We could use a larger + sampling budget the greater this cost is. +- During sampling, it might help to push down edges whose endpoints are + connected in the next-lower level, and to not count them against the sampling + budget. By paying for some of the edges, we're able to sample more edges, so + we're less likely to have to push down the forest edges. + + The downside is that checking whether the endpoints are in the same connected + component takes O(log N) time. To mitigate this, we should refrain from + attempting to push down edges until necessary. That is, we should spend the + entire sampling budget first, then search the edges we sampled for an edge + that we can push down. After finding one such edge, we should sample another + edge, then search for another edge to push down, etc. +- When we are done sampling edges, it might still be beneficial to iterate over + the rest of the same-level graph edges in a (semi-)random order. This does + increase the amount of time that iteration takes. However, using a random + order could be helpful if the sequence of updates has some sort of pattern + affecting the location of replacement edges. For example, if there is a + tendency to put replacement edges near the end of the Euler tour, or to + cluster replacement edges so that they are close to each other in the Euler + tour, then random iteration should tend to locate a replacement edge faster + than in-order iteration. (If we know from a previously mentioned optimization + that there is probably no replacement edge, the we shouldn't bother to iterate + over the edges in random order.) From 1069dc9700915389037c2d3be446b22a9b6d49a4 Mon Sep 17 00:00:00 2001 From: William Jacobs Date: Sat, 30 May 2020 10:36:45 -0400 Subject: [PATCH 07/29] Increased minimum Java version and updated documentation This increases the minimum Java version to 7.0, because Maven doesn't like Java 6.0, and it makes assorted improvements to the documentation. --- .classpath | 4 +--- .settings/org.eclipse.jdt.core.prefs | 6 +++--- README.md | 6 +++++- optimization_ideas.txt | 4 ++-- pom.xml | 6 +++--- .../btrekkie/connectivity/ConnGraph.java | 4 ++-- .../btrekkie/connectivity/ConnVertex.java | 5 +---- target/dynamic-connectivity-0.1.3.jar | Bin 16943 -> 0 bytes ...nectivity-0.2.0-jar-with-dependencies.jar} | Bin 36152 -> 36161 bytes target/dynamic-connectivity-0.2.0.jar | Bin 0 -> 16947 bytes 10 files changed, 17 insertions(+), 18 deletions(-) delete mode 100644 target/dynamic-connectivity-0.1.3.jar rename target/{dynamic-connectivity-0.1.3-jar-with-dependencies.jar => dynamic-connectivity-0.2.0-jar-with-dependencies.jar} (56%) create mode 100644 target/dynamic-connectivity-0.2.0.jar diff --git a/.classpath b/.classpath index e912f9f2b..e43402fa4 100644 --- a/.classpath +++ b/.classpath @@ -13,18 +13,16 @@ - - - + diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 552c39646..782ba92b7 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.compliance=1.7 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -11,4 +11,4 @@ org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.6 +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/README.md b/README.md index e6bd52389..cf506d951 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Javadoc documentation. the amount of gold the player can access, or the strongest monster that can reach him. Retrieving the combined augmentation for a connected component takes O(log N) time with high probability. -* Compatible with Java 6.0 and above. +* Compatible with Java 7.0 and above. # Limitations * `ConnGraph` does not directly support augmenting edges. However, this can be @@ -49,3 +49,7 @@ graph.connected(vertex1, vertex3); // Returns true graph.removeEdge(vertex1, vertex2); graph.connected(vertex1, vertex3); // Returns false ``` + +# Documentation +See for API +documentation. diff --git a/optimization_ideas.txt b/optimization_ideas.txt index af60f3573..75bb73110 100644 --- a/optimization_ideas.txt +++ b/optimization_ideas.txt @@ -110,5 +110,5 @@ Thoughts concerning optimization: cluster replacement edges so that they are close to each other in the Euler tour, then random iteration should tend to locate a replacement edge faster than in-order iteration. (If we know from a previously mentioned optimization - that there is probably no replacement edge, the we shouldn't bother to iterate - over the edges in random order.) + that there is probably no replacement edge, then we shouldn't bother to + iterate over the edges in random order.) diff --git a/pom.xml b/pom.xml index 8c90f5273..75d78e64b 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.github.btrekkie.connectivity dynamic-connectivity - 0.1.3 + 0.2.0 dynamic-connectivity Data structure for dynamic connectivity in undirected graphs @@ -27,8 +27,8 @@ maven-compiler-plugin 3.8.0 - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index d9225011e..5b9aa2ea1 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -61,7 +61,7 @@ import java.util.Map; * vertices' first and last visits. Instead, we have each vertex store a reference to an arbitrary visit to that vertex. * We also maintain edge objects for each of the edges in the Euler tours. Each such edge stores a pointer to the two * visits that precede the traversal of the edge in the Euler tour. These do not change when we perform a reroot. The - * remove edge operation then requires a pointer to the edge object, rather than a pointer to the vertices. Given the + * remove edge operation then requires a pointer to the edge object, rather than pointers to the vertices. Given the * edge object, we can splice out the range of nodes between the two visits that precede the edge. * * Rather than explicitly giving each edge a level number, the level numbers are implicit through links from each level @@ -898,7 +898,7 @@ public class ConnGraph { /** * Returns whether the specified vertices are connected - whether there is a path between them. Returns true if - * vertex1 == vertex2. This method takes O(lg N) time with high probability. + * vertex1 == vertex2. This method takes O(log N) time with high probability. */ public boolean connected(ConnVertex vertex1, ConnVertex vertex2) { if (vertex1 == vertex2) { diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java index f32860958..41d30c836 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java @@ -4,10 +4,7 @@ import java.util.Random; /** A vertex in a ConnGraph. See the comments for ConnGraph. */ public class ConnVertex { - /** - * The thread-local random number generator we use by default to set the "hash" field. We could use - * ThreadLocalRandom instead, but ThreadLocalRandom isn't available in Java 6.0 and below. - */ + /** The thread-local random number generator we use by default to set the "hash" field. */ private static final ThreadLocal random = new ThreadLocal() { @Override protected Random initialValue() { diff --git a/target/dynamic-connectivity-0.1.3.jar b/target/dynamic-connectivity-0.1.3.jar deleted file mode 100644 index cf2ff3a839315282b61eb2acb68df503a457b8d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16943 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 Date: Fri, 26 Jun 2020 10:51:11 -0400 Subject: [PATCH 08/29] Removed outdated pointer to documentation This removes the suggestion to check the source code for the documentation from README.md. Now that there is a website with the documentation, there is no need to look through the source code. --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cf506d951..6b728a197 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ dynamic connectivity. It supports adding and removing edges and determining whether two vertices are connected - whether there is a path between them. Adding and removing edges take O(log2N) amortized time with high probability, while checking whether two vertices are connected takes O(log N) -time with high probability. Check the source code to see the full API and the -Javadoc documentation. +time with high probability. # Features * Efficiently add and remove edges and determine whether vertices are connected. From 6f69316966f5f1611f546bf602e3b3b5ebab6987 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 12:15:30 +0000 Subject: [PATCH 09/29] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 75d78e64b..49bd8d9cd 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ junit junit - 4.12 + 4.13.1 test From 2031ed4bc3c70a24eb11f945e62f9c742a5713ff Mon Sep 17 00:00:00 2001 From: William Jacobs Date: Tue, 19 Jul 2022 12:12:57 -0400 Subject: [PATCH 10/29] Added ancestor pointers to optimization_ideas.txt This adds the idea of using ancestor pointers to optimization_ideas.txt. --- optimization_ideas.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/optimization_ideas.txt b/optimization_ideas.txt index 75bb73110..aa8255ac9 100644 --- a/optimization_ideas.txt +++ b/optimization_ideas.txt @@ -20,6 +20,15 @@ Thoughts concerning optimization: - Alternatively, in each B-tree node, we could store a binary search tree that combines the augmentation information for the items in that node. This might even benefit ConnGraphs that do not have user-supplied augmentation. +- Actually, there is a way of speeding up queries by an O(log log N) factor that + should be cleaner and easier to implement than a B-tree. We could change each + Euler tour node in the top level to store its kth ancestor (e.g. if k = 3, + then each node stores a pointer to its great grandparent). Each time we change + a node's parent, we have to change the pointers for all of the descendants + that are less than k levels below the node - up to 2^k - 1 nodes. Since there + are O(log N) parent pointer changes per update, and updates already take + O(log^2 N) amortized time, we can afford a k value of up to lg lg N + c (but + interestingly, not O(log log N)). - If I don't implement the B-tree optimization, there are a couple of small optimizations I could try. First, I could move the field EulerTourNode.augmentationFunc to EulerTourVertex, in order to save a little From 3eda7a57e835a1f87ecc1f32d4ffee647ec45e09 Mon Sep 17 00:00:00 2001 From: William Jacobs Date: Tue, 2 Aug 2022 19:27:45 -0400 Subject: [PATCH 11/29] Added assessment of ancestor pointers to optimization_ideas.txt This adds my assessment of the performance of the ancestor pointers optimization in practice to optimization_ideas.txt. --- optimization_ideas.txt | 5 +++-- .../java/com/github/btrekkie/connectivity/ConnGraph.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/optimization_ideas.txt b/optimization_ideas.txt index aa8255ac9..6feb84b0d 100644 --- a/optimization_ideas.txt +++ b/optimization_ideas.txt @@ -27,8 +27,9 @@ Thoughts concerning optimization: a node's parent, we have to change the pointers for all of the descendants that are less than k levels below the node - up to 2^k - 1 nodes. Since there are O(log N) parent pointer changes per update, and updates already take - O(log^2 N) amortized time, we can afford a k value of up to lg lg N + c (but - interestingly, not O(log log N)). + O(log^2 N) amortized time, we can afford a k value of up to lg lg N + O(1) + (but interestingly, not O(log log N)). However, I think this would be + significantly slower than a B-tree in practice. - If I don't implement the B-tree optimization, there are a couple of small optimizations I could try. First, I could move the field EulerTourNode.augmentationFunc to EulerTourVertex, in order to save a little diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index 5b9aa2ea1..545760892 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -39,8 +39,8 @@ import java.util.Map; * take O(log N) time rather than O(log N / log log N) time. * * This implementation is actually based on a slightly modified description of the data structure given in - * http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-851-advanced-data-structures-spring-2012/lecture-videos/session-20-dynamic-graphs-ii/ . - * The description in the video differs from the data structure in the paper in that the levels are numbered in reverse + * https://ocw.mit.edu/courses/6-851-advanced-data-structures-spring-2012/resources/session-20-dynamic-graphs-ii/ . The + * description in the video differs from the data structure in the paper in that the levels are numbered in reverse * order, the constraint on tree sizes is different, and the augmentation uses booleans in place of edges. In addition, * the video defines subgraphs G_i. The change in the constraint on tree sizes is beneficial because it makes it easier * to delete vertices. From f378578ada2e7fcc71bb6b5a54221c613590089d Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:16:08 -0700 Subject: [PATCH 12/29] misc intellij --- .idea/.gitignore | 8 ++++++ .idea/.name | 1 + .idea/compiler.xml | 16 ++++++++++++ .idea/jarRepositories.xml | 25 +++++++++++++++++++ ...com_github_btrekkie_RedBlackNode_1_0_1.xml | 13 ++++++++++ .idea/libraries/Maven__junit_junit_4_13_1.xml | 13 ++++++++++ .../Maven__org_hamcrest_hamcrest_core_1_3.xml | 13 ++++++++++ .idea/misc.xml | 13 ++++++++++ .idea/modules.xml | 8 ++++++ .idea/vcs.xml | 6 +++++ dynamic-connectivity.iml | 17 +++++++++++++ pom.xml | 8 ++++++ 12 files changed, 141 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/compiler.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml create mode 100644 .idea/libraries/Maven__junit_junit_4_13_1.xml create mode 100644 .idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 dynamic-connectivity.iml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 000000000..bd0668ea9 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +DynamicConnectivity \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..f8431b3b7 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 000000000..947ef884c --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml b/.idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml new file mode 100644 index 000000000..2dffd9274 --- /dev/null +++ b/.idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__junit_junit_4_13_1.xml b/.idea/libraries/Maven__junit_junit_4_13_1.xml new file mode 100644 index 000000000..9fa24fcbd --- /dev/null +++ b/.idea/libraries/Maven__junit_junit_4_13_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml b/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml new file mode 100644 index 000000000..f58bbc112 --- /dev/null +++ b/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..40d2f15f2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..c102a020f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/dynamic-connectivity.iml b/dynamic-connectivity.iml new file mode 100644 index 000000000..2a592a5bb --- /dev/null +++ b/dynamic-connectivity.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 49bd8d9cd..db53417a8 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,14 @@ 1.7 + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + From 3a8c6712b11a4df65994a215908b8530ddefad07 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:16:14 -0700 Subject: [PATCH 13/29] satisfied with benchmark --- .../connectivity/test/ConnGraphTest.java | 158 +++++++++++++++--- 1 file changed, 131 insertions(+), 27 deletions(-) diff --git a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java index 30feeae6a..9c90d981e 100644 --- a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java +++ b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java @@ -1,27 +1,118 @@ package com.github.btrekkie.connectivity.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; - -import org.junit.Test; - import com.github.btrekkie.connectivity.ConnGraph; import com.github.btrekkie.connectivity.ConnVertex; +import org.junit.Test; + +import java.util.*; + +import static org.junit.Assert.*; /* Note that most of the ConnGraphTest test methods use the one-argument ConnVertex constructor, in order to make their * behavior more predictable. That way, there are consistent test results, and test failures are easier to debug. */ public class ConnGraphTest { - /** Tests ConnectivityGraph on a small forest and a binary tree-like subgraph. */ + + @Test + public void testPerformanceOnRepeatedConnectionAndDisconnection() { + for (int trial = 0; trial < 10; trial++) { + try { + Thread.sleep(2000); + System.gc(); + Thread.sleep(2000); + } catch (InterruptedException ex) {} + long setup = System.currentTimeMillis(); + ConnGraph graph = new ConnGraph((a, b) -> (Integer) a + (Integer) b); + int SZ = 1000; + ConnVertex[] vertices = new ConnVertex[SZ * SZ]; + for (int i = 0; i < vertices.length; i++) { + vertices[i] = new ConnVertex(); + graph.setVertexAugmentation(vertices[i], 1); + } + for (int x = 0; x < SZ; x++) { + if (x % 100 == 0) { + System.out.println("Indicating progress: connected row " + x); + } + for (int y = 0; y < SZ; y++) { + if (y != SZ - 1 && y != SZ / 2) { // leave graph disconnected in the center - two big areas with no connection + graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + } + if (x != SZ - 1) { + graph.addEdge(vertices[x * SZ + y], vertices[(x + 1) * SZ + y]); + } + } + } + System.out.println("Setup took " + (System.currentTimeMillis() - setup)); + System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + + /* + // previous test for cutting in half + long a = System.currentTimeMillis(); + for (int x = 0; x < SZ; x++) { + int y = SZ / 2; + graph.removeEdge(vertices[x*SZ+y],vertices[x*SZ+y+1]); + System.out.println("Sz " + graph.getComponentAugmentation(vertices[0])); + } + System.out.println("Time: " + (System.currentTimeMillis() - a)); + */ + + // try connecting and disconnecting one edge + + for (int reconnectTrial = 0; reconnectTrial < 10; reconnectTrial++) { // then try connecting and disconnecting them + long start = System.currentTimeMillis(); + int x = SZ / 2; + int y = SZ / 2; + graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + long afterAdd = System.currentTimeMillis(); + System.out.println("Connected size " + graph.getComponentAugmentation(vertices[0])); + graph.removeEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + System.out.println("Disconnected size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Took " + (System.currentTimeMillis() - afterAdd) + " to remove and " + (afterAdd - start) + " to add"); + } + + System.out.println("entire row"); + + // now try connecting and disconnecting the entire row + + for (int reconnectTrial = 0; reconnectTrial < 10; reconnectTrial++) { // then try connecting and disconnecting them + long start = System.currentTimeMillis(); + int y = SZ / 2; + for (int x = 0; x < SZ; x++) { + graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + } + long afterAdd = System.currentTimeMillis(); + System.out.println("Connected size " + graph.getComponentAugmentation(vertices[0])); + for (int x = 0; x < SZ; x++) { + graph.removeEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + } + System.out.println("Disconnected size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Took " + (System.currentTimeMillis() - afterAdd) + " to remove and " + (afterAdd - start) + " to add"); + } + + // entire column + System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + { + int y = SZ / 2; + for (int x = 0; x < SZ; x++) { + graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + } + } + System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + long col = System.currentTimeMillis(); + { + int x = SZ / 2; + for (int y = 0; y < SZ; y++) { + graph.removeEdge(vertices[x * SZ + y], vertices[(x + 1) * SZ + y]); + } + } + System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Column took " + (System.currentTimeMillis() - col)); + } + } + + /** + * Tests ConnectivityGraph on a small forest and a binary tree-like subgraph. + */ @Test public void testForestAndBinaryTree() { ConnGraph graph = new ConnGraph(); @@ -117,7 +208,9 @@ public class ConnGraphTest { assertTrue(graph.connected(vertices.get(991), vertices.get(999))); } - /** Tests ConnectivityGraph on a small graph that has cycles. */ + /** + * Tests ConnectivityGraph on a small graph that has cycles. + */ @Test public void testSmallCycles() { ConnGraph graph = new ConnGraph(); @@ -148,7 +241,9 @@ public class ConnGraphTest { assertFalse(graph.connected(vertex1, vertex4)); } - /** Tests ConnectivityGraph on a grid-based graph. */ + /** + * Tests ConnectivityGraph on a grid-based graph. + */ @Test public void testGrid() { ConnGraph graph = new ConnGraph(); @@ -234,7 +329,9 @@ public class ConnGraphTest { assertFalse(graph.componentHasAugmentation(vertices.get(6).get(4))); } - /** Tests a graph with a hub-and-spokes subgraph and a clique subgraph. */ + /** + * Tests a graph with a hub-and-spokes subgraph and a clique subgraph. + */ @Test public void testWheelAndClique() { ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION); @@ -313,7 +410,7 @@ public class ConnGraphTest { assertEquals(new SumAndMax(9, 29), graph.setVertexAugmentation(clique.get(9), new SumAndMax(-20, 4))); for (int i = 0; i < 10; i++) { assertEquals( - new SumAndMax(i, i + 10), graph.setVertexAugmentation(spokes2.get(i), new SumAndMax(i - 1, i))); + new SumAndMax(i, i + 10), graph.setVertexAugmentation(spokes2.get(i), new SumAndMax(i - 1, i))); } assertNull(graph.removeVertexAugmentation(hub)); assertEquals(new SumAndMax(4, 4), graph.removeVertexAugmentation(spokes1.get(4))); @@ -355,12 +452,13 @@ public class ConnGraphTest { /** * Sets the matching between vertices.get(columnIndex) and vertices.get(columnIndex + 1) to the permutation * suggested by newPermutation. See the comments for the implementation of testPermutations(). - * @param graph The graph. - * @param vertices The vertices. - * @param columnIndex The index of the column. + * + * @param graph The graph. + * @param vertices The vertices. + * @param columnIndex The index of the column. * @param oldPermutation The permutation for the current matching between vertices.get(columnIndex) and - * vertices.get(columnIndex + 1). setPermutation removes the edges in this matching. If there are currently no - * edges between those columns, then oldPermutation is null. + * vertices.get(columnIndex + 1). setPermutation removes the edges in this matching. If there are currently no + * edges between those columns, then oldPermutation is null. * @param newPermutation The permutation for the new matching. * @return newPermutation. */ @@ -467,7 +565,9 @@ public class ConnGraphTest { checkPermutation(graph, vertices, 7, new int[]{5, 2, 0, 6, 4, 7, 3, 1}); } - /** Tests a graph based on the United States. */ + /** + * Tests a graph based on the United States. + */ @Test public void testUnitedStates() { ConnGraph graph = new ConnGraph(SumAndMax.AUGMENTATION); @@ -876,7 +976,9 @@ public class ConnGraphTest { assertNull(graph.getComponentAugmentation(arkansas)); } - /** Tests ConnectivityGraph on the graph for a dodecahedron. */ + /** + * Tests ConnectivityGraph on the graph for a dodecahedron. + */ @Test public void testDodecahedron() { ConnGraph graph = new ConnGraph(); @@ -957,7 +1059,9 @@ public class ConnGraphTest { assertFalse(graph.connected(vertex1, vertex2)); } - /** Tests the zero-argument ConnVertex constructor. */ + /** + * Tests the zero-argument ConnVertex constructor. + */ @Test public void testDefaultConnVertexConstructor() { ConnGraph graph = new ConnGraph(); From 802c81d766bde8da00e79ce2a01708705954df0e Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:23:39 -0700 Subject: [PATCH 14/29] bring in red black node --- dynamic-connectivity.iml | 1 - pom.xml | 5 - .../btrekkie/red_black_node/RedBlackNode.java | 1372 +++++++++++++++++ .../btrekkie/red_black_node/Reference.java | 31 + .../ArbitraryOrderCollection.java | 40 + .../ArbitraryOrderNode.java | 8 + .../ArbitraryOrderValue.java | 19 + .../test/ArbitraryOrderCollectionTest.java | 72 + .../btrekkie/interval_tree/IntervalTree.java | 60 + .../interval_tree/IntervalTreeInterval.java | 28 + .../interval_tree/IntervalTreeNode.java | 68 + .../interval_tree/test/IntervalTreeTest.java | 48 + .../red_black_node/test/RedBlackNodeTest.java | 171 ++ .../red_black_node/test/TestRedBlackNode.java | 36 + .../btrekkie/sub_array_min/SubArrayMin.java | 91 ++ .../sub_array_min/SubArrayMinNode.java | 56 + .../sub_array_min/test/SubArrayMinTest.java | 40 + 17 files changed, 2140 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java create mode 100644 src/main/java/com/github/btrekkie/red_black_node/Reference.java create mode 100644 src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java create mode 100644 src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java create mode 100644 src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java create mode 100644 src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java create mode 100644 src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java create mode 100644 src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java create mode 100644 src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java create mode 100644 src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java create mode 100644 src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java create mode 100644 src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java create mode 100644 src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java create mode 100644 src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java create mode 100644 src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java diff --git a/dynamic-connectivity.iml b/dynamic-connectivity.iml index 2a592a5bb..48ef3c32a 100644 --- a/dynamic-connectivity.iml +++ b/dynamic-connectivity.iml @@ -10,7 +10,6 @@ - diff --git a/pom.xml b/pom.xml index db53417a8..bb5f92c3f 100644 --- a/pom.xml +++ b/pom.xml @@ -48,11 +48,6 @@ - - com.github.btrekkie - RedBlackNode - 1.0.1 - junit junit diff --git a/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java new file mode 100644 index 000000000..51cf7c5c0 --- /dev/null +++ b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java @@ -0,0 +1,1372 @@ +// from: https://github.com/btrekkie/RedBlackNode/blob/master/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java +// also MIT: https://github.com/btrekkie/RedBlackNode/blob/master/LICENSE +package com.github.btrekkie.red_black_node; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). Compared to a class like Java's + * TreeMap, RedBlackNode is a low-level data structure. The internals of a node are exposed as public fields, allowing + * clients to directly observe and manipulate the structure of the tree. This gives clients flexibility, although it + * also enables them to violate the red-black or BST properties. The RedBlackNode class provides methods for performing + * various standard operations, such as insertion and removal. + * + * Unlike most implementations of binary search trees, RedBlackNode supports arbitrary augmentation. By subclassing + * RedBlackNode, clients can add arbitrary data and augmentation information to each node. For example, if we were to + * use a RedBlackNode subclass to implement a sorted set, the subclass would have a field storing an element in the set. + * If we wanted to keep track of the number of non-leaf nodes in each subtree, we would store this as a "size" field and + * override augment() to update this field. All RedBlackNode methods (such as "insert" and remove()) call augment() as + * necessary to correctly maintain the augmentation information, unless otherwise indicated. + * + * The values of the tree are stored in the non-leaf nodes. RedBlackNode does not support use cases where values must be + * stored in the leaf nodes. It is recommended that all of the leaf nodes in a given tree be the same (black) + * RedBlackNode instance, to save space. The root of an empty tree is a leaf node, as opposed to null. + * + * For reference, a red-black tree is a binary search tree satisfying the following properties: + * + * - Every node is colored red or black. + * - The leaf nodes, which are dummy nodes that do not store any values, are colored black. + * - The root is black. + * - Both children of each red node are black. + * - Every path from the root to a leaf contains the same number of black nodes. + * + * @param The type of node in the tree. For example, we might have + * "class FooNode extends RedBlackNode>". + * @author Bill Jacobs + */ +public abstract class RedBlackNode> implements Comparable { + /** A Comparator that compares Comparable elements using their natural order. */ + private static final Comparator> NATURAL_ORDER = new Comparator>() { + @Override + public int compare(Comparable value1, Comparable value2) { + return value1.compareTo(value2); + } + }; + + /** The parent of this node, if any. "parent" is null if this is a leaf node. */ + public N parent; + + /** The left child of this node. "left" is null if this is a leaf node. */ + public N left; + + /** The right child of this node. "right" is null if this is a leaf node. */ + public N right; + + /** Whether the node is colored red, as opposed to black. */ + public boolean isRed; + + /** + * Sets any augmentation information about the subtree rooted at this node that is stored in this node. For + * example, if we augment each node by subtree size (the number of non-leaf nodes in the subtree), this method would + * set the size field of this node to be equal to the size field of the left child plus the size field of the right + * child plus one. + * + * "Augmentation information" is information that we can compute about a subtree rooted at some node, preferably + * based only on the augmentation information in the node's two children and the information in the node. Examples + * of augmentation information are the sum of the values in a subtree and the number of non-leaf nodes in a subtree. + * Augmentation information may not depend on the colors of the nodes. + * + * This method returns whether the augmentation information in any of the ancestors of this node might have been + * affected by changes in this subtree since the last call to augment(). In the usual case, where the augmentation + * information depends only on the information in this node and the augmentation information in its immediate + * children, this is equivalent to whether the augmentation information changed as a result of this call to + * augment(). For example, in the case of subtree size, this returns whether the value of the size field prior to + * calling augment() differed from the size field of the left child plus the size field of the right child plus one. + * False positives are permitted. The return value is unspecified if we have not called augment() on this node + * before. + * + * This method may assume that this is not a leaf node. It may not assume that the augmentation information stored + * in any of the tree's nodes is correct. However, if the augmentation information stored in all of the node's + * descendants is correct, then the augmentation information stored in this node must be correct after calling + * augment(). + */ + public boolean augment() { + return false; + } + + /** + * Throws a RuntimeException if we detect that this node locally violates any invariants specific to this subclass + * of RedBlackNode. For example, if this stores the size of the subtree rooted at this node, this should throw a + * RuntimeException if the size field of this is not equal to the size field of the left child plus the size field + * of the right child plus one. Note that we may call this on a leaf node. + * + * assertSubtreeIsValid() calls assertNodeIsValid() on each node, or at least starts to do so until it detects a + * problem. assertNodeIsValid() should assume the node is in a tree that satisfies all properties common to all + * red-black trees, as assertSubtreeIsValid() is responsible for such checks. assertNodeIsValid() should be + * "downward-looking", i.e. it should ignore any information in "parent", and it should be "local", i.e. it should + * only check a constant number of descendants. To include "global" checks, such as verifying the BST property + * concerning ordering, override assertSubtreeIsValid(). assertOrderIsValid is useful for checking the BST + * property. + */ + public void assertNodeIsValid() { + + } + + /** Returns whether this is a leaf node. */ + public boolean isLeaf() { + return left == null; + } + + /** Returns the root of the tree that contains this node. */ + public N root() { + @SuppressWarnings("unchecked") + N node = (N)this; + while (node.parent != null) { + node = node.parent; + } + return node; + } + + /** Returns the first node in the subtree rooted at this node, if any. */ + public N min() { + if (isLeaf()) { + return null; + } + @SuppressWarnings("unchecked") + N node = (N)this; + while (!node.left.isLeaf()) { + node = node.left; + } + return node; + } + + /** Returns the last node in the subtree rooted at this node, if any. */ + public N max() { + if (isLeaf()) { + return null; + } + @SuppressWarnings("unchecked") + N node = (N)this; + while (!node.right.isLeaf()) { + node = node.right; + } + return node; + } + + /** Returns the node immediately before this in the tree that contains this node, if any. */ + public N predecessor() { + if (!left.isLeaf()) { + N node; + for (node = left; !node.right.isLeaf(); node = node.right); + return node; + } else if (parent == null) { + return null; + } else { + @SuppressWarnings("unchecked") + N node = (N)this; + while (node.parent != null && node.parent.left == node) { + node = node.parent; + } + return node.parent; + } + } + + /** Returns the node immediately after this in the tree that contains this node, if any. */ + public N successor() { + if (!right.isLeaf()) { + N node; + for (node = right; !node.left.isLeaf(); node = node.left); + return node; + } else if (parent == null) { + return null; + } else { + @SuppressWarnings("unchecked") + N node = (N)this; + while (node.parent != null && node.parent.right == node) { + node = node.parent; + } + return node.parent; + } + } + + /** + * Performs a left rotation about this node. This method assumes that !isLeaf() && !right.isLeaf(). It calls + * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting + * parent's ancestors, because that is normally the responsibility of the caller. + * @return The return value from calling augment() on the resulting parent. + */ + public boolean rotateLeft() { + if (isLeaf() || right.isLeaf()) { + throw new IllegalArgumentException("The node or its right child is a leaf"); + } + N newParent = right; + right = newParent.left; + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (!right.isLeaf()) { + right.parent = nThis; + } + newParent.parent = parent; + parent = newParent; + newParent.left = nThis; + if (newParent.parent != null) { + if (newParent.parent.left == this) { + newParent.parent.left = newParent; + } else { + newParent.parent.right = newParent; + } + } + augment(); + return newParent.augment(); + } + + /** + * Performs a right rotation about this node. This method assumes that !isLeaf() && !left.isLeaf(). It calls + * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting + * parent's ancestors, because that is normally the responsibility of the caller. + * @return The return value from calling augment() on the resulting parent. + */ + public boolean rotateRight() { + if (isLeaf() || left.isLeaf()) { + throw new IllegalArgumentException("The node or its left child is a leaf"); + } + N newParent = left; + left = newParent.right; + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (!left.isLeaf()) { + left.parent = nThis; + } + newParent.parent = parent; + parent = newParent; + newParent.right = nThis; + if (newParent.parent != null) { + if (newParent.parent.left == this) { + newParent.parent.left = newParent; + } else { + newParent.parent.right = newParent; + } + } + augment(); + return newParent.augment(); + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). This method is more efficient than fixInsertion if + * "augment" is false or augment() might return false. + * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment(). + */ + public void fixInsertionWithoutGettingRoot(boolean augment) { + if (!isRed) { + throw new IllegalArgumentException("The node must be red"); + } + boolean changed = augment; + if (augment) { + augment(); + } + + RedBlackNode node = this; + while (node.parent != null && node.parent.isRed) { + N parent = node.parent; + N grandparent = parent.parent; + if (grandparent.left.isRed && grandparent.right.isRed) { + grandparent.left.isRed = false; + grandparent.right.isRed = false; + grandparent.isRed = true; + + if (changed) { + changed = parent.augment(); + if (changed) { + changed = grandparent.augment(); + } + } + node = grandparent; + } else { + if (parent.left == node) { + if (grandparent.right == parent) { + parent.rotateRight(); + node = parent; + parent = node.parent; + } + } else if (grandparent.left == parent) { + parent.rotateLeft(); + node = parent; + parent = node.parent; + } + + if (parent.left == node) { + boolean grandparentChanged = grandparent.rotateRight(); + if (augment) { + changed = grandparentChanged; + } + } else { + boolean grandparentChanged = grandparent.rotateLeft(); + if (augment) { + changed = grandparentChanged; + } + } + + parent.isRed = false; + grandparent.isRed = true; + node = parent; + break; + } + } + + if (node.parent == null) { + node.isRed = false; + } + if (changed) { + for (node = node.parent; node != null; node = node.parent) { + if (!node.augment()) { + break; + } + } + } + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). This method is more efficient than fixInsertion() if + * augment() might return false. + */ + public void fixInsertionWithoutGettingRoot() { + fixInsertionWithoutGettingRoot(true); + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). + * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment(). + * @return The root of the resulting tree. + */ + public N fixInsertion(boolean augment) { + fixInsertionWithoutGettingRoot(augment); + return root(); + } + + /** + * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be + * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs + * any rotations by calling rotateLeft() and rotateRight(). + * @return The root of the resulting tree. + */ + public N fixInsertion() { + fixInsertionWithoutGettingRoot(true); + return root(); + } + + /** Returns a Comparator that compares instances of N using their natural order, as in N.compareTo. */ + @SuppressWarnings({"rawtypes", "unchecked"}) + private Comparator naturalOrder() { + Comparator comparator = (Comparator)NATURAL_ORDER; + return (Comparator)comparator; + } + + /** + * Inserts the specified node into the tree rooted at this node. Assumes this is the root. We treat newNode as a + * solitary node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed + * fields. + * + * If it is not efficient or convenient to find the location for a node using a Comparator, then you should manually + * add the node to the appropriate location, color it red, and call fixInsertion(). + * + * @param newNode The node to insert. + * @param allowDuplicates Whether to insert newNode if there is an equal node in the tree. To check whether we + * inserted newNode, check whether newNode.parent is null and the return value differs from newNode. + * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural + * order, as in N.compareTo. If you are passing null, then you must override the compareTo method, because the + * default implementation requires the nodes to already be in the same tree. + * @return The root of the resulting tree. + */ + public N insert(N newNode, boolean allowDuplicates, Comparator comparator) { + if (parent != null) { + throw new IllegalArgumentException("This is not the root of a tree"); + } + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (isLeaf()) { + newNode.isRed = false; + newNode.left = nThis; + newNode.right = nThis; + newNode.parent = null; + newNode.augment(); + return newNode; + } + if (comparator == null) { + comparator = naturalOrder(); + } + + N node = nThis; + int comparison; + while (true) { + comparison = comparator.compare(newNode, node); + if (comparison < 0) { + if (!node.left.isLeaf()) { + node = node.left; + } else { + newNode.left = node.left; + newNode.right = node.left; + node.left = newNode; + newNode.parent = node; + break; + } + } else if (comparison > 0 || allowDuplicates) { + if (!node.right.isLeaf()) { + node = node.right; + } else { + newNode.left = node.right; + newNode.right = node.right; + node.right = newNode; + newNode.parent = node; + break; + } + } else { + newNode.parent = null; + return nThis; + } + } + newNode.isRed = true; + return newNode.fixInsertion(); + } + + /** + * Moves this node to its successor's former position in the tree and vice versa, i.e. sets the "left", "right", + * "parent", and isRed fields of each. This method assumes that this is not a leaf node. + * @return The node with which we swapped. + */ + private N swapWithSuccessor() { + N replacement = successor(); + boolean oldReplacementIsRed = replacement.isRed; + N oldReplacementLeft = replacement.left; + N oldReplacementRight = replacement.right; + N oldReplacementParent = replacement.parent; + + replacement.isRed = isRed; + replacement.left = left; + replacement.right = right; + replacement.parent = parent; + if (parent != null) { + if (parent.left == this) { + parent.left = replacement; + } else { + parent.right = replacement; + } + } + + @SuppressWarnings("unchecked") + N nThis = (N)this; + isRed = oldReplacementIsRed; + left = oldReplacementLeft; + right = oldReplacementRight; + if (oldReplacementParent == this) { + parent = replacement; + parent.right = nThis; + } else { + parent = oldReplacementParent; + parent.left = nThis; + } + + replacement.right.parent = replacement; + if (!replacement.left.isLeaf()) { + replacement.left.parent = replacement; + } + if (!right.isLeaf()) { + right.parent = nThis; + } + return replacement; + } + + /** + * Performs red-black deletion fixup. To be more precise, this fixes a tree that satisfies all of the requirements + * of red-black trees, except that all paths from the root to a leaf that pass through the sibling of this node have + * one fewer black node than all other root-to-leaf paths. This method assumes that this is not a leaf node. + */ + private void fixSiblingDeletion() { + RedBlackNode sibling = this; + boolean changed = true; + boolean haveAugmentedParent = false; + boolean haveAugmentedGrandparent = false; + while (true) { + N parent = sibling.parent; + if (sibling.isRed) { + parent.isRed = true; + sibling.isRed = false; + if (parent.left == sibling) { + changed = parent.rotateRight(); + sibling = parent.left; + } else { + changed = parent.rotateLeft(); + sibling = parent.right; + } + haveAugmentedParent = true; + haveAugmentedGrandparent = true; + } else if (!sibling.left.isRed && !sibling.right.isRed) { + sibling.isRed = true; + if (parent.isRed) { + parent.isRed = false; + break; + } else { + if (changed && !haveAugmentedParent) { + changed = parent.augment(); + } + N grandparent = parent.parent; + if (grandparent == null) { + break; + } else if (grandparent.left == parent) { + sibling = grandparent.right; + } else { + sibling = grandparent.left; + } + haveAugmentedParent = haveAugmentedGrandparent; + haveAugmentedGrandparent = false; + } + } else { + if (sibling == parent.left) { + if (!sibling.left.isRed) { + sibling.rotateLeft(); + sibling = sibling.parent; + } + } else if (!sibling.right.isRed) { + sibling.rotateRight(); + sibling = sibling.parent; + } + sibling.isRed = parent.isRed; + parent.isRed = false; + if (sibling == parent.left) { + sibling.left.isRed = false; + changed = parent.rotateRight(); + } else { + sibling.right.isRed = false; + changed = parent.rotateLeft(); + } + haveAugmentedParent = haveAugmentedGrandparent; + haveAugmentedGrandparent = false; + break; + } + } + + // Update augmentation info + N parent = sibling.parent; + if (changed && parent != null) { + if (!haveAugmentedParent) { + changed = parent.augment(); + } + if (changed && parent.parent != null) { + parent = parent.parent; + if (!haveAugmentedGrandparent) { + changed = parent.augment(); + } + if (changed) { + for (parent = parent.parent; parent != null; parent = parent.parent) { + if (!parent.augment()) { + break; + } + } + } + } + } + } + + /** + * Removes this node from the tree that contains it. The effect of this method on the fields of this node is + * unspecified. This method assumes that this is not a leaf node. This method is more efficient than remove() if + * augment() might return false. + * + * If the node has two children, we begin by moving the node's successor to its former position, by changing the + * successor's "left", "right", "parent", and isRed fields. + */ + public void removeWithoutGettingRoot() { + if (isLeaf()) { + throw new IllegalArgumentException("Attempted to remove a leaf node"); + } + N replacement; + if (left.isLeaf() || right.isLeaf()) { + replacement = null; + } else { + replacement = swapWithSuccessor(); + } + + N child; + if (!left.isLeaf()) { + child = left; + } else if (!right.isLeaf()) { + child = right; + } else { + child = null; + } + + if (child != null) { + // Replace this node with its child + child.parent = parent; + if (parent != null) { + if (parent.left == this) { + parent.left = child; + } else { + parent.right = child; + } + } + child.isRed = false; + + if (child.parent != null) { + N parent; + for (parent = child.parent; parent != null; parent = parent.parent) { + if (!parent.augment()) { + break; + } + } + } + } else if (parent != null) { + // Replace this node with a leaf node + N leaf = left; + N parent = this.parent; + N sibling; + if (parent.left == this) { + parent.left = leaf; + sibling = parent.right; + } else { + parent.right = leaf; + sibling = parent.left; + } + + if (!isRed) { + RedBlackNode siblingNode = sibling; + siblingNode.fixSiblingDeletion(); + } else { + while (parent != null) { + if (!parent.augment()) { + break; + } + parent = parent.parent; + } + } + } + + if (replacement != null) { + replacement.augment(); + for (N parent = replacement.parent; parent != null; parent = parent.parent) { + if (!parent.augment()) { + break; + } + } + } + + // Clear any previously existing links, so that we're more likely to encounter an exception if we attempt to + // access the removed node + parent = null; + left = null; + right = null; + isRed = true; + } + + /** + * Removes this node from the tree that contains it. The effect of this method on the fields of this node is + * unspecified. This method assumes that this is not a leaf node. + * + * If the node has two children, we begin by moving the node's successor to its former position, by changing the + * successor's "left", "right", "parent", and isRed fields. + * + * @return The root of the resulting tree. + */ + public N remove() { + if (isLeaf()) { + throw new IllegalArgumentException("Attempted to remove a leaf node"); + } + + // Find an arbitrary non-leaf node in the tree other than this node + N node; + if (parent != null) { + node = parent; + } else if (!left.isLeaf()) { + node = left; + } else if (!right.isLeaf()) { + node = right; + } else { + return left; + } + + removeWithoutGettingRoot(); + return node.root(); + } + + /** + * Returns the root of a perfectly height-balanced subtree containing the next "size" (non-leaf) nodes from + * "iterator", in iteration order. This method is responsible for setting the "left", "right", "parent", and isRed + * fields of the nodes, and calling augment() as appropriate. It ignores the initial values of the "left", "right", + * "parent", and isRed fields. + * @param iterator The nodes. + * @param size The number of nodes. + * @param height The "height" of the subtree's root node above the deepest leaf in the tree that contains it. Since + * insertion fixup is slow if there are too many red nodes and deleteion fixup is slow if there are too few red + * nodes, we compromise and have red nodes at every fourth level. We color a node red iff its "height" is equal + * to 1 mod 4. + * @param leaf The leaf node. + * @return The root of the subtree. + */ + private static > N createTree( + Iterator iterator, int size, int height, N leaf) { + if (size == 0) { + return leaf; + } else { + N left = createTree(iterator, (size - 1) / 2, height - 1, leaf); + N node = iterator.next(); + N right = createTree(iterator, size / 2, height - 1, leaf); + + node.isRed = height % 4 == 1; + node.left = left; + node.right = right; + if (!left.isLeaf()) { + left.parent = node; + } + if (!right.isLeaf()) { + right.parent = node; + } + + node.augment(); + return node; + } + } + + /** + * Returns the root of a perfectly height-balanced tree containing the specified nodes, in iteration order. This + * method is responsible for setting the "left", "right", "parent", and isRed fields of the nodes (excluding + * "leaf"), and calling augment() as appropriate. It ignores the initial values of the "left", "right", "parent", + * and isRed fields. + * @param nodes The nodes. + * @param leaf The leaf node. + * @return The root of the tree. + */ + public static > N createTree(Collection nodes, N leaf) { + int size = nodes.size(); + if (size == 0) { + return leaf; + } + + int height = 0; + for (int subtreeSize = size; subtreeSize > 0; subtreeSize /= 2) { + height++; + } + + N node = createTree(nodes.iterator(), size, height, leaf); + node.parent = null; + node.isRed = false; + return node; + } + + /** + * Concatenates to the end of the tree rooted at this node. To be precise, given that all of the nodes in this + * precede the node "pivot", which precedes all of the nodes in "last", this returns the root of a tree containing + * all of these nodes. This method destroys the trees rooted at "this" and "last". We treat "pivot" as a solitary + * node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed fields. + * This method assumes that this node and "last" are the roots of their respective trees. + * + * This method takes O(log N) time. It is more efficient than inserting "pivot" and then calling concatenate(last). + * It is considerably more efficient than inserting "pivot" and all of the nodes in "last". + */ + public N concatenate(N last, N pivot) { + // If the black height of "first", where first = this, is less than or equal to that of "last", starting at the + // root of "last", we keep going left until we reach a black node whose black height is equal to that of + // "first". Then, we make "pivot" the parent of that node and of "first", coloring it red, and perform + // insertion fixup on the pivot. If the black height of "first" is greater than that of "last", we do the + // mirror image of the above. + + if (parent != null) { + throw new IllegalArgumentException("This is not the root of a tree"); + } + if (last.parent != null) { + throw new IllegalArgumentException("\"last\" is not the root of a tree"); + } + + // Compute the black height of the trees + int firstBlackHeight = 0; + @SuppressWarnings("unchecked") + N first = (N)this; + for (N node = first; node != null; node = node.right) { + if (!node.isRed) { + firstBlackHeight++; + } + } + int lastBlackHeight = 0; + for (N node = last; node != null; node = node.right) { + if (!node.isRed) { + lastBlackHeight++; + } + } + + // Identify the children and parent of pivot + N firstChild = first; + N lastChild = last; + N parent; + if (firstBlackHeight <= lastBlackHeight) { + parent = null; + int blackHeight = lastBlackHeight; + while (blackHeight > firstBlackHeight) { + if (!lastChild.isRed) { + blackHeight--; + } + parent = lastChild; + lastChild = lastChild.left; + } + if (lastChild.isRed) { + parent = lastChild; + lastChild = lastChild.left; + } + } else { + parent = null; + int blackHeight = firstBlackHeight; + while (blackHeight > lastBlackHeight) { + if (!firstChild.isRed) { + blackHeight--; + } + parent = firstChild; + firstChild = firstChild.right; + } + if (firstChild.isRed) { + parent = firstChild; + firstChild = firstChild.right; + } + } + + // Add "pivot" to the tree + pivot.isRed = true; + pivot.parent = parent; + if (parent != null) { + if (firstBlackHeight < lastBlackHeight) { + parent.left = pivot; + } else { + parent.right = pivot; + } + } + pivot.left = firstChild; + if (!firstChild.isLeaf()) { + firstChild.parent = pivot; + } + pivot.right = lastChild; + if (!lastChild.isLeaf()) { + lastChild.parent = pivot; + } + + // Perform insertion fixup + return pivot.fixInsertion(); + } + + /** + * Concatenates the tree rooted at "last" to the end of the tree rooted at this node. To be precise, given that all + * of the nodes in this precede all of the nodes in "last", this returns the root of a tree containing all of these + * nodes. This method destroys the trees rooted at "this" and "last". It assumes that this node and "last" are the + * roots of their respective trees. This method takes O(log N) time. It is considerably more efficient than + * inserting all of the nodes in "last". + */ + public N concatenate(N last) { + if (parent != null || last.parent != null) { + throw new IllegalArgumentException("The node is not the root of a tree"); + } + if (isLeaf()) { + return last; + } else if (last.isLeaf()) { + @SuppressWarnings("unchecked") + N nThis = (N)this; + return nThis; + } else { + N node = last.min(); + last = node.remove(); + return concatenate(last, node); + } + } + + /** + * Splits the tree rooted at this node into two trees, so that the first element of the return value is the root of + * a tree consisting of the nodes that were before the specified node, and the second element of the return value is + * the root of a tree consisting of the nodes that were equal to or after the specified node. This method is + * destructive, meaning it does not preserve the original tree. It assumes that this node is the root and is in the + * same tree as splitNode. It takes O(log N) time. It is considerably more efficient than removing all of the + * nodes at or after splitNode and then creating a new tree from those nodes. + * @param The node at which to split the tree. + * @return An array consisting of the resulting trees. + */ + public N[] split(N splitNode) { + // To split the tree, we accumulate a pre-split tree and a post-split tree. We walk down the tree toward the + // position where we are splitting. Whenever we go left, we concatenate the right subtree with the post-split + // tree, and whenever we go right, we concatenate the pre-split tree with the left subtree. We use the + // concatenation algorithm described in concatenate(Object, Object). For the pivot, we use the last node where + // we went left in the case of a left move, and the last node where we went right in the case of a right move. + // + // The method uses the following variables: + // + // node: The current node in our walk down the tree. + // first: A node on the right spine of the pre-split tree. At the beginning of each iteration, it is the black + // node with the same black height as "node". If the pre-split tree is empty, this is null instead. + // firstParent: The parent of "first". If the pre-split tree is empty, this is null. Otherwise, this is the + // same as first.parent, unless first.isLeaf(). + // firstPivot: The node where we last went right, i.e. the next node to use as a pivot when concatenating with + // the pre-split tree. + // advanceFirst: Whether to set "first" to be its next black descendant at the end of the loop. + // last, lastParent, lastPivot, advanceLast: Analogous to "first", firstParent, firstPivot, and advanceFirst, + // but for the post-split tree. + if (parent != null) { + throw new IllegalArgumentException("This is not the root of a tree"); + } + if (isLeaf() || splitNode.isLeaf()) { + throw new IllegalArgumentException("The root or the split node is a leaf"); + } + + // Create an array containing the path from the root to splitNode + int depth = 1; + N parent; + for (parent = splitNode; parent.parent != null; parent = parent.parent) { + depth++; + } + if (parent != this) { + throw new IllegalArgumentException("The split node does not belong to this tree"); + } + RedBlackNode[] path = new RedBlackNode[depth]; + for (parent = splitNode; parent != null; parent = parent.parent) { + depth--; + path[depth] = parent; + } + + @SuppressWarnings("unchecked") + N node = (N)this; + N first = null; + N firstParent = null; + N last = null; + N lastParent = null; + N firstPivot = null; + N lastPivot = null; + while (!node.isLeaf()) { + boolean advanceFirst = !node.isRed && firstPivot != null; + boolean advanceLast = !node.isRed && lastPivot != null; + if ((depth + 1 < path.length && path[depth + 1] == node.left) || depth + 1 == path.length) { + // Left move + if (lastPivot == null) { + // The post-split tree is empty + last = node.right; + last.parent = null; + if (last.isRed) { + last.isRed = false; + lastParent = last; + last = last.left; + } + } else { + // Concatenate node.right and the post-split tree + if (node.right.isRed) { + node.right.isRed = false; + } else if (!node.isRed) { + lastParent = last; + last = last.left; + if (last.isRed) { + lastParent = last; + last = last.left; + } + advanceLast = false; + } + lastPivot.isRed = true; + lastPivot.parent = lastParent; + if (lastParent != null) { + lastParent.left = lastPivot; + } + lastPivot.left = node.right; + if (!lastPivot.left.isLeaf()) { + lastPivot.left.parent = lastPivot; + } + lastPivot.right = last; + if (!last.isLeaf()) { + last.parent = lastPivot; + } + last = lastPivot.left; + lastParent = lastPivot; + lastPivot.fixInsertionWithoutGettingRoot(false); + } + lastPivot = node; + node = node.left; + } else { + // Right move + if (firstPivot == null) { + // The pre-split tree is empty + first = node.left; + first.parent = null; + if (first.isRed) { + first.isRed = false; + firstParent = first; + first = first.right; + } + } else { + // Concatenate the post-split tree and node.left + if (node.left.isRed) { + node.left.isRed = false; + } else if (!node.isRed) { + firstParent = first; + first = first.right; + if (first.isRed) { + firstParent = first; + first = first.right; + } + advanceFirst = false; + } + firstPivot.isRed = true; + firstPivot.parent = firstParent; + if (firstParent != null) { + firstParent.right = firstPivot; + } + firstPivot.right = node.left; + if (!firstPivot.right.isLeaf()) { + firstPivot.right.parent = firstPivot; + } + firstPivot.left = first; + if (!first.isLeaf()) { + first.parent = firstPivot; + } + first = firstPivot.right; + firstParent = firstPivot; + firstPivot.fixInsertionWithoutGettingRoot(false); + } + firstPivot = node; + node = node.right; + } + + depth++; + + // Update "first" and "last" to be the nodes at the proper black height + if (advanceFirst) { + firstParent = first; + first = first.right; + if (first.isRed) { + firstParent = first; + first = first.right; + } + } + if (advanceLast) { + lastParent = last; + last = last.left; + if (last.isRed) { + lastParent = last; + last = last.left; + } + } + } + + // Add firstPivot to the pre-split tree + N leaf = node; + if (first == null) { + first = leaf; + } else { + firstPivot.isRed = true; + firstPivot.parent = firstParent; + if (firstParent != null) { + firstParent.right = firstPivot; + } + firstPivot.left = leaf; + firstPivot.right = leaf; + firstPivot.fixInsertionWithoutGettingRoot(false); + for (first = firstPivot; first.parent != null; first = first.parent) { + first.augment(); + } + first.augment(); + } + + // Add lastPivot to the post-split tree + lastPivot.isRed = true; + lastPivot.parent = lastParent; + if (lastParent != null) { + lastParent.left = lastPivot; + } + lastPivot.left = leaf; + lastPivot.right = leaf; + lastPivot.fixInsertionWithoutGettingRoot(false); + for (last = lastPivot; last.parent != null; last = last.parent) { + last.augment(); + } + last.augment(); + + @SuppressWarnings("unchecked") + N[] result = (N[])Array.newInstance(getClass(), 2); + result[0] = first; + result[1] = last; + return result; + } + + /** + * Returns the lowest common ancestor of this node and "other" - the node that is an ancestor of both and is not the + * parent of a node that is an ancestor of both. Assumes that this is in the same tree as "other". Assumes that + * neither "this" nor "other" is a leaf node. This method may return "this" or "other". + * + * Note that while it is possible to compute the lowest common ancestor in O(P) time, where P is the length of the + * path from this node to "other", the "lca" method is not guaranteed to take O(P) time. If your application + * requires this, then you should write your own lowest common ancestor method. + */ + public N lca(N other) { + if (isLeaf() || other.isLeaf()) { + throw new IllegalArgumentException("One of the nodes is a leaf node"); + } + + // Compute the depth of each node + int depth = 0; + for (N parent = this.parent; parent != null; parent = parent.parent) { + depth++; + } + int otherDepth = 0; + for (N parent = other.parent; parent != null; parent = parent.parent) { + otherDepth++; + } + + // Go up to nodes of the same depth + @SuppressWarnings("unchecked") + N parent = (N)this; + N otherParent = other; + if (depth <= otherDepth) { + for (int i = otherDepth; i > depth; i--) { + otherParent = otherParent.parent; + } + } else { + for (int i = depth; i > otherDepth; i--) { + parent = parent.parent; + } + } + + // Find the LCA + while (parent != otherParent) { + parent = parent.parent; + otherParent = otherParent.parent; + } + if (parent != null) { + return parent; + } else { + throw new IllegalArgumentException("The nodes do not belong to the same tree"); + } + } + + /** + * Returns an integer comparing the position of this node in the tree that contains it with that of "other". Returns + * a negative number if this is earlier, a positive number if this is later, and 0 if this is at the same position. + * Assumes that this is in the same tree as "other". Assumes that neither "this" nor "other" is a leaf node. + * + * The base class's implementation takes O(log N) time. If a RedBlackNode subclass stores a value used to order the + * nodes, then it could override compareTo to compare the nodes' values, which would take O(1) time. + * + * Note that while it is possible to compare the positions of two nodes in O(P) time, where P is the length of the + * path from this node to "other", the default implementation of compareTo is not guaranteed to take O(P) time. If + * your application requires this, then you should write your own comparison method. + */ + @Override + public int compareTo(N other) { + if (isLeaf() || other.isLeaf()) { + throw new IllegalArgumentException("One of the nodes is a leaf node"); + } + + // The algorithm operates as follows: compare the depth of this node to that of "other". If the depth of + // "other" is greater, keep moving up from "other" until we find the ancestor at the same depth. Then, keep + // moving up from "this" and from that node until we reach the lowest common ancestor. The node that arrived + // from the left child of the common ancestor is earlier. The algorithm is analogous if the depth of "other" is + // not greater. + if (this == other) { + return 0; + } + + // Compute the depth of each node + int depth = 0; + RedBlackNode parent; + for (parent = this; parent.parent != null; parent = parent.parent) { + depth++; + } + int otherDepth = 0; + N otherParent; + for (otherParent = other; otherParent.parent != null; otherParent = otherParent.parent) { + otherDepth++; + } + + // Go up to nodes of the same depth + if (depth < otherDepth) { + otherParent = other; + for (int i = otherDepth - 1; i > depth; i--) { + otherParent = otherParent.parent; + } + if (otherParent.parent != this) { + otherParent = otherParent.parent; + } else if (left == otherParent) { + return 1; + } else { + return -1; + } + parent = this; + } else if (depth > otherDepth) { + parent = this; + for (int i = depth - 1; i > otherDepth; i--) { + parent = parent.parent; + } + if (parent.parent != other) { + parent = parent.parent; + } else if (other.left == parent) { + return -1; + } else { + return 1; + } + otherParent = other; + } else { + parent = this; + otherParent = other; + } + + // Keep going up until we reach the lowest common ancestor + while (parent.parent != otherParent.parent) { + parent = parent.parent; + otherParent = otherParent.parent; + } + if (parent.parent == null) { + throw new IllegalArgumentException("The nodes do not belong to the same tree"); + } + if (parent.parent.left == parent) { + return -1; + } else { + return 1; + } + } + + /** Throws a RuntimeException if the RedBlackNode fields of this are not correct for a leaf node. */ + private void assertIsValidLeaf() { + if (left != null || right != null || parent != null || isRed) { + throw new RuntimeException("A leaf node's \"left\", \"right\", \"parent\", or isRed field is incorrect"); + } + } + + /** + * Throws a RuntimeException if the subtree rooted at this node does not satisfy the red-black properties, excluding + * the requirement that the root be black, or it contains a repeated node other than a leaf node. + * @param blackHeight The required number of black nodes in each path from this to a leaf node, including this and + * the leaf node. + * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in + * the subtree rooted at this node to "visited". + */ + private void assertSubtreeIsValidRedBlack(int blackHeight, Set> visited) { + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (left == null || right == null) { + assertIsValidLeaf(); + if (blackHeight != 1) { + throw new RuntimeException("Not all root-to-leaf paths have the same number of black nodes"); + } + return; + } else if (!visited.add(new Reference(nThis))) { + throw new RuntimeException("The tree contains a repeated non-leaf node"); + } else { + int childBlackHeight; + if (isRed) { + if ((!left.isLeaf() && left.isRed) || (!right.isLeaf() && right.isRed)) { + throw new RuntimeException("A red node has a red child"); + } + childBlackHeight = blackHeight; + } else if (blackHeight == 0) { + throw new RuntimeException("Not all root-to-leaf paths have the same number of black nodes"); + } else { + childBlackHeight = blackHeight - 1; + } + + if (!left.isLeaf() && left.parent != this) { + throw new RuntimeException("left.parent != this"); + } + if (!right.isLeaf() && right.parent != this) { + throw new RuntimeException("right.parent != this"); + } + RedBlackNode leftNode = left; + RedBlackNode rightNode = right; + leftNode.assertSubtreeIsValidRedBlack(childBlackHeight, visited); + rightNode.assertSubtreeIsValidRedBlack(childBlackHeight, visited); + } + } + + /** Calls assertNodeIsValid() on every node in the subtree rooted at this node. */ + private void assertNodesAreValid() { + assertNodeIsValid(); + if (left != null) { + RedBlackNode leftNode = left; + RedBlackNode rightNode = right; + leftNode.assertNodesAreValid(); + rightNode.assertNodesAreValid(); + } + } + + /** + * Throws a RuntimeException if the subtree rooted at this node is not a valid red-black tree, e.g. if a red node + * has a red child or it contains a non-leaf node "node" for which node.left.parent != node. (If parent != null, + * it's okay if isRed is true.) This method is useful for debugging. See also assertSubtreeIsValid(). + */ + public void assertSubtreeIsValidRedBlack() { + if (isLeaf()) { + assertIsValidLeaf(); + } else { + if (parent == null && isRed) { + throw new RuntimeException("The root is red"); + } + + // Compute the black height of the tree + Set> nodes = new HashSet>(); + int blackHeight = 0; + @SuppressWarnings("unchecked") + N node = (N)this; + while (node != null) { + if (!nodes.add(new Reference(node))) { + throw new RuntimeException("The tree contains a repeated non-leaf node"); + } + if (!node.isRed) { + blackHeight++; + } + node = node.left; + } + + assertSubtreeIsValidRedBlack(blackHeight, new HashSet>()); + } + } + + /** + * Throws a RuntimeException if we detect a problem with the subtree rooted at this node, such as a red child of a + * red node or a non-leaf descendant "node" for which node.left.parent != node. This method is useful for + * debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call assertOrderIsValid. + */ + public void assertSubtreeIsValid() { + assertSubtreeIsValidRedBlack(); + assertNodesAreValid(); + } + + /** + * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order or they + * do not lie in the specified range. Assumes that the subtree rooted at this node is a valid binary tree, i.e. it + * has no repeated nodes other than leaf nodes. + * @param comparator A comparator indicating how the nodes should be ordered. + * @param start The lower limit for nodes in the subtree, if any. + * @param end The upper limit for nodes in the subtree, if any. + */ + private void assertOrderIsValid(Comparator comparator, N start, N end) { + if (!isLeaf()) { + @SuppressWarnings("unchecked") + N nThis = (N)this; + if (start != null && comparator.compare(nThis, start) < 0) { + throw new RuntimeException("The nodes are not ordered correctly"); + } + if (end != null && comparator.compare(nThis, end) > 0) { + throw new RuntimeException("The nodes are not ordered correctly"); + } + RedBlackNode leftNode = left; + RedBlackNode rightNode = right; + leftNode.assertOrderIsValid(comparator, start, nThis); + rightNode.assertOrderIsValid(comparator, nThis, end); + } + } + + /** + * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order. + * Assumes that this is a valid binary tree, i.e. there are no repeated nodes other than leaf nodes. This method is + * useful for debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call + * assertOrderIsValid. + * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes' + * natural order, as in N.compareTo. + */ + public void assertOrderIsValid(Comparator comparator) { + if (comparator == null) { + comparator = naturalOrder(); + } + assertOrderIsValid(comparator, null, null); + } +} + diff --git a/src/main/java/com/github/btrekkie/red_black_node/Reference.java b/src/main/java/com/github/btrekkie/red_black_node/Reference.java new file mode 100644 index 000000000..410b9fa28 --- /dev/null +++ b/src/main/java/com/github/btrekkie/red_black_node/Reference.java @@ -0,0 +1,31 @@ +// from: https://github.com/btrekkie/RedBlackNode/blob/master/src/main/java/com/github/btrekkie/red_black_node/Reference.java +// also MIT: https://github.com/btrekkie/RedBlackNode/blob/master/LICENSE +package com.github.btrekkie.red_black_node; + +/** + * Wraps a value using reference equality. In other words, two references are equal only if their values are the same + * object instance, as in ==. + * @param The type of value. + */ +class Reference { + /** The value this wraps. */ + private final T value; + + public Reference(T value) { + this.value = value; + } + + public boolean equals(Object obj) { + if (!(obj instanceof Reference)) { + return false; + } + Reference reference = (Reference)obj; + return value == reference.value; + } + + @Override + public int hashCode() { + return System.identityHashCode(value); + } +} + diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java new file mode 100644 index 000000000..8b5102471 --- /dev/null +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java @@ -0,0 +1,40 @@ +package com.github.btrekkie.arbitrary_order_collection; + +import java.util.Comparator; + +/** + * Provides objects ordered in an arbitrary, but consistent fashion, through the createValue() method. To determine the + * relative order of two values, use ArbitraryOrderValue.compareTo. We may only compare values on which we have not + * called "remove" that were created in the same ArbitraryOrderCollection instance. Note that despite the name, + * ArbitraryOrderCollection does not implement Collection. + */ +/* We implement an ArbitraryOrderCollection using a red-black tree. We order the nodes arbitrarily. + */ +public class ArbitraryOrderCollection { + /** The Comparator for ordering ArbitraryOrderNodes. */ + private static final Comparator NODE_COMPARATOR = new Comparator() { + @Override + public int compare(ArbitraryOrderNode node1, ArbitraryOrderNode node2) { + return 0; + } + }; + + /** The root node of the tree. */ + private ArbitraryOrderNode root = new ArbitraryOrderNode(); + + /** Adds and returns a new value for ordering. */ + public ArbitraryOrderValue createValue() { + ArbitraryOrderNode node = new ArbitraryOrderNode(); + root = root.insert(node, true, NODE_COMPARATOR); + return new ArbitraryOrderValue(node); + } + + /** + * Removes the specified value from this collection. Assumes we obtained the value by calling createValue() on this + * instance of ArbitraryOrderCollection. After calling "remove" on a value, we may no longer compare it to other + * values. + */ + public void remove(ArbitraryOrderValue value) { + root = value.node.remove(); + } +} diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java new file mode 100644 index 000000000..b5d28d9be --- /dev/null +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java @@ -0,0 +1,8 @@ +package com.github.btrekkie.arbitrary_order_collection; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** A node in an ArbitraryOrderCollection tree. See ArbitraryOrderCollection. */ +class ArbitraryOrderNode extends RedBlackNode { + +} diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java new file mode 100644 index 000000000..c12b995ad --- /dev/null +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java @@ -0,0 +1,19 @@ +package com.github.btrekkie.arbitrary_order_collection; + +/** + * A value in an ArbitraryOrderCollection. To determine the relative order of two values in the same collection, call + * compareTo. + */ +public class ArbitraryOrderValue implements Comparable { + /** The node that establishes this value's relative position. */ + final ArbitraryOrderNode node; + + ArbitraryOrderValue(ArbitraryOrderNode node) { + this.node = node; + } + + @Override + public int compareTo(ArbitraryOrderValue other) { + return node.compareTo(other.node); + } +} diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java new file mode 100644 index 000000000..9d855d17d --- /dev/null +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java @@ -0,0 +1,72 @@ +package com.github.btrekkie.arbitrary_order_collection.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; + +import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderCollection; +import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderValue; + +public class ArbitraryOrderCollectionTest { + /** Tests ArbitraryOrderCollection. */ + @Test + public void test() { + ArbitraryOrderCollection collection = new ArbitraryOrderCollection(); + List values1 = new ArrayList(5); + ArbitraryOrderValue value = collection.createValue(); + assertEquals(0, value.compareTo(value)); + values1.add(value); + for (int i = 0; i < 4; i++) { + values1.add(collection.createValue()); + } + Collections.sort(values1); + List values2 = new ArrayList(10); + for (int i = 0; i < 10; i++) { + value = collection.createValue(); + values2.add(value); + } + for (int i = 0; i < 5; i++) { + collection.remove(values2.get(2 * i)); + } + assertEquals(0, values1.get(0).compareTo(values1.get(0))); + assertTrue(values1.get(0).compareTo(values1.get(1)) < 0); + assertTrue(values1.get(1).compareTo(values1.get(0)) > 0); + assertTrue(values1.get(4).compareTo(values1.get(2)) > 0); + assertTrue(values1.get(0).compareTo(values1.get(4)) < 0); + + collection = new ArbitraryOrderCollection(); + values1 = new ArrayList(1000); + for (int i = 0; i < 1000; i++) { + value = collection.createValue(); + values1.add(value); + } + for (int i = 0; i < 500; i++) { + collection.remove(values1.get(2 * i)); + } + values2 = new ArrayList(500); + for (int i = 0; i < 500; i++) { + values2.add(values1.get(2 * i + 1)); + } + for (int i = 0; i < 500; i++) { + values2.get(0).compareTo(values2.get(i)); + } + Collections.sort(values2); + for (int i = 0; i < 500; i++) { + collection.createValue(); + } + for (int i = 0; i < 499; i++) { + assertTrue(values2.get(i).compareTo(values2.get(i + 1)) < 0); + assertTrue(values2.get(i + 1).compareTo(values2.get(i)) > 0); + } + for (int i = 1; i < 500; i++) { + assertEquals(0, values2.get(i).compareTo(values2.get(i))); + assertTrue(values2.get(0).compareTo(values2.get(i)) < 0); + assertTrue(values2.get(i).compareTo(values2.get(0)) > 0); + } + } +} diff --git a/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java b/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java new file mode 100644 index 000000000..d5e88624e --- /dev/null +++ b/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java @@ -0,0 +1,60 @@ +package com.github.btrekkie.interval_tree; + +/** + * An interval tree data structure, which supports adding or removing an interval and finding an arbitrary interval that + * contains a specified value. + */ +/* The interval tree is ordered in ascending order of the start an interval, with ties broken by the end of the + * interval. Each node is augmented with the maximum ending value of an interval in the subtree rooted at the node. + */ +public class IntervalTree { + /** The root node of the tree. */ + private IntervalTreeNode root = IntervalTreeNode.LEAF; + + /** Adds the specified interval to this. */ + public void addInterval(IntervalTreeInterval interval) { + root = root.insert(new IntervalTreeNode(interval), true, null); + } + + /** + * Removes the specified interval from this, if it is present. + * @param interval The interval. + * @return Whether the interval was present. + */ + public boolean removeInterval(IntervalTreeInterval interval) { + IntervalTreeNode node = root; + while (!node.isLeaf()) { + if (interval.start < node.interval.start) { + node = node.left; + } else if (interval.start > node.interval.start) { + node = node.right; + } else if (interval.end < node.interval.end) { + node = node.left; + } else if (interval.end > node.interval.end) { + node = node.right; + } else { + root = node.remove(); + return true; + } + } + return false; + } + + /** + * Returns an aribtrary IntervalTreeInterval in this that contains the specified value. Returns null if there is no + * such interval. + */ + public IntervalTreeInterval findInterval(double value) { + IntervalTreeNode node = root; + while (!node.isLeaf()) { + if (value >= node.interval.start && value <= node.interval.end) { + return node.interval; + } else if (value <= node.left.maxEnd) { + node = node.left; + } else { + node = node.right; + } + } + return null; + } +} diff --git a/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java new file mode 100644 index 000000000..e9f38b9d9 --- /dev/null +++ b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java @@ -0,0 +1,28 @@ +package com.github.btrekkie.interval_tree; + +/** + * An inclusive range of values [start, end]. Two intervals are equal if they have the same starting and ending values. + */ +public class IntervalTreeInterval { + /** The smallest value in the range. */ + public final double start; + + /** The largest value in the range. */ + public final double end; + + public IntervalTreeInterval(double start, double end) { + if (start > end) { + throw new IllegalArgumentException("The end of the range must be at most the start"); + } + this.start = start; + this.end = end; + } + + public boolean equals(Object obj) { + if (!(obj instanceof IntervalTreeInterval)) { + return false; + } + IntervalTreeInterval interval = (IntervalTreeInterval)obj; + return start == interval.start && end == interval.end; + } +} diff --git a/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java new file mode 100644 index 000000000..d75004e77 --- /dev/null +++ b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java @@ -0,0 +1,68 @@ +package com.github.btrekkie.interval_tree; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** + * A node in an IntervalTree. See the comments for the implementation of IntervalTree. Its compareTo method orders + * nodes as suggested in the comments for the implementation of IntervalTree. + */ +class IntervalTreeNode extends RedBlackNode { + /** The dummy leaf node. */ + public static final IntervalTreeNode LEAF = new IntervalTreeNode(); + + /** The interval stored in this node. */ + public IntervalTreeInterval interval; + + /** The maximum ending value of an interval in the subtree rooted at this node. */ + public double maxEnd; + + public IntervalTreeNode(IntervalTreeInterval interval) { + this.interval = interval; + maxEnd = interval.end; + } + + /** Constructs a new dummy leaf node. */ + private IntervalTreeNode() { + interval = null; + maxEnd = Double.NEGATIVE_INFINITY; + } + + @Override + public boolean augment() { + double newMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd)); + if (newMaxEnd == maxEnd) { + return false; + } else { + maxEnd = newMaxEnd; + return true; + } + } + + @Override + public void assertNodeIsValid() { + double expectedMaxEnd; + if (isLeaf()) { + expectedMaxEnd = Double.NEGATIVE_INFINITY; + } else { + expectedMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd)); + } + if (maxEnd != expectedMaxEnd) { + throw new RuntimeException("The node's maxEnd does not match that of the children"); + } + } + + @Override + public int compareTo(IntervalTreeNode other) { + if (interval.start != interval.end) { + return Double.compare(interval.start, other.interval.start); + } else { + return Double.compare(interval.end, other.interval.end); + } + } + + @Override + public void assertSubtreeIsValid() { + super.assertSubtreeIsValid(); + assertOrderIsValid(null); + } +} diff --git a/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java b/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java new file mode 100644 index 000000000..cf0dbdfa6 --- /dev/null +++ b/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java @@ -0,0 +1,48 @@ +package com.github.btrekkie.interval_tree.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.github.btrekkie.interval_tree.IntervalTree; +import com.github.btrekkie.interval_tree.IntervalTreeInterval; + +public class IntervalTreeTest { + /** Tests IntervalTree. */ + @Test + public void test() { + IntervalTree tree = new IntervalTree(); + assertNull(tree.findInterval(0.5)); + assertNull(tree.findInterval(-1)); + tree.addInterval(new IntervalTreeInterval(5, 7)); + tree.addInterval(new IntervalTreeInterval(42, 48)); + tree.addInterval(new IntervalTreeInterval(-1, 2)); + tree.addInterval(new IntervalTreeInterval(6, 12)); + tree.addInterval(new IntervalTreeInterval(21, 23)); + assertTrue(tree.removeInterval(new IntervalTreeInterval(-1, 2))); + assertFalse(tree.removeInterval(new IntervalTreeInterval(-1, 2))); + tree.addInterval(new IntervalTreeInterval(-6, -2)); + assertEquals(new IntervalTreeInterval(6, 12), tree.findInterval(8)); + assertNull(tree.findInterval(0)); + assertEquals(new IntervalTreeInterval(21, 23), tree.findInterval(21)); + assertEquals(new IntervalTreeInterval(42, 48), tree.findInterval(48)); + IntervalTreeInterval interval = tree.findInterval(6.5); + assertTrue(new IntervalTreeInterval(5, 7).equals(interval) || new IntervalTreeInterval(6, 12).equals(interval)); + + tree = new IntervalTree(); + for (int i = 0; i < 500; i++) { + tree.addInterval(new IntervalTreeInterval(2 * i, 2 * i + 1)); + } + for (int i = 0; i < 250; i++) { + tree.removeInterval(new IntervalTreeInterval(4 * i + 2, 4 * i + 3)); + } + assertNull(tree.findInterval(123.5)); + assertEquals(new IntervalTreeInterval(124, 125), tree.findInterval(124.5)); + assertEquals(new IntervalTreeInterval(776, 777), tree.findInterval(776)); + assertEquals(new IntervalTreeInterval(0, 1), tree.findInterval(0.5)); + assertEquals(new IntervalTreeInterval(996, 997), tree.findInterval(997)); + } +} diff --git a/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java b/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java new file mode 100644 index 000000000..4d571aeb1 --- /dev/null +++ b/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java @@ -0,0 +1,171 @@ +package com.github.btrekkie.red_black_node.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Comparator; + +import org.junit.Test; + +/** + * Tests RedBlackNode. Most of the testing for RedBlackNode takes place in TreeListTest, IntervalTreeTest, + * SubArrayMinTest, and ArbitraryOrderCollectionTest, which test realistic use cases of RedBlackNode. TreeListTest + * tests most of the RedBlackNode methods, while IntervalTreeTest tests the "insert" method, SubArrayMinTest tests + * "lca", ArbitraryOrderCollectionTest tests compareTo, and RedBlackNodeTest tests assertSubtreeIsValid() and + * assertOrderIsValid. + */ +public class RedBlackNodeTest { + /** + * Returns whether the subtree rooted at the specified node is valid, as in TestRedBlackNode.assertSubtreeIsValid(). + */ + private boolean isSubtreeValid(TestRedBlackNode node) { + try { + node.assertSubtreeIsValid(); + return true; + } catch (RuntimeException exception) { + return false; + } + } + + /** + * Returns whether the nodes in the subtree rooted at the specified node are ordered correctly, as in + * TestRedBlackNode.assertOrderIsValid. + * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes' + * natural ordering, as in TestRedBlackNode.compare. + */ + private boolean isOrderValid(TestRedBlackNode node, Comparator comparator) { + try { + node.assertOrderIsValid(null); + return true; + } catch (RuntimeException exception) { + return false; + } + } + + /** Tests RedBlackNode.assertSubtreeIsValid() and RedBlackNode.assertOrderIsValid. */ + @Test + public void testAssertIsValid() { + // Create a perfectly balanced tree of height 3 + TestRedBlackNode node0 = new TestRedBlackNode(0); + TestRedBlackNode node1 = new TestRedBlackNode(1); + TestRedBlackNode node2 = new TestRedBlackNode(2); + TestRedBlackNode node3 = new TestRedBlackNode(3); + TestRedBlackNode node4 = new TestRedBlackNode(4); + TestRedBlackNode node5 = new TestRedBlackNode(5); + TestRedBlackNode node6 = new TestRedBlackNode(6); + node0.parent = node1; + node0.left = TestRedBlackNode.LEAF; + node0.right = TestRedBlackNode.LEAF; + node1.parent = node3; + node1.left = node0; + node1.right = node2; + node1.isRed = true; + node2.parent = node1; + node2.left = TestRedBlackNode.LEAF; + node2.right = TestRedBlackNode.LEAF; + node3.left = node1; + node3.right = node5; + node4.parent = node5; + node4.left = TestRedBlackNode.LEAF; + node4.right = TestRedBlackNode.LEAF; + node5.parent = node3; + node5.left = node4; + node5.right = node6; + node5.isRed = true; + node6.parent = node5; + node6.left = TestRedBlackNode.LEAF; + node6.right = TestRedBlackNode.LEAF; + + node3.left = node3; + node3.right = node3; + node3.parent = node3; + assertFalse(isSubtreeValid(node3)); + node3.left = node1; + node3.right = node5; + node3.parent = null; + + node0.parent = node3; + assertFalse(isSubtreeValid(node3)); + node0.parent = node1; + + node1.right = node0; + assertFalse(isSubtreeValid(node3)); + node1.right = node2; + + node5.isRed = false; + assertFalse(isSubtreeValid(node3)); + assertTrue(isSubtreeValid(node5)); + node5.isRed = true; + + node3.isRed = true; + assertFalse(isSubtreeValid(node3)); + assertTrue(isSubtreeValid(node5)); + node3.isRed = false; + + node0.isRed = true; + node2.isRed = true; + node4.isRed = true; + node6.isRed = true; + assertFalse(isSubtreeValid(node3)); + node0.isRed = false; + node2.isRed = false; + node4.isRed = false; + node6.isRed = false; + + TestRedBlackNode.LEAF.isRed = true; + assertFalse(isSubtreeValid(node3)); + TestRedBlackNode.LEAF.isRed = false; + + TestRedBlackNode.LEAF.isValid = false; + assertFalse(isSubtreeValid(node3)); + assertFalse(isSubtreeValid(TestRedBlackNode.LEAF)); + TestRedBlackNode.LEAF.isValid = true; + + node1.isValid = false; + assertFalse(isSubtreeValid(node3)); + node1.isValid = true; + + node3.value = 2; + node2.value = 3; + assertFalse(isOrderValid(node3, null)); + assertFalse( + isOrderValid(node3, new Comparator() { + @Override + public int compare(TestRedBlackNode node1, TestRedBlackNode node2) { + return node1.value - node2.value; + } + })); + node3.value = 3; + node2.value = 2; + + node2.value = 4; + node4.value = 2; + assertFalse(isOrderValid(node3, null)); + node2.value = 2; + node4.value = 4; + + node0.value = 1; + node1.value = 0; + assertFalse(isOrderValid(node3, null)); + node0.value = 0; + node1.value = 1; + + // Do all of the assertions for which the tree is supposed to be valid at the end, to make sure we didn't make a + // mistake undoing any of the modifications + assertTrue(isSubtreeValid(node3)); + assertTrue(isSubtreeValid(node1)); + assertTrue(isSubtreeValid(node0)); + assertTrue(isSubtreeValid(TestRedBlackNode.LEAF)); + assertTrue(isOrderValid(node3, null)); + assertTrue(isOrderValid(node1, null)); + assertTrue(isOrderValid(node0, null)); + assertTrue(isOrderValid(TestRedBlackNode.LEAF, null)); + assertTrue( + isOrderValid(node3, new Comparator() { + @Override + public int compare(TestRedBlackNode node1, TestRedBlackNode node2) { + return node1.value - node2.value; + } + })); + } +} diff --git a/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java b/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java new file mode 100644 index 000000000..8e3b78cd8 --- /dev/null +++ b/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java @@ -0,0 +1,36 @@ +package com.github.btrekkie.red_black_node.test; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** A RedBlackNode for RedBlackNodeTest. */ +class TestRedBlackNode extends RedBlackNode { + /** The dummy leaf node. */ + public static final TestRedBlackNode LEAF = new TestRedBlackNode(); + + /** The value stored in this node. "value" is unspecified if this is a leaf node. */ + public int value; + + /** Whether this node is considered valid, as in assertNodeIsValid(). */ + public boolean isValid = true; + + public TestRedBlackNode(int value) { + this.value = value; + } + + /** Constructs a new dummy leaf node. */ + private TestRedBlackNode() { + + } + + @Override + public void assertNodeIsValid() { + if (!isValid) { + throw new RuntimeException("isValid is false"); + } + } + + @Override + public int compareTo(TestRedBlackNode other) { + return value - other.value; + } +} diff --git a/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java new file mode 100644 index 000000000..63634c9f8 --- /dev/null +++ b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java @@ -0,0 +1,91 @@ +package com.github.btrekkie.sub_array_min; + +/** A list of integers. SubArrayMin provides the ability to quickly determine the minimum value in a given sublist. */ +/* We implement SubArrayMin using a red-black tree augmented by subtree size and minimum value. Using the subtree size + * augmentation, we can find the node at a given index. + */ +public class SubArrayMin { + /** The root node. */ + private SubArrayMinNode root = SubArrayMinNode.LEAF; + + /** Appends the specified value to the end of the list. */ + public void add(int value) { + SubArrayMinNode newNode = new SubArrayMinNode(value); + newNode.left = SubArrayMinNode.LEAF; + newNode.right = SubArrayMinNode.LEAF; + if (root.isLeaf()) { + root = newNode; + newNode.augment(); + } else { + SubArrayMinNode node = root.max(); + node.right = newNode; + newNode.parent = node; + newNode.isRed = true; + root = newNode.fixInsertion(); + } + } + + /** Returns the node for the element with the specified index. Assumes "index" is in the range [0, root.size). */ + private SubArrayMinNode getNode(int index) { + if (index < 0 || index >= root.size) { + throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + ")"); + } + int rank = index; + SubArrayMinNode node = root; + while (rank != node.left.size) { + if (rank < node.left.size) { + node = node.left; + } else { + rank -= node.left.size + 1; + node = node.right; + } + } + return node; + } + + /** + * Returns the minimum value in the subarray starting at index startIndex and ending at index endIndex - 1, + * inclusive. Assumes startIndex < endIndex, and assumes this contains indices startIndex and endIndex - 1. + */ + public int min(int startIndex, int endIndex) { + if (startIndex >= endIndex) { + throw new IllegalArgumentException("The start index must be less than the end index"); + } + SubArrayMinNode start = getNode(startIndex); + SubArrayMinNode end = getNode(endIndex - 1); + SubArrayMinNode lca = start.lca(end); + + int min = Math.min(lca.value, Math.min(start.value, end.value)); + if (start != lca) { + if (start.right.min < min) { + min = start.right.min; + } + for (SubArrayMinNode node = start; node.parent != lca; node = node.parent) { + if (node.parent.left == node) { + if (node.parent.value < min) { + min = node.parent.value; + } + if (node.parent.right.min < min) { + min = node.parent.right.min; + } + } + } + } + if (end != lca) { + if (end.left.min < min) { + min = end.left.min; + } + for (SubArrayMinNode node = end; node.parent != lca; node = node.parent) { + if (node.parent.right == node) { + if (node.parent.value < min) { + min = node.parent.value; + } + if (node.parent.left.min < min) { + min = node.parent.left.min; + } + } + } + } + return min; + } +} diff --git a/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java new file mode 100644 index 000000000..1839b7220 --- /dev/null +++ b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java @@ -0,0 +1,56 @@ +package com.github.btrekkie.sub_array_min; + +import com.github.btrekkie.red_black_node.RedBlackNode; + +/** A node in a SubArrayMin object. See the comments for the implementation of that class. */ +class SubArrayMinNode extends RedBlackNode { + /** The dummy leaf node. */ + public static final SubArrayMinNode LEAF = new SubArrayMinNode(); + + /** The element stored in the node. The value is unspecified if this is a leaf node. */ + public final int value; + + /** The number of elements in the subtree rooted at this node. */ + public int size; + + /** The minimum element in the subtree rooted at this node. This is Integer.MAX_VALUE if this is a leaf node. */ + public int min; + + public SubArrayMinNode(int value) { + this.value = value; + } + + private SubArrayMinNode() { + value = 0; + min = Integer.MAX_VALUE; + } + + @Override + public boolean augment() { + int newSize = left.size + right.size + 1; + int newMin = Math.min(value, Math.min(left.min, right.min)); + if (newSize == size && newMin == min) { + return false; + } else { + size = newSize; + min = newMin; + return true; + } + } + + @Override + public void assertNodeIsValid() { + int expectedSize; + int expectedMin; + if (isLeaf()) { + expectedSize = 0; + expectedMin = Integer.MAX_VALUE; + } else { + expectedSize = left.size + right.size + 1; + expectedMin = Math.min(value, Math.min(left.min, right.min)); + } + if (size != expectedSize || min != expectedMin) { + throw new RuntimeException("The node's size or minimum value does not match that of the children"); + } + } +} diff --git a/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java b/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java new file mode 100644 index 000000000..5daa7e053 --- /dev/null +++ b/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java @@ -0,0 +1,40 @@ +package com.github.btrekkie.sub_array_min.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.github.btrekkie.sub_array_min.SubArrayMin; + +public class SubArrayMinTest { + /** Tests SubArrayMin. */ + @Test + public void test() { + SubArrayMin sam = new SubArrayMin(); + sam.add(12); + sam.add(42); + sam.add(-3); + sam.add(16); + sam.add(5); + sam.add(8); + sam.add(4); + assertEquals(-3, sam.min(0, 7)); + assertEquals(12, sam.min(0, 2)); + assertEquals(-3, sam.min(2, 4)); + assertEquals(12, sam.min(0, 1)); + assertEquals(5, sam.min(3, 6)); + assertEquals(4, sam.min(4, 7)); + + sam = new SubArrayMin(); + for (int i = 0; i < 1000; i++) { + sam.add(-Integer.bitCount(i)); + } + assertEquals(0, sam.min(0, 1)); + assertEquals(-4, sam.min(0, 30)); + assertEquals(-9, sam.min(0, 1000)); + assertEquals(-9, sam.min(123, 777)); + assertEquals(-8, sam.min(777, 888)); + assertEquals(-6, sam.min(777, 788)); + assertEquals(-9, sam.min(900, 1000)); + } +} From 2af6dec3df255b2ce552d166752afec5e3dbf622 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:26:44 -0700 Subject: [PATCH 15/29] switch from maven to gradle --- .classpath | 36 ---- .gitignore | 10 + .idea/.gitignore | 8 - .idea/.name | 1 - .idea/compiler.xml | 16 -- .idea/jarRepositories.xml | 25 --- ...com_github_btrekkie_RedBlackNode_1_0_1.xml | 13 -- .idea/libraries/Maven__junit_junit_4_13_1.xml | 13 -- .../Maven__org_hamcrest_hamcrest_core_1_3.xml | 13 -- .idea/misc.xml | 13 -- .idea/modules.xml | 8 - .idea/vcs.xml | 6 - .project | 23 --- .settings/org.eclipse.jdt.core.prefs | 14 -- .settings/org.eclipse.m2e.core.prefs | 4 - build.gradle | 16 ++ dynamic-connectivity.iml | 16 -- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55190 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 ++++++++++++++++++ gradlew.bat | 84 +++++++++ pom.xml | 58 ------ settings.gradle | 2 + ...nnectivity-0.2.0-jar-with-dependencies.jar | Bin 36161 -> 0 bytes target/dynamic-connectivity-0.2.0.jar | Bin 16947 -> 0 bytes 25 files changed, 289 insertions(+), 267 deletions(-) delete mode 100644 .classpath delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/compiler.xml delete mode 100644 .idea/jarRepositories.xml delete mode 100644 .idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml delete mode 100644 .idea/libraries/Maven__junit_junit_4_13_1.xml delete mode 100644 .idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .project delete mode 100644 .settings/org.eclipse.jdt.core.prefs delete mode 100644 .settings/org.eclipse.m2e.core.prefs create mode 100644 build.gradle delete mode 100644 dynamic-connectivity.iml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 pom.xml create mode 100644 settings.gradle delete mode 100644 target/dynamic-connectivity-0.2.0-jar-with-dependencies.jar delete mode 100644 target/dynamic-connectivity-0.2.0.jar diff --git a/.classpath b/.classpath deleted file mode 100644 index e43402fa4..000000000 --- a/.classpath +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.gitignore b/.gitignore index 118c59c78..abbb1ca40 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,13 @@ hs_err_pid* .DS_Store target/** !target/dynamic-connectivity*.jar + + +# gradle +build/ +.gradle/ +classes/ +*.class + +# intellij +.idea/ diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b81b..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index bd0668ea9..000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -DynamicConnectivity \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml deleted file mode 100644 index f8431b3b7..000000000 --- a/.idea/compiler.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml deleted file mode 100644 index 947ef884c..000000000 --- a/.idea/jarRepositories.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml b/.idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml deleted file mode 100644 index 2dffd9274..000000000 --- a/.idea/libraries/Maven__com_github_btrekkie_RedBlackNode_1_0_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__junit_junit_4_13_1.xml b/.idea/libraries/Maven__junit_junit_4_13_1.xml deleted file mode 100644 index 9fa24fcbd..000000000 --- a/.idea/libraries/Maven__junit_junit_4_13_1.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml b/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml deleted file mode 100644 index f58bbc112..000000000 --- a/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 40d2f15f2..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index c102a020f..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1ddfb..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.project b/.project deleted file mode 100644 index aee6738ae..000000000 --- a/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - DynamicConnectivity - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.jdt.core.javanature - org.eclipse.m2e.core.maven2Nature - - diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 782ba92b7..000000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,14 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=1.7 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs deleted file mode 100644 index f897a7f1c..000000000 --- a/.settings/org.eclipse.m2e.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -activeProfiles= -eclipse.preferences.version=1 -resolveWorkspaceProjects=true -version=1 diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..242b3e28e --- /dev/null +++ b/build.gradle @@ -0,0 +1,16 @@ +plugins { + id 'java' +} + +group 'org.example' +version '1.0-SNAPSHOT' + +sourceCompatibility = 1.8 + +repositories { + mavenCentral() +} + +dependencies { + testCompile group: 'junit', name: 'junit', version: '4.12' +} diff --git a/dynamic-connectivity.iml b/dynamic-connectivity.iml deleted file mode 100644 index 48ef3c32a..000000000 --- a/dynamic-connectivity.iml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..87b738cbd051603d91cc39de6cb000dd98fe6b02 GIT binary patch literal 55190 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 z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..0f8d5937c --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml deleted file mode 100644 index bb5f92c3f..000000000 --- a/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - 4.0.0 - com.github.btrekkie.connectivity - dynamic-connectivity - 0.2.0 - dynamic-connectivity - Data structure for dynamic connectivity in undirected graphs - - - - maven-assembly-plugin - - - package - - single - - - - - - jar-with-dependencies - - - - - maven-compiler-plugin - 3.8.0 - - 1.7 - 1.7 - - - - org.apache.maven.plugins - maven-compiler-plugin - - 8 - 8 - - - - - - - jitpack.io - https://jitpack.io - - - - - junit - junit - 4.13.1 - test - - - \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..c59f52a5d --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'dynamic-connectivity' + diff --git a/target/dynamic-connectivity-0.2.0-jar-with-dependencies.jar b/target/dynamic-connectivity-0.2.0-jar-with-dependencies.jar deleted file mode 100644 index 8fcda1fce400e734cf732726d536c4222a481ebc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36161 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%0ASqkq20+Mj($bx8kwzN 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 BIx+wN diff --git a/target/dynamic-connectivity-0.2.0.jar b/target/dynamic-connectivity-0.2.0.jar deleted file mode 100644 index c042747576813c96ec1fb02d3080176537657136..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16947 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 Date: Wed, 15 Mar 2023 13:30:23 -0700 Subject: [PATCH 16/29] reformat all code --- .../btrekkie/connectivity/Augmentation.java | 8 +- .../btrekkie/connectivity/ConnEdge.java | 10 +- .../btrekkie/connectivity/ConnGraph.java | 88 +++++---- .../btrekkie/connectivity/ConnVertex.java | 11 +- .../btrekkie/connectivity/EulerTourNode.java | 22 ++- .../connectivity/EulerTourVertex.java | 8 +- .../btrekkie/connectivity/VertexInfo.java | 4 +- .../btrekkie/red_black_node/RedBlackNode.java | 184 +++++++++++------- .../btrekkie/red_black_node/Reference.java | 7 +- .../ArbitraryOrderCollection.java | 12 +- .../ArbitraryOrderNode.java | 4 +- .../ArbitraryOrderValue.java | 4 +- .../test/ArbitraryOrderCollectionTest.java | 15 +- .../btrekkie/connectivity/test/SumAndMax.java | 14 +- .../btrekkie/interval_tree/IntervalTree.java | 9 +- .../interval_tree/IntervalTreeInterval.java | 10 +- .../interval_tree/IntervalTreeNode.java | 16 +- .../interval_tree/test/IntervalTreeTest.java | 14 +- .../red_black_node/test/RedBlackNodeTest.java | 37 ++-- .../red_black_node/test/TestRedBlackNode.java | 20 +- .../btrekkie/sub_array_min/SubArrayMin.java | 16 +- .../sub_array_min/SubArrayMinNode.java | 20 +- .../sub_array_min/test/SubArrayMinTest.java | 11 +- 23 files changed, 341 insertions(+), 203 deletions(-) diff --git a/src/main/java/com/github/btrekkie/connectivity/Augmentation.java b/src/main/java/com/github/btrekkie/connectivity/Augmentation.java index 1d2a443c8..4c9a4f8d7 100644 --- a/src/main/java/com/github/btrekkie/connectivity/Augmentation.java +++ b/src/main/java/com/github/btrekkie/connectivity/Augmentation.java @@ -7,7 +7,7 @@ package com.github.btrekkie.connectivity; * combine(combine(combine(A1, A2), A3), A4). In order for an augmentation result to be meaningful, the combining * function must be commutative, meaning combine(x, y) is equivalent to combine(y, x), and associative, meaning * combine(x, combine(y, z)) is equivalent to combine(combine(x, y), z). - * + *

* If a ConnGraph represents a game map, then one example of an augmentation would be the amount of gold accessible from * a certain location. Each vertex would be augmented with the amount of gold in that location, and the combining * function would add the two amounts of gold passed in as arguments. Another example would be the strongest monster @@ -16,18 +16,18 @@ package com.github.btrekkie.connectivity; * in as arguments. A third example would be the number of locations accessible from a given vertex. Each vertex would * be augmented with the number 1, and the combining function would add the two numbers of vertices passed in as * arguments. - * + *

* ConnGraph treats two augmentation values X and Y as interchangeable if they are equal, as in * X != null ? X.equals(Y) : Y == null. The same holds for two combined augmentation values, and for one combined * augmentation value and one augmentation value. - * + *

* See the comments for ConnGraph. */ public interface Augmentation { /** * Returns the result of combining the specified values into one. Each argument is either the augmentation * information associated with a vertex, or the result of a previous call to "combine". - * + *

* Note that a value of null is never passed in to indicate the absence of augmentation information. The fact that * ConnGraph.getVertexAugmentation, for example, may return null when there is no associated augmentation might lead * you to believe that a null argument indicates the absence of augmentation information, but again, it does not. A diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java index 691aa6ed7..1b8ed65e3 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnEdge.java @@ -4,7 +4,7 @@ package com.github.btrekkie.connectivity; * Represents an edge in a ConnGraph, at the level of the edge (i.e. at the lowest level i for which G_i contains the * edge). Every graph edge has exactly one corresponding ConnEdge object, regardless of the number of levels it appears * in. See the comments for the implementation of ConnGraph. - * + *

* ConnEdges are stored in the linked lists suggested by EulerTourVertex.graphListHead and * EulerTourVertex.forestListHead. Each ConnEdge is in two linked lists, so care must be taken when traversing the * linked lists. prev1 and next1 are the links for the list starting at vertex1.graphListHead or vertex1.forestListHead, @@ -14,10 +14,14 @@ package com.github.btrekkie.connectivity; * after next1 is next1.next1, but otherwise, it is next1.next2. */ class ConnEdge { - /** The edge's first endpoint (at the same level as the edge). */ + /** + * The edge's first endpoint (at the same level as the edge). + */ public EulerTourVertex vertex1; - /** The edge's second endpoint (at the same level as the edge). */ + /** + * The edge's second endpoint (at the same level as the edge). + */ public EulerTourVertex vertex2; /** diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index 545760892..793eef0fd 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -1,10 +1,6 @@ package com.github.btrekkie.connectivity; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * Implements an undirected graph with dynamic connectivity. It supports adding and removing edges and determining @@ -12,7 +8,7 @@ import java.util.Map; * amortized time with high probability, while checking whether two vertices are connected takes O(log N) time with high * probability. It uses O(V log V + E) space, where V is the number of vertices and E is the number of edges. Note that * a ConnVertex may appear in multiple ConnGraphs, with a different set of adjacent vertices in each graph. - * + *

* ConnGraph optionally supports arbitrary augmentation. Each vertex may have an associated augmentation, or value. * Given a vertex V, ConnGraph can quickly report the result of combining the augmentations of all of the vertices in * the connected component containing V, using a combining function provided to the constructor. For example, if a @@ -22,12 +18,12 @@ import java.util.Map; * the augmentation takes a constant amount of space. Retrieving the combined augmentation for a connected component * takes O(log N) time with high probability. (Although ConnGraph does not directly support augmenting edges, this can * also be accomplished, by imputing each edge's augmentation to an adjacent vertex.) - * + *

* When a vertex no longer has any adjacent edges, and it has no augmentation information, ConnGraph stops keeping track * of the vertex. This reduces the time and space bounds of the ConnGraph, and it enables the ConnVertex to be garbage * collected. If you know you are finished with a vertex, and that vertex has an augmentation, then you should call * removeVertexAugmentation on the vertex, so that the graph can release it. - * + *

* As a side note, it would be more proper if ConnGraph had a generic type parameter indicating the type of the * augmentation values. However, it is expected that it is more common not to use augmentation, so by not using a type * parameter, we make usage of the ConnGraph class more convenient and less confusing in the common case. @@ -106,7 +102,7 @@ public class ConnGraph { * The maximum number of vertices we can store in a ConnGraph. This is limited by the fact that EulerTourNode.size * is an int. Since the size of an Euler tour tree is one less than twice the number of vertices in the tree, the * number of vertices may be at most (int)((((long)Integer.MAX_VALUE) + 1) / 2). - * + *

* Of course, we could simply change the "size" field to be a long. But more fundamentally, the number of vertices * is limited by the fact that vertexInfo and VertexInfo.edges use HashMaps. Using a HashMap becomes problematic at * around Integer.MAX_VALUE entries. HashMap buckets entries based on 32-bit hash codes, so in principle, it can @@ -115,7 +111,9 @@ public class ConnGraph { */ private static final int MAX_VERTEX_COUNT = 1 << 30; - /** The augmentation function for the graph, if any. */ + /** + * The augmentation function for the graph, if any. + */ private final Augmentation augmentation; /** @@ -139,22 +137,28 @@ public class ConnGraph { */ private int maxVertexInfoSize; - /** Constructs a new ConnGraph with no augmentation. */ + /** + * Constructs a new ConnGraph with no augmentation. + */ public ConnGraph() { augmentation = null; } - /** Constructs an augmented ConnGraph, using the specified function to combine augmentation values. */ + /** + * Constructs an augmented ConnGraph, using the specified function to combine augmentation values. + */ public ConnGraph(Augmentation augmentation) { this.augmentation = augmentation; } - /** Equivalent implementation is contractual. */ + /** + * Equivalent implementation is contractual. + */ private void assertIsAugmented() { if (augmentation == null) { throw new RuntimeException( - "You may only call augmentation-related methods on ConnGraph if the graph is augmented, i.e. if an " + - "Augmentation was passed to the constructor"); + "You may only call augmentation-related methods on ConnGraph if the graph is augmented, i.e. if an " + + "Augmentation was passed to the constructor"); } } @@ -171,8 +175,8 @@ public class ConnGraph { if (vertexInfo.size() == MAX_VERTEX_COUNT) { throw new RuntimeException( - "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + - "storing more than ~2^30 vertices at a time."); + "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + + "storing more than ~2^30 vertices at a time."); } EulerTourVertex eulerTourVertex = new EulerTourVertex(); @@ -215,9 +219,10 @@ public class ConnGraph { * list for an EulerTourVertex that represents the same underlying ConnVertex, but at a higher level. This has the * effect of prepending the list for the lower level to the beginning of the list for the higher level, and * replacing all links to the lower-level vertex in the ConnEdges with links to the higher-level vertex. - * @param head The first node in the list for the higher-level vertex. - * @param lowerHead The first node in the list for the lower-level vertex. - * @param vertex The higher-level vertex. + * + * @param head The first node in the list for the higher-level vertex. + * @param lowerHead The first node in the list for the lower-level vertex. + * @param vertex The higher-level vertex. * @param lowerVertex The lower-level vertex. * @return The head of the combined linked list. */ @@ -257,7 +262,7 @@ public class ConnGraph { /** * Equivalent implementation is contractual. - * + *

* This method is useful for when an EulerTourVertex's lists (graphListHead or forestListHead) or arbitrary visit * change, as these affect the hasGraphEdge and hasForestEdge augmentations. */ @@ -301,7 +306,7 @@ public class ConnGraph { } vertex.graphListHead = - collapseEdgeList(vertex.graphListHead, lowerVertex.graphListHead, vertex, lowerVertex); + collapseEdgeList(vertex.graphListHead, lowerVertex.graphListHead, vertex, lowerVertex); if (lowerVertex.forestListHead != null) { // Change the eulerTourEdge links ConnEdge lowerEdge = lowerVertex.forestListHead; @@ -320,7 +325,7 @@ public class ConnGraph { } vertex.forestListHead = - collapseEdgeList(vertex.forestListHead, lowerVertex.forestListHead, vertex, lowerVertex); + collapseEdgeList(vertex.forestListHead, lowerVertex.forestListHead, vertex, lowerVertex); } } @@ -505,7 +510,9 @@ public class ConnGraph { return new EulerTourEdge(newNode, max); } - /** Removes the specified edge from the Euler tour forest F_i. */ + /** + * Removes the specified edge from the Euler tour forest F_i. + */ private void removeForestEdge(EulerTourEdge edge) { EulerTourNode firstNode; EulerTourNode secondNode; @@ -535,8 +542,9 @@ public class ConnGraph { /** * Adds the specified edge to the edge map for srcInfo (srcInfo.edges). Assumes that the edge is not currently in * the map. - * @param edge The edge. - * @param srcInfo The source vertex's info. + * + * @param edge The edge. + * @param srcInfo The source vertex's info. * @param destVertex The destination vertex, i.e. the edge's key in srcInfo.edges. */ private void addToEdgeMap(ConnEdge edge, VertexInfo srcInfo, ConnVertex destVertex) { @@ -549,6 +557,7 @@ public class ConnGraph { /** * Adds an edge between the specified vertices, if such an edge is not already present. Taken together with * removeEdge, this method takes O(log^2 N) amortized time with high probability. + * * @return Whether there was no edge between the vertices. */ public boolean addEdge(ConnVertex connVertex1, ConnVertex connVertex2) { @@ -557,8 +566,8 @@ public class ConnGraph { } if (vertexInfo.size() >= MAX_VERTEX_COUNT - 1) { throw new RuntimeException( - "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + - "storing more than ~2^30 vertices at a time."); + "Sorry, ConnGraph has too many vertices to perform this operation. ConnGraph does not support " + + "storing more than ~2^30 vertices at a time."); } VertexInfo info1 = ensureInfo(connVertex1); if (info1.edges.containsKey(connVertex2)) { @@ -614,7 +623,7 @@ public class ConnGraph { return; } EulerTourNode node; - for (node = root; node.left.hasForestEdge; node = node.left); + for (node = root; node.left.hasForestEdge; node = node.left) ; while (node != null) { EulerTourVertex vertex = node.vertex; ConnEdge edge = vertex.forestListHead; @@ -657,7 +666,7 @@ public class ConnGraph { // Iterate to the next node with hasForestEdge == true, clearing hasForestEdge as we go if (node.right.hasForestEdge) { - for (node = node.right; node.left.hasForestEdge; node = node.left); + for (node = node.right; node.left.hasForestEdge; node = node.left) ; } else { node.hasForestEdge = false; while (node.parent != null && node.parent.right == node) { @@ -674,6 +683,7 @@ public class ConnGraph { * tree, where i is the level of the tree. This is a "replacement" edge because it replaces the edge that was * previously connecting the two trees. We push any level-i edges we encounter that do not connect to another tree * down to level i - 1, adding them to G_{i - 1}. This method assumes that root.hasForestEdge is false. + * * @param root The root of the tree. * @return The replacement edge, or null if there is no replacement edge. */ @@ -683,7 +693,7 @@ public class ConnGraph { return null; } EulerTourNode node; - for (node = root; node.left.hasGraphEdge; node = node.left); + for (node = root; node.left.hasGraphEdge; node = node.left) ; while (node != null) { EulerTourVertex vertex = node.vertex; ConnEdge edge = vertex.graphListHead; @@ -767,7 +777,7 @@ public class ConnGraph { // Iterate to the next node with hasGraphEdge == true. Note that nodes' hasGraphEdge fields can change as we // push down edges. if (node.right.hasGraphEdge) { - for (node = node.right; node.left.hasGraphEdge; node = node.left); + for (node = node.right; node.left.hasGraphEdge; node = node.left) ; } else { while (node.parent != null && (node.parent.right == node || !node.parent.hasGraphEdge)) { node = node.parent; @@ -798,6 +808,7 @@ public class ConnGraph { /** * Removes the edge between the specified vertices, if there is such an edge. Taken together with addEdge, this * method takes O(log^2 N) amortized time with high probability. + * * @return Whether there was an edge between the vertices. */ public boolean removeEdge(ConnVertex vertex1, ConnVertex vertex2) { @@ -912,7 +923,9 @@ public class ConnGraph { return info2 != null && info1.vertex.arbitraryVisit.root() == info2.vertex.arbitraryVisit.root(); } - /** Returns the vertices that are directly adjacent to the specified vertex. */ + /** + * Returns the vertices that are directly adjacent to the specified vertex. + */ public Collection adjacentVertices(ConnVertex vertex) { VertexInfo info = vertexInfo.get(vertex); if (info != null) { @@ -925,12 +938,12 @@ public class ConnGraph { /** * Sets the augmentation associated with the specified vertex. This method takes O(log N) time with high * probability. - * + *

* Note that passing a null value for the second argument is not the same as removing the augmentation. For that, * you need to call removeVertexAugmentation. * * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any - * associated augmentation. + * associated augmentation. */ public Object setVertexAugmentation(ConnVertex connVertex, Object vertexAugmentation) { assertIsAugmented(); @@ -952,8 +965,9 @@ public class ConnGraph { /** * Removes any augmentation associated with the specified vertex. This method takes O(log N) time with high * probability. + * * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any - * associated augmentation. + * associated augmentation. */ public Object removeVertexAugmentation(ConnVertex connVertex) { assertIsAugmented(); @@ -1127,7 +1141,7 @@ public class ConnGraph { private void optimizeGraphEdges() { for (VertexInfo info : vertexInfo.values()) { EulerTourVertex vertex; - for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex); + for (vertex = info.vertex; vertex.lowerVertex != null; vertex = vertex.lowerVertex) ; while (vertex != null) { EulerTourNode node = vertex.arbitraryVisit; ConnEdge edge = vertex.graphListHead; diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java index 41d30c836..49e55413a 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java @@ -2,9 +2,13 @@ package com.github.btrekkie.connectivity; import java.util.Random; -/** A vertex in a ConnGraph. See the comments for ConnGraph. */ +/** + * A vertex in a ConnGraph. See the comments for ConnGraph. + */ public class ConnVertex { - /** The thread-local random number generator we use by default to set the "hash" field. */ + /** + * The thread-local random number generator we use by default to set the "hash" field. + */ private static final ThreadLocal random = new ThreadLocal() { @Override protected Random initialValue() { @@ -24,8 +28,9 @@ public class ConnVertex { /** * Constructs a new ConnVertex. + * * @param random The random number generator to use to produce a random hash code. ConnGraph relies on random hash - * codes for its performance guarantees. + * codes for its performance guarantees. */ public ConnVertex(Random random) { hash = random.nextInt(); diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java index 9d1f068f9..356f98733 100644 --- a/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourNode.java @@ -7,13 +7,19 @@ import com.github.btrekkie.red_black_node.RedBlackNode; * ConnGraph. */ class EulerTourNode extends RedBlackNode { - /** The dummy leaf node. */ + /** + * The dummy leaf node. + */ public static final EulerTourNode LEAF = new EulerTourNode(null, null); - /** The vertex this node visits. */ + /** + * The vertex this node visits. + */ public final EulerTourVertex vertex; - /** The number of nodes in the subtree rooted at this node. */ + /** + * The number of nodes in the subtree rooted at this node. + */ public int size; /** @@ -53,13 +59,15 @@ class EulerTourNode extends RedBlackNode { this.augmentationFunc = augmentationFunc; } - /** Like augment(), but only updates the augmentation fields hasGraphEdge and hasForestEdge. */ + /** + * Like augment(), but only updates the augmentation fields hasGraphEdge and hasForestEdge. + */ public boolean augmentFlags() { boolean newHasGraphEdge = - left.hasGraphEdge || right.hasGraphEdge || (vertex.arbitraryVisit == this && vertex.graphListHead != null); + left.hasGraphEdge || right.hasGraphEdge || (vertex.arbitraryVisit == this && vertex.graphListHead != null); boolean newHasForestEdge = - left.hasForestEdge || right.hasForestEdge || - (vertex.arbitraryVisit == this && vertex.forestListHead != null); + left.hasForestEdge || right.hasForestEdge || + (vertex.arbitraryVisit == this && vertex.forestListHead != null); if (newHasGraphEdge == hasGraphEdge && newHasForestEdge == hasForestEdge) { return false; } else { diff --git a/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java index 2b02940b0..e2412e189 100644 --- a/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java +++ b/src/main/java/com/github/btrekkie/connectivity/EulerTourVertex.java @@ -35,9 +35,13 @@ class EulerTourVertex { */ public ConnEdge forestListHead; - /** The augmentation associated with this vertex, if any. This is null instead if higherVertex != null. */ + /** + * The augmentation associated with this vertex, if any. This is null instead if higherVertex != null. + */ public Object augmentation; - /** Whether there is any augmentation associated with this vertex. This is false instead if higherVertex != null. */ + /** + * Whether there is any augmentation associated with this vertex. This is false instead if higherVertex != null. + */ public boolean hasAugmentation; } diff --git a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java index 1593a2040..19c172ed1 100644 --- a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java +++ b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java @@ -8,7 +8,9 @@ import java.util.Map; * a given graph, regardless of how many levels the vertex is in. See the comments for the implementation of ConnGraph. */ class VertexInfo { - /** The representation of the vertex in the highest level. */ + /** + * The representation of the vertex in the highest level. + */ public EulerTourVertex vertex; /** diff --git a/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java index 51cf7c5c0..1484562cd 100644 --- a/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java +++ b/src/main/java/com/github/btrekkie/red_black_node/RedBlackNode.java @@ -3,11 +3,7 @@ package com.github.btrekkie.red_black_node; import java.lang.reflect.Array; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; +import java.util.*; /** * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). Compared to a class like Java's @@ -15,20 +11,20 @@ import java.util.Set; * clients to directly observe and manipulate the structure of the tree. This gives clients flexibility, although it * also enables them to violate the red-black or BST properties. The RedBlackNode class provides methods for performing * various standard operations, such as insertion and removal. - * + *

* Unlike most implementations of binary search trees, RedBlackNode supports arbitrary augmentation. By subclassing * RedBlackNode, clients can add arbitrary data and augmentation information to each node. For example, if we were to * use a RedBlackNode subclass to implement a sorted set, the subclass would have a field storing an element in the set. * If we wanted to keep track of the number of non-leaf nodes in each subtree, we would store this as a "size" field and * override augment() to update this field. All RedBlackNode methods (such as "insert" and remove()) call augment() as * necessary to correctly maintain the augmentation information, unless otherwise indicated. - * + *

* The values of the tree are stored in the non-leaf nodes. RedBlackNode does not support use cases where values must be * stored in the leaf nodes. It is recommended that all of the leaf nodes in a given tree be the same (black) * RedBlackNode instance, to save space. The root of an empty tree is a leaf node, as opposed to null. - * + *

* For reference, a red-black tree is a binary search tree satisfying the following properties: - * + *

* - Every node is colored red or black. * - The leaf nodes, which are dummy nodes that do not store any values, are colored black. * - The root is black. @@ -36,11 +32,13 @@ import java.util.Set; * - Every path from the root to a leaf contains the same number of black nodes. * * @param The type of node in the tree. For example, we might have - * "class FooNode extends RedBlackNode>". + * "class FooNode extends RedBlackNode>". * @author Bill Jacobs */ public abstract class RedBlackNode> implements Comparable { - /** A Comparator that compares Comparable elements using their natural order. */ + /** + * A Comparator that compares Comparable elements using their natural order. + */ private static final Comparator> NATURAL_ORDER = new Comparator>() { @Override public int compare(Comparable value1, Comparable value2) { @@ -48,16 +46,24 @@ public abstract class RedBlackNode> implements Compara } }; - /** The parent of this node, if any. "parent" is null if this is a leaf node. */ + /** + * The parent of this node, if any. "parent" is null if this is a leaf node. + */ public N parent; - /** The left child of this node. "left" is null if this is a leaf node. */ + /** + * The left child of this node. "left" is null if this is a leaf node. + */ public N left; - /** The right child of this node. "right" is null if this is a leaf node. */ + /** + * The right child of this node. "right" is null if this is a leaf node. + */ public N right; - /** Whether the node is colored red, as opposed to black. */ + /** + * Whether the node is colored red, as opposed to black. + */ public boolean isRed; /** @@ -65,12 +71,12 @@ public abstract class RedBlackNode> implements Compara * example, if we augment each node by subtree size (the number of non-leaf nodes in the subtree), this method would * set the size field of this node to be equal to the size field of the left child plus the size field of the right * child plus one. - * + *

* "Augmentation information" is information that we can compute about a subtree rooted at some node, preferably * based only on the augmentation information in the node's two children and the information in the node. Examples * of augmentation information are the sum of the values in a subtree and the number of non-leaf nodes in a subtree. * Augmentation information may not depend on the colors of the nodes. - * + *

* This method returns whether the augmentation information in any of the ancestors of this node might have been * affected by changes in this subtree since the last call to augment(). In the usual case, where the augmentation * information depends only on the information in this node and the augmentation information in its immediate @@ -79,7 +85,7 @@ public abstract class RedBlackNode> implements Compara * calling augment() differed from the size field of the left child plus the size field of the right child plus one. * False positives are permitted. The return value is unspecified if we have not called augment() on this node * before. - * + *

* This method may assume that this is not a leaf node. It may not assume that the augmentation information stored * in any of the tree's nodes is correct. However, if the augmentation information stored in all of the node's * descendants is correct, then the augmentation information stored in this node must be correct after calling @@ -94,7 +100,7 @@ public abstract class RedBlackNode> implements Compara * of RedBlackNode. For example, if this stores the size of the subtree rooted at this node, this should throw a * RuntimeException if the size field of this is not equal to the size field of the left child plus the size field * of the right child plus one. Note that we may call this on a leaf node. - * + *

* assertSubtreeIsValid() calls assertNodeIsValid() on each node, or at least starts to do so until it detects a * problem. assertNodeIsValid() should assume the node is in a tree that satisfies all properties common to all * red-black trees, as assertSubtreeIsValid() is responsible for such checks. assertNodeIsValid() should be @@ -107,58 +113,68 @@ public abstract class RedBlackNode> implements Compara } - /** Returns whether this is a leaf node. */ + /** + * Returns whether this is a leaf node. + */ public boolean isLeaf() { return left == null; } - /** Returns the root of the tree that contains this node. */ + /** + * Returns the root of the tree that contains this node. + */ public N root() { @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; while (node.parent != null) { node = node.parent; } return node; } - /** Returns the first node in the subtree rooted at this node, if any. */ + /** + * Returns the first node in the subtree rooted at this node, if any. + */ public N min() { if (isLeaf()) { return null; } @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; while (!node.left.isLeaf()) { node = node.left; } return node; } - /** Returns the last node in the subtree rooted at this node, if any. */ + /** + * Returns the last node in the subtree rooted at this node, if any. + */ public N max() { if (isLeaf()) { return null; } @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; while (!node.right.isLeaf()) { node = node.right; } return node; } - /** Returns the node immediately before this in the tree that contains this node, if any. */ + /** + * Returns the node immediately before this in the tree that contains this node, if any. + */ public N predecessor() { if (!left.isLeaf()) { N node; - for (node = left; !node.right.isLeaf(); node = node.right); + for (node = left; !node.right.isLeaf(); node = node.right) ; return node; } else if (parent == null) { return null; } else { @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; while (node.parent != null && node.parent.left == node) { node = node.parent; } @@ -166,17 +182,19 @@ public abstract class RedBlackNode> implements Compara } } - /** Returns the node immediately after this in the tree that contains this node, if any. */ + /** + * Returns the node immediately after this in the tree that contains this node, if any. + */ public N successor() { if (!right.isLeaf()) { N node; - for (node = right; !node.left.isLeaf(); node = node.left); + for (node = right; !node.left.isLeaf(); node = node.left) ; return node; } else if (parent == null) { return null; } else { @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; while (node.parent != null && node.parent.right == node) { node = node.parent; } @@ -188,6 +206,7 @@ public abstract class RedBlackNode> implements Compara * Performs a left rotation about this node. This method assumes that !isLeaf() && !right.isLeaf(). It calls * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting * parent's ancestors, because that is normally the responsibility of the caller. + * * @return The return value from calling augment() on the resulting parent. */ public boolean rotateLeft() { @@ -197,7 +216,7 @@ public abstract class RedBlackNode> implements Compara N newParent = right; right = newParent.left; @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; if (!right.isLeaf()) { right.parent = nThis; } @@ -219,6 +238,7 @@ public abstract class RedBlackNode> implements Compara * Performs a right rotation about this node. This method assumes that !isLeaf() && !left.isLeaf(). It calls * augment() on this node and on its resulting parent. However, it does not call augment() on any of the resulting * parent's ancestors, because that is normally the responsibility of the caller. + * * @return The return value from calling augment() on the resulting parent. */ public boolean rotateRight() { @@ -228,7 +248,7 @@ public abstract class RedBlackNode> implements Compara N newParent = left; left = newParent.right; @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; if (!left.isLeaf()) { left.parent = nThis; } @@ -252,6 +272,7 @@ public abstract class RedBlackNode> implements Compara * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs * any rotations by calling rotateLeft() and rotateRight(). This method is more efficient than fixInsertion if * "augment" is false or augment() might return false. + * * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment(). */ public void fixInsertionWithoutGettingRoot(boolean augment) { @@ -339,6 +360,7 @@ public abstract class RedBlackNode> implements Compara * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs * any rotations by calling rotateLeft() and rotateRight(). + * * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment(). * @return The root of the resulting tree. */ @@ -352,6 +374,7 @@ public abstract class RedBlackNode> implements Compara * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be * red. node.isRed must initially be true. This method assumes that this is not a leaf node. The method performs * any rotations by calling rotateLeft() and rotateRight(). + * * @return The root of the resulting tree. */ public N fixInsertion() { @@ -359,27 +382,29 @@ public abstract class RedBlackNode> implements Compara return root(); } - /** Returns a Comparator that compares instances of N using their natural order, as in N.compareTo. */ + /** + * Returns a Comparator that compares instances of N using their natural order, as in N.compareTo. + */ @SuppressWarnings({"rawtypes", "unchecked"}) private Comparator naturalOrder() { - Comparator comparator = (Comparator)NATURAL_ORDER; - return (Comparator)comparator; + Comparator comparator = (Comparator) NATURAL_ORDER; + return (Comparator) comparator; } /** * Inserts the specified node into the tree rooted at this node. Assumes this is the root. We treat newNode as a * solitary node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed * fields. - * + *

* If it is not efficient or convenient to find the location for a node using a Comparator, then you should manually * add the node to the appropriate location, color it red, and call fixInsertion(). * - * @param newNode The node to insert. + * @param newNode The node to insert. * @param allowDuplicates Whether to insert newNode if there is an equal node in the tree. To check whether we - * inserted newNode, check whether newNode.parent is null and the return value differs from newNode. - * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural - * order, as in N.compareTo. If you are passing null, then you must override the compareTo method, because the - * default implementation requires the nodes to already be in the same tree. + * inserted newNode, check whether newNode.parent is null and the return value differs from newNode. + * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural + * order, as in N.compareTo. If you are passing null, then you must override the compareTo method, because the + * default implementation requires the nodes to already be in the same tree. * @return The root of the resulting tree. */ public N insert(N newNode, boolean allowDuplicates, Comparator comparator) { @@ -387,7 +412,7 @@ public abstract class RedBlackNode> implements Compara throw new IllegalArgumentException("This is not the root of a tree"); } @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; if (isLeaf()) { newNode.isRed = false; newNode.left = nThis; @@ -436,6 +461,7 @@ public abstract class RedBlackNode> implements Compara /** * Moves this node to its successor's former position in the tree and vice versa, i.e. sets the "left", "right", * "parent", and isRed fields of each. This method assumes that this is not a leaf node. + * * @return The node with which we swapped. */ private N swapWithSuccessor() { @@ -458,7 +484,7 @@ public abstract class RedBlackNode> implements Compara } @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; isRed = oldReplacementIsRed; left = oldReplacementLeft; right = oldReplacementRight; @@ -575,7 +601,7 @@ public abstract class RedBlackNode> implements Compara * Removes this node from the tree that contains it. The effect of this method on the fields of this node is * unspecified. This method assumes that this is not a leaf node. This method is more efficient than remove() if * augment() might return false. - * + *

* If the node has two children, we begin by moving the node's successor to its former position, by changing the * successor's "left", "right", "parent", and isRed fields. */ @@ -665,7 +691,7 @@ public abstract class RedBlackNode> implements Compara /** * Removes this node from the tree that contains it. The effect of this method on the fields of this node is * unspecified. This method assumes that this is not a leaf node. - * + *

* If the node has two children, we begin by moving the node's successor to its former position, by changing the * successor's "left", "right", "parent", and isRed fields. * @@ -697,13 +723,14 @@ public abstract class RedBlackNode> implements Compara * "iterator", in iteration order. This method is responsible for setting the "left", "right", "parent", and isRed * fields of the nodes, and calling augment() as appropriate. It ignores the initial values of the "left", "right", * "parent", and isRed fields. + * * @param iterator The nodes. - * @param size The number of nodes. - * @param height The "height" of the subtree's root node above the deepest leaf in the tree that contains it. Since - * insertion fixup is slow if there are too many red nodes and deleteion fixup is slow if there are too few red - * nodes, we compromise and have red nodes at every fourth level. We color a node red iff its "height" is equal - * to 1 mod 4. - * @param leaf The leaf node. + * @param size The number of nodes. + * @param height The "height" of the subtree's root node above the deepest leaf in the tree that contains it. Since + * insertion fixup is slow if there are too many red nodes and deleteion fixup is slow if there are too few red + * nodes, we compromise and have red nodes at every fourth level. We color a node red iff its "height" is equal + * to 1 mod 4. + * @param leaf The leaf node. * @return The root of the subtree. */ private static > N createTree( @@ -735,8 +762,9 @@ public abstract class RedBlackNode> implements Compara * method is responsible for setting the "left", "right", "parent", and isRed fields of the nodes (excluding * "leaf"), and calling augment() as appropriate. It ignores the initial values of the "left", "right", "parent", * and isRed fields. + * * @param nodes The nodes. - * @param leaf The leaf node. + * @param leaf The leaf node. * @return The root of the tree. */ public static > N createTree(Collection nodes, N leaf) { @@ -762,7 +790,7 @@ public abstract class RedBlackNode> implements Compara * all of these nodes. This method destroys the trees rooted at "this" and "last". We treat "pivot" as a solitary * node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed fields. * This method assumes that this node and "last" are the roots of their respective trees. - * + *

* This method takes O(log N) time. It is more efficient than inserting "pivot" and then calling concatenate(last). * It is considerably more efficient than inserting "pivot" and all of the nodes in "last". */ @@ -783,7 +811,7 @@ public abstract class RedBlackNode> implements Compara // Compute the black height of the trees int firstBlackHeight = 0; @SuppressWarnings("unchecked") - N first = (N)this; + N first = (N) this; for (N node = first; node != null; node = node.right) { if (!node.isRed) { firstBlackHeight++; @@ -868,7 +896,7 @@ public abstract class RedBlackNode> implements Compara return last; } else if (last.isLeaf()) { @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; return nThis; } else { N node = last.min(); @@ -884,6 +912,7 @@ public abstract class RedBlackNode> implements Compara * destructive, meaning it does not preserve the original tree. It assumes that this node is the root and is in the * same tree as splitNode. It takes O(log N) time. It is considerably more efficient than removing all of the * nodes at or after splitNode and then creating a new tree from those nodes. + * * @param The node at which to split the tree. * @return An array consisting of the resulting trees. */ @@ -929,7 +958,7 @@ public abstract class RedBlackNode> implements Compara } @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; N first = null; N firstParent = null; N last = null; @@ -1082,7 +1111,7 @@ public abstract class RedBlackNode> implements Compara last.augment(); @SuppressWarnings("unchecked") - N[] result = (N[])Array.newInstance(getClass(), 2); + N[] result = (N[]) Array.newInstance(getClass(), 2); result[0] = first; result[1] = last; return result; @@ -1092,7 +1121,7 @@ public abstract class RedBlackNode> implements Compara * Returns the lowest common ancestor of this node and "other" - the node that is an ancestor of both and is not the * parent of a node that is an ancestor of both. Assumes that this is in the same tree as "other". Assumes that * neither "this" nor "other" is a leaf node. This method may return "this" or "other". - * + *

* Note that while it is possible to compute the lowest common ancestor in O(P) time, where P is the length of the * path from this node to "other", the "lca" method is not guaranteed to take O(P) time. If your application * requires this, then you should write your own lowest common ancestor method. @@ -1114,7 +1143,7 @@ public abstract class RedBlackNode> implements Compara // Go up to nodes of the same depth @SuppressWarnings("unchecked") - N parent = (N)this; + N parent = (N) this; N otherParent = other; if (depth <= otherDepth) { for (int i = otherDepth; i > depth; i--) { @@ -1142,10 +1171,10 @@ public abstract class RedBlackNode> implements Compara * Returns an integer comparing the position of this node in the tree that contains it with that of "other". Returns * a negative number if this is earlier, a positive number if this is later, and 0 if this is at the same position. * Assumes that this is in the same tree as "other". Assumes that neither "this" nor "other" is a leaf node. - * + *

* The base class's implementation takes O(log N) time. If a RedBlackNode subclass stores a value used to order the * nodes, then it could override compareTo to compare the nodes' values, which would take O(1) time. - * + *

* Note that while it is possible to compare the positions of two nodes in O(P) time, where P is the length of the * path from this node to "other", the default implementation of compareTo is not guaranteed to take O(P) time. If * your application requires this, then you should write your own comparison method. @@ -1224,7 +1253,9 @@ public abstract class RedBlackNode> implements Compara } } - /** Throws a RuntimeException if the RedBlackNode fields of this are not correct for a leaf node. */ + /** + * Throws a RuntimeException if the RedBlackNode fields of this are not correct for a leaf node. + */ private void assertIsValidLeaf() { if (left != null || right != null || parent != null || isRed) { throw new RuntimeException("A leaf node's \"left\", \"right\", \"parent\", or isRed field is incorrect"); @@ -1234,14 +1265,15 @@ public abstract class RedBlackNode> implements Compara /** * Throws a RuntimeException if the subtree rooted at this node does not satisfy the red-black properties, excluding * the requirement that the root be black, or it contains a repeated node other than a leaf node. + * * @param blackHeight The required number of black nodes in each path from this to a leaf node, including this and - * the leaf node. - * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in - * the subtree rooted at this node to "visited". + * the leaf node. + * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in + * the subtree rooted at this node to "visited". */ private void assertSubtreeIsValidRedBlack(int blackHeight, Set> visited) { @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; if (left == null || right == null) { assertIsValidLeaf(); if (blackHeight != 1) { @@ -1276,7 +1308,9 @@ public abstract class RedBlackNode> implements Compara } } - /** Calls assertNodeIsValid() on every node in the subtree rooted at this node. */ + /** + * Calls assertNodeIsValid() on every node in the subtree rooted at this node. + */ private void assertNodesAreValid() { assertNodeIsValid(); if (left != null) { @@ -1304,7 +1338,7 @@ public abstract class RedBlackNode> implements Compara Set> nodes = new HashSet>(); int blackHeight = 0; @SuppressWarnings("unchecked") - N node = (N)this; + N node = (N) this; while (node != null) { if (!nodes.add(new Reference(node))) { throw new RuntimeException("The tree contains a repeated non-leaf node"); @@ -1333,14 +1367,15 @@ public abstract class RedBlackNode> implements Compara * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order or they * do not lie in the specified range. Assumes that the subtree rooted at this node is a valid binary tree, i.e. it * has no repeated nodes other than leaf nodes. + * * @param comparator A comparator indicating how the nodes should be ordered. - * @param start The lower limit for nodes in the subtree, if any. - * @param end The upper limit for nodes in the subtree, if any. + * @param start The lower limit for nodes in the subtree, if any. + * @param end The upper limit for nodes in the subtree, if any. */ private void assertOrderIsValid(Comparator comparator, N start, N end) { if (!isLeaf()) { @SuppressWarnings("unchecked") - N nThis = (N)this; + N nThis = (N) this; if (start != null && comparator.compare(nThis, start) < 0) { throw new RuntimeException("The nodes are not ordered correctly"); } @@ -1359,8 +1394,9 @@ public abstract class RedBlackNode> implements Compara * Assumes that this is a valid binary tree, i.e. there are no repeated nodes other than leaf nodes. This method is * useful for debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call * assertOrderIsValid. + * * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes' - * natural order, as in N.compareTo. + * natural order, as in N.compareTo. */ public void assertOrderIsValid(Comparator comparator) { if (comparator == null) { diff --git a/src/main/java/com/github/btrekkie/red_black_node/Reference.java b/src/main/java/com/github/btrekkie/red_black_node/Reference.java index 410b9fa28..6fbd9ddeb 100644 --- a/src/main/java/com/github/btrekkie/red_black_node/Reference.java +++ b/src/main/java/com/github/btrekkie/red_black_node/Reference.java @@ -5,10 +5,13 @@ package com.github.btrekkie.red_black_node; /** * Wraps a value using reference equality. In other words, two references are equal only if their values are the same * object instance, as in ==. + * * @param The type of value. */ class Reference { - /** The value this wraps. */ + /** + * The value this wraps. + */ private final T value; public Reference(T value) { @@ -19,7 +22,7 @@ class Reference { if (!(obj instanceof Reference)) { return false; } - Reference reference = (Reference)obj; + Reference reference = (Reference) obj; return value == reference.value; } diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java index 8b5102471..90374ca52 100644 --- a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java @@ -11,7 +11,9 @@ import java.util.Comparator; /* We implement an ArbitraryOrderCollection using a red-black tree. We order the nodes arbitrarily. */ public class ArbitraryOrderCollection { - /** The Comparator for ordering ArbitraryOrderNodes. */ + /** + * The Comparator for ordering ArbitraryOrderNodes. + */ private static final Comparator NODE_COMPARATOR = new Comparator() { @Override public int compare(ArbitraryOrderNode node1, ArbitraryOrderNode node2) { @@ -19,10 +21,14 @@ public class ArbitraryOrderCollection { } }; - /** The root node of the tree. */ + /** + * The root node of the tree. + */ private ArbitraryOrderNode root = new ArbitraryOrderNode(); - /** Adds and returns a new value for ordering. */ + /** + * Adds and returns a new value for ordering. + */ public ArbitraryOrderValue createValue() { ArbitraryOrderNode node = new ArbitraryOrderNode(); root = root.insert(node, true, NODE_COMPARATOR); diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java index b5d28d9be..8a889bb9a 100644 --- a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java @@ -2,7 +2,9 @@ package com.github.btrekkie.arbitrary_order_collection; import com.github.btrekkie.red_black_node.RedBlackNode; -/** A node in an ArbitraryOrderCollection tree. See ArbitraryOrderCollection. */ +/** + * A node in an ArbitraryOrderCollection tree. See ArbitraryOrderCollection. + */ class ArbitraryOrderNode extends RedBlackNode { } diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java index c12b995ad..160c4ee75 100644 --- a/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java @@ -5,7 +5,9 @@ package com.github.btrekkie.arbitrary_order_collection; * compareTo. */ public class ArbitraryOrderValue implements Comparable { - /** The node that establishes this value's relative position. */ + /** + * The node that establishes this value's relative position. + */ final ArbitraryOrderNode node; ArbitraryOrderValue(ArbitraryOrderNode node) { diff --git a/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java b/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java index 9d855d17d..ad36a5c62 100644 --- a/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java +++ b/src/test/java/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java @@ -1,19 +1,20 @@ package com.github.btrekkie.arbitrary_order_collection.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderCollection; +import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderValue; +import org.junit.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.junit.Test; - -import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderCollection; -import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class ArbitraryOrderCollectionTest { - /** Tests ArbitraryOrderCollection. */ + /** + * Tests ArbitraryOrderCollection. + */ @Test public void test() { ArbitraryOrderCollection collection = new ArbitraryOrderCollection(); diff --git a/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java b/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java index 393c3599f..1b2a89b76 100644 --- a/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java +++ b/src/test/java/com/github/btrekkie/connectivity/test/SumAndMax.java @@ -2,14 +2,18 @@ package com.github.btrekkie.connectivity.test; import com.github.btrekkie.connectivity.Augmentation; -/** Stores two values: a sum and a maximum. Used for testing augmentation in ConnGraph. */ +/** + * Stores two values: a sum and a maximum. Used for testing augmentation in ConnGraph. + */ class SumAndMax { - /** An Augmentation that combines two SumAndMaxes into one. */ + /** + * An Augmentation that combines two SumAndMaxes into one. + */ public static final Augmentation AUGMENTATION = new Augmentation() { @Override public Object combine(Object value1, Object value2) { - SumAndMax sumAndMax1 = (SumAndMax)value1; - SumAndMax sumAndMax2 = (SumAndMax)value2; + SumAndMax sumAndMax1 = (SumAndMax) value1; + SumAndMax sumAndMax2 = (SumAndMax) value2; return new SumAndMax(sumAndMax1.sum + sumAndMax2.sum, Math.max(sumAndMax1.max, sumAndMax2.max)); } }; @@ -28,7 +32,7 @@ class SumAndMax { if (!(obj instanceof SumAndMax)) { return false; } - SumAndMax sumAndMax = (SumAndMax)obj; + SumAndMax sumAndMax = (SumAndMax) obj; return sum == sumAndMax.sum && max == sumAndMax.max; } diff --git a/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java b/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java index d5e88624e..aa0204517 100644 --- a/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java +++ b/src/test/java/com/github/btrekkie/interval_tree/IntervalTree.java @@ -8,16 +8,21 @@ package com.github.btrekkie.interval_tree; * interval. Each node is augmented with the maximum ending value of an interval in the subtree rooted at the node. */ public class IntervalTree { - /** The root node of the tree. */ + /** + * The root node of the tree. + */ private IntervalTreeNode root = IntervalTreeNode.LEAF; - /** Adds the specified interval to this. */ + /** + * Adds the specified interval to this. + */ public void addInterval(IntervalTreeInterval interval) { root = root.insert(new IntervalTreeNode(interval), true, null); } /** * Removes the specified interval from this, if it is present. + * * @param interval The interval. * @return Whether the interval was present. */ diff --git a/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java index e9f38b9d9..182eb37c6 100644 --- a/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java +++ b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeInterval.java @@ -4,10 +4,14 @@ package com.github.btrekkie.interval_tree; * An inclusive range of values [start, end]. Two intervals are equal if they have the same starting and ending values. */ public class IntervalTreeInterval { - /** The smallest value in the range. */ + /** + * The smallest value in the range. + */ public final double start; - /** The largest value in the range. */ + /** + * The largest value in the range. + */ public final double end; public IntervalTreeInterval(double start, double end) { @@ -22,7 +26,7 @@ public class IntervalTreeInterval { if (!(obj instanceof IntervalTreeInterval)) { return false; } - IntervalTreeInterval interval = (IntervalTreeInterval)obj; + IntervalTreeInterval interval = (IntervalTreeInterval) obj; return start == interval.start && end == interval.end; } } diff --git a/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java index d75004e77..7b6bffaa7 100644 --- a/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java +++ b/src/test/java/com/github/btrekkie/interval_tree/IntervalTreeNode.java @@ -7,13 +7,19 @@ import com.github.btrekkie.red_black_node.RedBlackNode; * nodes as suggested in the comments for the implementation of IntervalTree. */ class IntervalTreeNode extends RedBlackNode { - /** The dummy leaf node. */ + /** + * The dummy leaf node. + */ public static final IntervalTreeNode LEAF = new IntervalTreeNode(); - /** The interval stored in this node. */ + /** + * The interval stored in this node. + */ public IntervalTreeInterval interval; - /** The maximum ending value of an interval in the subtree rooted at this node. */ + /** + * The maximum ending value of an interval in the subtree rooted at this node. + */ public double maxEnd; public IntervalTreeNode(IntervalTreeInterval interval) { @@ -21,7 +27,9 @@ class IntervalTreeNode extends RedBlackNode { maxEnd = interval.end; } - /** Constructs a new dummy leaf node. */ + /** + * Constructs a new dummy leaf node. + */ private IntervalTreeNode() { interval = null; maxEnd = Double.NEGATIVE_INFINITY; diff --git a/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java b/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java index cf0dbdfa6..5af2bd6d2 100644 --- a/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java +++ b/src/test/java/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java @@ -1,17 +1,15 @@ package com.github.btrekkie.interval_tree.test; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - import com.github.btrekkie.interval_tree.IntervalTree; import com.github.btrekkie.interval_tree.IntervalTreeInterval; +import org.junit.Test; + +import static org.junit.Assert.*; public class IntervalTreeTest { - /** Tests IntervalTree. */ + /** + * Tests IntervalTree. + */ @Test public void test() { IntervalTree tree = new IntervalTree(); diff --git a/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java b/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java index 4d571aeb1..b487e54fe 100644 --- a/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java +++ b/src/test/java/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java @@ -1,11 +1,11 @@ package com.github.btrekkie.red_black_node.test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import org.junit.Test; import java.util.Comparator; -import org.junit.Test; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; /** * Tests RedBlackNode. Most of the testing for RedBlackNode takes place in TreeListTest, IntervalTreeTest, @@ -30,8 +30,9 @@ public class RedBlackNodeTest { /** * Returns whether the nodes in the subtree rooted at the specified node are ordered correctly, as in * TestRedBlackNode.assertOrderIsValid. + * * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes' - * natural ordering, as in TestRedBlackNode.compare. + * natural ordering, as in TestRedBlackNode.compare. */ private boolean isOrderValid(TestRedBlackNode node, Comparator comparator) { try { @@ -42,7 +43,9 @@ public class RedBlackNodeTest { } } - /** Tests RedBlackNode.assertSubtreeIsValid() and RedBlackNode.assertOrderIsValid. */ + /** + * Tests RedBlackNode.assertSubtreeIsValid() and RedBlackNode.assertOrderIsValid. + */ @Test public void testAssertIsValid() { // Create a perfectly balanced tree of height 3 @@ -129,12 +132,12 @@ public class RedBlackNodeTest { node2.value = 3; assertFalse(isOrderValid(node3, null)); assertFalse( - isOrderValid(node3, new Comparator() { - @Override - public int compare(TestRedBlackNode node1, TestRedBlackNode node2) { - return node1.value - node2.value; - } - })); + isOrderValid(node3, new Comparator() { + @Override + public int compare(TestRedBlackNode node1, TestRedBlackNode node2) { + return node1.value - node2.value; + } + })); node3.value = 3; node2.value = 2; @@ -161,11 +164,11 @@ public class RedBlackNodeTest { assertTrue(isOrderValid(node0, null)); assertTrue(isOrderValid(TestRedBlackNode.LEAF, null)); assertTrue( - isOrderValid(node3, new Comparator() { - @Override - public int compare(TestRedBlackNode node1, TestRedBlackNode node2) { - return node1.value - node2.value; - } - })); + isOrderValid(node3, new Comparator() { + @Override + public int compare(TestRedBlackNode node1, TestRedBlackNode node2) { + return node1.value - node2.value; + } + })); } } diff --git a/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java b/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java index 8e3b78cd8..86ab1cb3a 100644 --- a/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java +++ b/src/test/java/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java @@ -2,22 +2,32 @@ package com.github.btrekkie.red_black_node.test; import com.github.btrekkie.red_black_node.RedBlackNode; -/** A RedBlackNode for RedBlackNodeTest. */ +/** + * A RedBlackNode for RedBlackNodeTest. + */ class TestRedBlackNode extends RedBlackNode { - /** The dummy leaf node. */ + /** + * The dummy leaf node. + */ public static final TestRedBlackNode LEAF = new TestRedBlackNode(); - /** The value stored in this node. "value" is unspecified if this is a leaf node. */ + /** + * The value stored in this node. "value" is unspecified if this is a leaf node. + */ public int value; - /** Whether this node is considered valid, as in assertNodeIsValid(). */ + /** + * Whether this node is considered valid, as in assertNodeIsValid(). + */ public boolean isValid = true; public TestRedBlackNode(int value) { this.value = value; } - /** Constructs a new dummy leaf node. */ + /** + * Constructs a new dummy leaf node. + */ private TestRedBlackNode() { } diff --git a/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java index 63634c9f8..7c8e1c4ba 100644 --- a/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java +++ b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMin.java @@ -1,14 +1,20 @@ package com.github.btrekkie.sub_array_min; -/** A list of integers. SubArrayMin provides the ability to quickly determine the minimum value in a given sublist. */ +/** + * A list of integers. SubArrayMin provides the ability to quickly determine the minimum value in a given sublist. + */ /* We implement SubArrayMin using a red-black tree augmented by subtree size and minimum value. Using the subtree size * augmentation, we can find the node at a given index. */ public class SubArrayMin { - /** The root node. */ + /** + * The root node. + */ private SubArrayMinNode root = SubArrayMinNode.LEAF; - /** Appends the specified value to the end of the list. */ + /** + * Appends the specified value to the end of the list. + */ public void add(int value) { SubArrayMinNode newNode = new SubArrayMinNode(value); newNode.left = SubArrayMinNode.LEAF; @@ -25,7 +31,9 @@ public class SubArrayMin { } } - /** Returns the node for the element with the specified index. Assumes "index" is in the range [0, root.size). */ + /** + * Returns the node for the element with the specified index. Assumes "index" is in the range [0, root.size). + */ private SubArrayMinNode getNode(int index) { if (index < 0 || index >= root.size) { throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + ")"); diff --git a/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java index 1839b7220..e76860f09 100644 --- a/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java +++ b/src/test/java/com/github/btrekkie/sub_array_min/SubArrayMinNode.java @@ -2,18 +2,28 @@ package com.github.btrekkie.sub_array_min; import com.github.btrekkie.red_black_node.RedBlackNode; -/** A node in a SubArrayMin object. See the comments for the implementation of that class. */ +/** + * A node in a SubArrayMin object. See the comments for the implementation of that class. + */ class SubArrayMinNode extends RedBlackNode { - /** The dummy leaf node. */ + /** + * The dummy leaf node. + */ public static final SubArrayMinNode LEAF = new SubArrayMinNode(); - /** The element stored in the node. The value is unspecified if this is a leaf node. */ + /** + * The element stored in the node. The value is unspecified if this is a leaf node. + */ public final int value; - /** The number of elements in the subtree rooted at this node. */ + /** + * The number of elements in the subtree rooted at this node. + */ public int size; - /** The minimum element in the subtree rooted at this node. This is Integer.MAX_VALUE if this is a leaf node. */ + /** + * The minimum element in the subtree rooted at this node. This is Integer.MAX_VALUE if this is a leaf node. + */ public int min; public SubArrayMinNode(int value) { diff --git a/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java b/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java index 5daa7e053..95266b387 100644 --- a/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java +++ b/src/test/java/com/github/btrekkie/sub_array_min/test/SubArrayMinTest.java @@ -1,13 +1,14 @@ package com.github.btrekkie.sub_array_min.test; +import com.github.btrekkie.sub_array_min.SubArrayMin; +import org.junit.Test; + import static org.junit.Assert.assertEquals; -import org.junit.Test; - -import com.github.btrekkie.sub_array_min.SubArrayMin; - public class SubArrayMinTest { - /** Tests SubArrayMin. */ + /** + * Tests SubArrayMin. + */ @Test public void test() { SubArrayMin sam = new SubArrayMin(); From 9870cbddc3437ea7277d12046073f40f30eb1416 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:33:22 -0700 Subject: [PATCH 17/29] supposedly need to bump junit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 242b3e28e..4cec80d2b 100644 --- a/build.gradle +++ b/build.gradle @@ -12,5 +12,5 @@ repositories { } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile group: 'junit', name: 'junit', version: '4.13.1' } From c177d6c70844fe50a4771a159f8caf33542d0bca Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:39:27 -0700 Subject: [PATCH 18/29] need more heap --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 4cec80d2b..34c70f7e6 100644 --- a/build.gradle +++ b/build.gradle @@ -13,4 +13,9 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.13.1' + compile 'it.unimi.dsi:fastutil:8.5.12' } + +test { + maxHeapSize = "8g" +} \ No newline at end of file From 52f795c3ae78b7baa4ca425372e9341fd68b7a05 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:43:39 -0700 Subject: [PATCH 19/29] ram usage of empty hashmap buckets doesn't affect my use case --- .../btrekkie/connectivity/ConnGraph.java | 27 ------------------- .../btrekkie/connectivity/VertexInfo.java | 8 ------ 2 files changed, 35 deletions(-) diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index 793eef0fd..40c411847 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -130,13 +130,6 @@ public class ConnGraph { */ private int maxLogVertexCountSinceRebuild; - /** - * The maximum number of entries in vertexInfo since the last time we copied that field to a new HashMap. We do this - * when the number of vertices drops sufficiently, in order to limit space usage. (The capacity of a HashMap is not - * automatically reduced as the number of entries decreases, so we have to limit space usage manually.) - */ - private int maxVertexInfoSize; - /** * Constructs a new ConnGraph with no augmentation. */ @@ -191,7 +184,6 @@ public class ConnGraph { if (vertexInfo.size() > 1 << maxLogVertexCountSinceRebuild) { maxLogVertexCountSinceRebuild++; } - maxVertexInfoSize = Math.max(maxVertexInfoSize, vertexInfo.size()); return info; } @@ -202,13 +194,6 @@ public class ConnGraph { */ private void remove(ConnVertex vertex) { vertexInfo.remove(vertex); - if (4 * vertexInfo.size() <= maxVertexInfoSize && maxVertexInfoSize > 12) { - // The capacity of a HashMap is not automatically reduced as the number of entries decreases. To avoid - // violating our O(V log V + E) space guarantee, we copy vertexInfo to a new HashMap, which will have a - // suitable capacity. - vertexInfo = new HashMap(vertexInfo); - maxVertexInfoSize = vertexInfo.size(); - } if (vertexInfo.size() << REBUILD_CHANGE <= 1 << maxLogVertexCountSinceRebuild) { rebuild(); } @@ -549,9 +534,6 @@ public class ConnGraph { */ private void addToEdgeMap(ConnEdge edge, VertexInfo srcInfo, ConnVertex destVertex) { srcInfo.edges.put(destVertex, edge); - if (srcInfo.edges.size() > srcInfo.maxEdgeCountSinceRebuild) { - srcInfo.maxEdgeCountSinceRebuild = srcInfo.edges.size(); - } } /** @@ -794,14 +776,6 @@ public class ConnGraph { */ private ConnEdge removeFromEdgeMap(VertexInfo srcInfo, ConnVertex destVertex) { ConnEdge edge = srcInfo.edges.remove(destVertex); - if (edge != null && 4 * srcInfo.edges.size() <= srcInfo.maxEdgeCountSinceRebuild && - srcInfo.maxEdgeCountSinceRebuild > 6) { - // The capacity of a HashMap is not automatically reduced as the number of entries decreases. To avoid - // violating our O(V log V + E) space guarantee, we copy srcInfo.edges to a new HashMap, which will have a - // suitable capacity. - srcInfo.edges = new HashMap(srcInfo.edges); - srcInfo.maxEdgeCountSinceRebuild = srcInfo.edges.size(); - } return edge; } @@ -1058,7 +1032,6 @@ public class ConnGraph { // space vertexInfo = new HashMap(); maxLogVertexCountSinceRebuild = 0; - maxVertexInfoSize = 0; } /** diff --git a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java index 19c172ed1..8fbbbe903 100644 --- a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java +++ b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java @@ -20,14 +20,6 @@ class VertexInfo { */ public Map edges = new HashMap(); - /** - * The maximum number of entries in "edges" since the last time we "rebuilt" that field. When the number of edges - * drops sufficiently, we rebuild "edges" by copying its contents to a new HashMap. We do this to ensure that - * "edges" uses O(K) space, where K is the number of vertices adjacent to this. (The capacity of a HashMap is not - * automatically reduced as the number of entries decreases, so we have to limit space usage manually.) - */ - public int maxEdgeCountSinceRebuild; - public VertexInfo(EulerTourVertex vertex) { this.vertex = vertex; } From 49b620a7cf20a10596a512ce167bd2153e425b64 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 13:59:57 -0700 Subject: [PATCH 20/29] just checking if a long map could be faster, doesnt seem to matter (yet?) --- .../btrekkie/connectivity/ConnGraph.java | 85 +++++++++++++++---- .../btrekkie/connectivity/ConnVertex.java | 10 ++- .../btrekkie/connectivity/VertexInfo.java | 4 +- .../connectivity/test/ConnGraphTest.java | 10 +-- 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java index 40c411847..2a813153f 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnGraph.java @@ -1,5 +1,9 @@ package com.github.btrekkie.connectivity; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; + import java.util.*; /** @@ -122,7 +126,7 @@ public class ConnGraph { * expected time and O(log N / log log N) time with high probability, because vertexInfo is a HashMap, and * ConnVertex.hashCode() returns a random integer. */ - private Map vertexInfo = new HashMap(); + private Long2ObjectOpenHashMap vertexInfo = new Long2ObjectOpenHashMap<>(); /** * Ceiling of log base 2 of the maximum number of vertices in this graph since the last rebuild. This is 0 if that @@ -160,7 +164,7 @@ public class ConnGraph { * this graph (i.e. it does not have an entry in vertexInfo), this method adds it to the graph, and creates a * VertexInfo object for it. */ - private VertexInfo ensureInfo(ConnVertex vertex) { + private VertexInfo ensureInfo(long vertex) { VertexInfo info = vertexInfo.get(vertex); if (info != null) { return info; @@ -192,7 +196,7 @@ public class ConnGraph { * adjacent edges and does not have any augmentation information. This method assumes that the vertex is currently * in the graph. */ - private void remove(ConnVertex vertex) { + private void remove(long vertex) { vertexInfo.remove(vertex); if (vertexInfo.size() << REBUILD_CHANGE <= 1 << maxLogVertexCountSinceRebuild) { rebuild(); @@ -532,17 +536,22 @@ public class ConnGraph { * @param srcInfo The source vertex's info. * @param destVertex The destination vertex, i.e. the edge's key in srcInfo.edges. */ - private void addToEdgeMap(ConnEdge edge, VertexInfo srcInfo, ConnVertex destVertex) { + private void addToEdgeMap(ConnEdge edge, VertexInfo srcInfo, long destVertex) { srcInfo.edges.put(destVertex, edge); } + @Deprecated + public boolean addEdge(ConnVertex connVertex1, ConnVertex connVertex2) { + return addEdge(connVertex1.getIdentity(), connVertex2.getIdentity()); + } + /** * Adds an edge between the specified vertices, if such an edge is not already present. Taken together with * removeEdge, this method takes O(log^2 N) amortized time with high probability. * * @return Whether there was no edge between the vertices. */ - public boolean addEdge(ConnVertex connVertex1, ConnVertex connVertex2) { + public boolean addEdge(long connVertex1, long connVertex2) { if (connVertex1 == connVertex2) { throw new IllegalArgumentException("Self-loops are not allowed"); } @@ -774,18 +783,23 @@ public class ConnGraph { * Removes the edge from srcInfo to destVertex from the edge map for srcInfo (srcInfo.edges), if it is present. * Returns the edge that we removed, if any. */ - private ConnEdge removeFromEdgeMap(VertexInfo srcInfo, ConnVertex destVertex) { + private ConnEdge removeFromEdgeMap(VertexInfo srcInfo, long destVertex) { ConnEdge edge = srcInfo.edges.remove(destVertex); return edge; } + @Deprecated + public boolean removeEdge(ConnVertex vertex1, ConnVertex vertex2) { + return removeEdge(vertex1.getIdentity(), vertex2.getIdentity()); + } + /** * Removes the edge between the specified vertices, if there is such an edge. Taken together with addEdge, this * method takes O(log^2 N) amortized time with high probability. * * @return Whether there was an edge between the vertices. */ - public boolean removeEdge(ConnVertex vertex1, ConnVertex vertex2) { + public boolean removeEdge(long vertex1, long vertex2) { if (vertex1 == vertex2) { throw new IllegalArgumentException("Self-loops are not allowed"); } @@ -881,11 +895,16 @@ public class ConnGraph { return true; } + @Deprecated + public boolean connected(ConnVertex vertex1, ConnVertex vertex2) { + return connected(vertex1.getIdentity(), vertex2.getIdentity()); + } + /** * Returns whether the specified vertices are connected - whether there is a path between them. Returns true if * vertex1 == vertex2. This method takes O(log N) time with high probability. */ - public boolean connected(ConnVertex vertex1, ConnVertex vertex2) { + public boolean connected(long vertex1, long vertex2) { if (vertex1 == vertex2) { return true; } @@ -900,15 +919,20 @@ public class ConnGraph { /** * Returns the vertices that are directly adjacent to the specified vertex. */ - public Collection adjacentVertices(ConnVertex vertex) { + public LongSet adjacentVertices(long vertex) { VertexInfo info = vertexInfo.get(vertex); if (info != null) { - return new ArrayList(info.edges.keySet()); + return info.edges.keySet(); } else { - return Collections.emptyList(); + return LongSets.emptySet(); } } + @Deprecated + public Object setVertexAugmentation(ConnVertex connVertex, Object vertexAugmentation) { + return setVertexAugmentation(connVertex.getIdentity(), vertexAugmentation); + } + /** * Sets the augmentation associated with the specified vertex. This method takes O(log N) time with high * probability. @@ -919,7 +943,7 @@ public class ConnGraph { * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any * associated augmentation. */ - public Object setVertexAugmentation(ConnVertex connVertex, Object vertexAugmentation) { + public Object setVertexAugmentation(long connVertex, Object vertexAugmentation) { assertIsAugmented(); EulerTourVertex vertex = ensureInfo(connVertex).vertex; Object oldAugmentation = vertex.augmentation; @@ -936,6 +960,11 @@ public class ConnGraph { return oldAugmentation; } + @Deprecated + public Object removeVertexAugmentation(ConnVertex connVertex) { + return removeVertexAugmentation(connVertex.getIdentity()); + } + /** * Removes any augmentation associated with the specified vertex. This method takes O(log N) time with high * probability. @@ -943,7 +972,7 @@ public class ConnGraph { * @return The augmentation that was previously associated with the vertex. Returns null if it did not have any * associated augmentation. */ - public Object removeVertexAugmentation(ConnVertex connVertex) { + public Object removeVertexAugmentation(long connVertex) { assertIsAugmented(); VertexInfo info = vertexInfo.get(connVertex); if (info == null) { @@ -966,11 +995,16 @@ public class ConnGraph { return oldAugmentation; } + @Deprecated + public Object getVertexAugmentation(ConnVertex vertex) { + return getVertexAugmentation(vertex.getIdentity()); + } + /** * Returns the augmentation associated with the specified vertex. Returns null if it does not have any associated * augmentation. At present, this method takes constant expected time. Contrast with getComponentAugmentation. */ - public Object getVertexAugmentation(ConnVertex vertex) { + public Object getVertexAugmentation(long vertex) { assertIsAugmented(); VertexInfo info = vertexInfo.get(vertex); if (info != null) { @@ -980,12 +1014,17 @@ public class ConnGraph { } } + @Deprecated + public Object getComponentAugmentation(ConnVertex vertex) { + return getComponentAugmentation(vertex.getIdentity()); + } + /** * Returns the result of combining the augmentations associated with all of the vertices in the connected component * containing the specified vertex. Returns null if none of those vertices has any associated augmentation. This * method takes O(log N) time with high probability. */ - public Object getComponentAugmentation(ConnVertex vertex) { + public Object getComponentAugmentation(long vertex) { assertIsAugmented(); VertexInfo info = vertexInfo.get(vertex); if (info != null) { @@ -995,11 +1034,16 @@ public class ConnGraph { } } + @Deprecated + public boolean vertexHasAugmentation(ConnVertex vertex) { + return vertexHasAugmentation(vertex.getIdentity()); + } + /** * Returns whether the specified vertex has any associated augmentation. At present, this method takes constant * expected time. Contrast with componentHasAugmentation. */ - public boolean vertexHasAugmentation(ConnVertex vertex) { + public boolean vertexHasAugmentation(long vertex) { assertIsAugmented(); VertexInfo info = vertexInfo.get(vertex); if (info != null) { @@ -1009,11 +1053,16 @@ public class ConnGraph { } } + @Deprecated + public boolean componentHasAugmentation(ConnVertex vertex) { + return componentHasAugmentation(vertex.getIdentity()); + } + /** * Returns whether any of the vertices in the connected component containing the specified vertex has any associated * augmentation. This method takes O(log N) time with high probability. */ - public boolean componentHasAugmentation(ConnVertex vertex) { + public boolean componentHasAugmentation(long vertex) { assertIsAugmented(); VertexInfo info = vertexInfo.get(vertex); if (info != null) { @@ -1030,7 +1079,7 @@ public class ConnGraph { public void clear() { // Note that we construct a new HashMap rather than calling vertexInfo.clear() in order to ensure a reduction in // space - vertexInfo = new HashMap(); + vertexInfo = new Long2ObjectOpenHashMap<>(); maxLogVertexCountSinceRebuild = 0; } diff --git a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java index 49e55413a..4e562f73f 100644 --- a/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java +++ b/src/main/java/com/github/btrekkie/connectivity/ConnVertex.java @@ -20,10 +20,10 @@ public class ConnVertex { * A randomly generated integer to use as the return value of hashCode(). ConnGraph relies on random hash codes for * its performance guarantees. */ - private final int hash; + private final long hash; public ConnVertex() { - hash = random.get().nextInt(); + hash = random.get().nextLong(); } /** @@ -33,11 +33,15 @@ public class ConnVertex { * codes for its performance guarantees. */ public ConnVertex(Random random) { - hash = random.nextInt(); + hash = random.nextLong(); } @Override public int hashCode() { + throw new UnsupportedOperationException(); + } + + public long getIdentity(){ return hash; } } diff --git a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java index 8fbbbe903..ff9ff0b08 100644 --- a/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java +++ b/src/main/java/com/github/btrekkie/connectivity/VertexInfo.java @@ -1,5 +1,7 @@ package com.github.btrekkie.connectivity; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + import java.util.HashMap; import java.util.Map; @@ -18,7 +20,7 @@ class VertexInfo { * vertex. Lookups take O(1) expected time and O(log N / log log N) time with high probability, because "edges" is a * HashMap, and ConnVertex.hashCode() returns a random integer. */ - public Map edges = new HashMap(); + public Long2ObjectOpenHashMap edges = new Long2ObjectOpenHashMap<>(); public VertexInfo(EulerTourVertex vertex) { this.vertex = vertex; diff --git a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java index 9c90d981e..52482802b 100644 --- a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java +++ b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java @@ -160,7 +160,7 @@ public class ConnGraphTest { assertFalse(graph.connected(vertex1, vertex8)); assertFalse(graph.connected(vertex6, vertex9)); - Set expectedAdjVertices = new HashSet(); + /*Set expectedAdjVertices = new HashSet(); expectedAdjVertices.add(vertex2); expectedAdjVertices.add(vertex3); expectedAdjVertices.add(vertex4); @@ -170,7 +170,7 @@ public class ConnGraphTest { expectedAdjVertices.add(vertex7); assertEquals(expectedAdjVertices, new HashSet(graph.adjacentVertices(vertex6))); assertEquals(Collections.singleton(vertex8), new HashSet(graph.adjacentVertices(vertex9))); - assertEquals(Collections.emptySet(), new HashSet(graph.adjacentVertices(new ConnVertex(random)))); + assertEquals(Collections.emptySet(), new HashSet(graph.adjacentVertices(new ConnVertex(random))));*/ graph.optimize(); List vertices = new ArrayList(1000); @@ -1089,8 +1089,8 @@ public class ConnGraphTest { assertTrue(graph.connected(vertex4, vertex5)); assertFalse(graph.connected(vertex1, vertex4)); - assertEquals(Collections.singleton(vertex3), new HashSet(graph.adjacentVertices(vertex2))); - assertTrue(graph.adjacentVertices(vertex1).isEmpty()); - assertTrue(graph.adjacentVertices(vertex6).isEmpty()); + //assertEquals(Collections.singleton(vertex3), new HashSet(graph.adjacentVertices(vertex2))); + //assertTrue(graph.adjacentVertices(vertex1).isEmpty()); + //assertTrue(graph.adjacentVertices(vertex6).isEmpty()); } } From 7ee6b40815aaa0f2007885fcfd999ec97f7086d6 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 14:12:14 -0700 Subject: [PATCH 21/29] and i guess this should also use long instead of connvertex --- .../connectivity/test/ConnGraphTest.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java index 52482802b..d2d3b175a 100644 --- a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java +++ b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java @@ -4,7 +4,9 @@ import com.github.btrekkie.connectivity.ConnGraph; import com.github.btrekkie.connectivity.ConnVertex; import org.junit.Test; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; import static org.junit.Assert.*; @@ -12,6 +14,9 @@ import static org.junit.Assert.*; * behavior more predictable. That way, there are consistent test results, and test failures are easier to debug. */ public class ConnGraphTest { + private static long toLong(int x, int y) { + return (long) x & 0xffffffffL | ((long) y & 0xffffffffL) << 32; + } @Test public void testPerformanceOnRepeatedConnectionAndDisconnection() { @@ -24,10 +29,10 @@ public class ConnGraphTest { long setup = System.currentTimeMillis(); ConnGraph graph = new ConnGraph((a, b) -> (Integer) a + (Integer) b); int SZ = 1000; - ConnVertex[] vertices = new ConnVertex[SZ * SZ]; - for (int i = 0; i < vertices.length; i++) { - vertices[i] = new ConnVertex(); - graph.setVertexAugmentation(vertices[i], 1); + for (int x = 0; x < SZ; x++) { + for (int y = 0; y < SZ; y++) { + graph.setVertexAugmentation(toLong(x, y), 1); // much faster to do this earlier idk + } } for (int x = 0; x < SZ; x++) { if (x % 100 == 0) { @@ -35,15 +40,15 @@ public class ConnGraphTest { } for (int y = 0; y < SZ; y++) { if (y != SZ - 1 && y != SZ / 2) { // leave graph disconnected in the center - two big areas with no connection - graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + graph.addEdge(toLong(x, y), toLong(x, y + 1)); } if (x != SZ - 1) { - graph.addEdge(vertices[x * SZ + y], vertices[(x + 1) * SZ + y]); + graph.addEdge(toLong(x, y), toLong(x + 1, y)); } } } System.out.println("Setup took " + (System.currentTimeMillis() - setup)); - System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Part size " + graph.getComponentAugmentation(0)); /* // previous test for cutting in half @@ -62,11 +67,11 @@ public class ConnGraphTest { long start = System.currentTimeMillis(); int x = SZ / 2; int y = SZ / 2; - graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + graph.addEdge(toLong(x, y), toLong(x, y + 1)); long afterAdd = System.currentTimeMillis(); - System.out.println("Connected size " + graph.getComponentAugmentation(vertices[0])); - graph.removeEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); - System.out.println("Disconnected size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Connected size " + graph.getComponentAugmentation(0)); + graph.removeEdge(toLong(x, y), toLong(x, y + 1)); + System.out.println("Disconnected size " + graph.getComponentAugmentation(0)); System.out.println("Took " + (System.currentTimeMillis() - afterAdd) + " to remove and " + (afterAdd - start) + " to add"); } @@ -78,34 +83,34 @@ public class ConnGraphTest { long start = System.currentTimeMillis(); int y = SZ / 2; for (int x = 0; x < SZ; x++) { - graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + graph.addEdge(toLong(x, y), toLong(x, y + 1)); } long afterAdd = System.currentTimeMillis(); - System.out.println("Connected size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Connected size " + graph.getComponentAugmentation(0)); for (int x = 0; x < SZ; x++) { - graph.removeEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + graph.removeEdge(toLong(x, y), toLong(x, y + 1)); } - System.out.println("Disconnected size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Disconnected size " + graph.getComponentAugmentation(0)); System.out.println("Took " + (System.currentTimeMillis() - afterAdd) + " to remove and " + (afterAdd - start) + " to add"); } // entire column - System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Part size " + graph.getComponentAugmentation(0)); { int y = SZ / 2; for (int x = 0; x < SZ; x++) { - graph.addEdge(vertices[x * SZ + y], vertices[x * SZ + y + 1]); + graph.addEdge(toLong(x, y), toLong(x, y + 1)); } } - System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Part size " + graph.getComponentAugmentation(0)); long col = System.currentTimeMillis(); { int x = SZ / 2; for (int y = 0; y < SZ; y++) { - graph.removeEdge(vertices[x * SZ + y], vertices[(x + 1) * SZ + y]); + graph.removeEdge(toLong(x, y), toLong(x + 1, y)); } } - System.out.println("Part size " + graph.getComponentAugmentation(vertices[0])); + System.out.println("Part size " + graph.getComponentAugmentation(0)); System.out.println("Column took " + (System.currentTimeMillis() - col)); } } From 236d171d151d44fafae12426043f8667380f1a84 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 14:53:19 -0700 Subject: [PATCH 22/29] just in case anyone is curious, check in my partial impl from last year of euler tour forests over splay nodes (it was too intimidating to fully reimpl on top of a red-black or avl tree) --- .../com/github/leijurv/EulerTourForest.java | 866 ++++++++++++++++++ .../connectivity/test/ConnGraphTest.java | 2 + 2 files changed, 868 insertions(+) create mode 100644 src/main/java/com/github/leijurv/EulerTourForest.java diff --git a/src/main/java/com/github/leijurv/EulerTourForest.java b/src/main/java/com/github/leijurv/EulerTourForest.java new file mode 100644 index 000000000..c2d0b9554 --- /dev/null +++ b/src/main/java/com/github/leijurv/EulerTourForest.java @@ -0,0 +1,866 @@ +package com.github.leijurv; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.IntStream; + +public class EulerTourForest { + + static long parentWalks; + static long parentCalls; + // https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf + // https://u.cs.biu.ac.il/~rodittl/p723-holm.pdf + // https://infoscience.epfl.ch/record/99353/files/HenzingerK99.pdf + // https://en.wikipedia.org/wiki/Dynamic_connectivity#The_Level_structure + + public final BSTNode[] loopbacks; // a (v,v) fake edge is created per vertex and maintained at the appropriate location in the tree, to allow fast lookups of where "v" is, without having to rely on the presence or absence of tree edges connected to v + + public EulerTourForest(int n) { + this.loopbacks = IntStream.range(0, n).mapToObj(SplayNode::new).toArray(BSTNode[]::new); + } + + public TreeEdge link(int vertA, int vertB) { + if (connected(vertA, vertB)) { + throw new IllegalStateException(); + } + BSTNode outgoing = new SplayNode(vertA, vertB); + BSTNode incoming = new SplayNode(vertB, vertA); + BSTNode.barrelRollToLowest(loopbacks[vertA]); + BSTNode.barrelRollToLowest(loopbacks[vertB]); + BSTNode.concatenate(loopbacks[vertA], outgoing); // (a,a) ... (a,b) + BSTNode.concatenate(outgoing, loopbacks[vertB]); // (a,a) ... (a,b) (b,b) ... + BSTNode.concatenate(loopbacks[vertB], incoming); // (a,a) ... (a,b) (b,b) ... (b,a) + return new TreeEdge(incoming, outgoing); + } + + public void cut(TreeEdge edge) { + if (edge.owner() != this) { + throw new IllegalArgumentException(); + } + if (edge.cut) { + return; + } + edge.cut = true; + BSTNode outgoing = edge.left; + BSTNode incoming = edge.right; + if (incoming.src != outgoing.dst || incoming.dst != outgoing.src || outgoing == incoming || incoming.src == incoming.dst) { + throw new IllegalStateException(); + } + if (!connected(incoming.src, incoming.dst)) { + throw new IllegalStateException(); + } + BSTNode.barrelRollToLowest(outgoing); + BSTNodePair disconnected = incoming.disconnect(Direction.RIGHT); + if (disconnected.left.walkDescendant(Direction.LEFT) != outgoing || disconnected.right.walkDescendant(Direction.LEFT) != incoming) { + throw new IllegalStateException(); + } + if (loopbacks[incoming.src].walkAncestor() != disconnected.left || loopbacks[outgoing.src].walkAncestor() != disconnected.right) { + throw new IllegalStateException(); + } + outgoing.remove(); + incoming.remove(); + } + + public boolean connected(int vertA, int vertB) { + return loopbacks[vertA].walkAncestor() == loopbacks[vertB].walkAncestor(); + } + + public int size(int vert) { + return loopbacks[vert].walkAncestor().loopbackSize; + } + + public int[] walk(int vert) { + BSTNode root = loopbacks[vert].walkAncestor(); + int[] ret = new int[root.loopbackSize]; + int[] idx = {0}; + root.walk(node -> { + if (node.isLoopback()) { + ret[idx[0]++] = node.src; + } + }); + return ret; + } + + // redblacknode impl deleted here + + public static class SplayNode extends BSTNode { + + private SplayNode() { + super(); + } + + private SplayNode(int same) { + super(same); + } + + private SplayNode(int src, int dst) { + super(src, dst); + } + + public void splay() { + while (parent != null) { + BSTNode grandparent = parent.parent; + Direction myDir = whichChildAmI(); + if (grandparent != null) { + Direction parentDir = parent.whichChildAmI(); + if (myDir == parentDir) { + // see "Zig-zig step" of https://en.wikipedia.org/wiki/Splay_tree + grandparent.splayRotate(parentDir); + parent.splayRotate(myDir); + } else { + // see "Zig-zag step" of https://en.wikipedia.org/wiki/Splay_tree + parent.splayRotate(myDir); + grandparent.splayRotate(parentDir); + } + } else { + parent.splayRotate(myDir); + if (parent != null) { + throw new IllegalStateException(); + } + } + } + } + + @Override + protected BSTNode concatenateRoots(BSTNode right) { + if (this.parent != null || right.parent != null || this == right) { + throw new IllegalStateException(); + } + SplayNode newRoot = (SplayNode) right.walkDescendant(Direction.LEFT); + newRoot.splay(); + // newRoot is now the root of a splay tree containing all of right + // and, it is the LOWEST value of right, and left is assumed to be less than it, so now we can just attach left as its left child + // (since it is the lowest value of right, it cannot possibly have any left child already) + if (newRoot.getChild(Direction.LEFT) != null) { + throw new IllegalStateException(); + } + newRoot.setChild(Direction.LEFT, this); + newRoot.childUpdated(); + return newRoot; + } + + @Override + protected BSTNodePair disconnect(Direction remainOnSide) { + splay(); // SIGNIFICANTLY easier to split the tree in half, if we are the root node + BSTNode left = this; + BSTNode right = this; + // simple detach of one side + if (remainOnSide == Direction.LEFT) { + right = rightChild; + rightChild = null; + childUpdated(); + if (right != null) { + right.sizeMustBeAccurate(); + right.parent = null; + } + } else { + left = leftChild; + leftChild = null; + childUpdated(); + if (left != null) { + left.sizeMustBeAccurate(); + left.parent = null; + } + } + return new BSTNodePair(left, right); + } + + public void remove() { + splay(); + if (leftChild != null) { + leftChild.parent = null; + } + if (rightChild != null) { + rightChild.parent = null; + } + if (leftChild != null && rightChild != null) { + leftChild.concatenateRoots(rightChild); + } + } + } + + public abstract static class BSTNode { + + protected final int src; + protected final int dst; + + protected BSTNode leftChild; + protected BSTNode rightChild; + protected BSTNode parent; + + protected int loopbackSize; + + private BSTNode() { + this(-1); + } + + private BSTNode(int same) { + this.src = same; + this.dst = same; + this.loopbackSize = 1; + } + + private BSTNode(int src, int dst) { + if (src == dst) { + throw new IllegalArgumentException(); + } + this.src = src; + this.dst = dst; + this.loopbackSize = 0; + } + + protected void splayRotate(Direction dir) { + rotateSecret(dir); + } + + protected void redBlackRotate(Direction dir) { + rotateSecret(dir.opposite()); // i guess they just use different conventions? + } + + private void rotateSecret(Direction dir) { + // promote my "dir" child to my level, swap myself down to that level + + // see "Zig step" of https://en.wikipedia.org/wiki/Splay_tree + BSTNode child = this.getChild(dir); + BSTNode replacementChild = child.getChild(dir.opposite()); // stays at the same level, is just rotated to the other side of the tree + this.setChild(dir, replacementChild); + if (parent == null) { + child.parent = null; + } else { + parent.setChild(whichChildAmI(), child); + } + child.setChild(dir.opposite(), this); // e.g. my left child now has me as their right child + childUpdated(); + parent.childUpdated(); + } + + public static BSTNode concatenate(BSTNode left, BSTNode right) { + if (left == null) { + throw new IllegalStateException(); + } + if (right == null) { + return left; + } + return left.walkAncestor().concatenateRoots(right.walkAncestor()); + } + + protected void afterSwap(BSTNode other) { + + } + + protected void swapLocationWith(BSTNode other) { + if (other == this) { + throw new IllegalStateException(); + } + if (other.parent == this) { + other.swapLocationWith(this); + return; + } + if (parent == other) { + // grandpa + // other + // this otherChild + // left right + // and we want that to become + // grandpa + // this + // other otherChild + // left right + Direction dir = whichChildAmI(); // LEFT, in the above example + BSTNode otherChild = other.getChild(dir.opposite()); + BSTNode left = leftChild; + BSTNode right = rightChild; + if (other.parent == null) { // grandpa + parent = null; + } else { + other.parent.setChild(other.whichChildAmI(), this); + } + setChild(dir, other); + setChild(dir.opposite(), otherChild); + other.setChild(Direction.LEFT, left); + other.setChild(Direction.RIGHT, right); + other.childUpdated(); + childUpdated(); + } else { + Direction myDir = parent == null ? null : whichChildAmI(); + Direction otherDir = other.parent == null ? null : other.whichChildAmI(); + BSTNode tmpLeft = leftChild; + BSTNode tmpRight = rightChild; + + BSTNode tmpParent = parent; + if (other.parent == null) { // grandpa + parent = null; + } else { + other.parent.setChild(otherDir, this); + } + if (tmpParent == null) { + other.parent = null; + } else { + tmpParent.setChild(myDir, other); + } + + setChild(Direction.LEFT, other.leftChild); + setChild(Direction.RIGHT, other.rightChild); + other.setChild(Direction.LEFT, tmpLeft); + other.setChild(Direction.RIGHT, tmpRight); + calcSize(); + if (parent != null) { + parent.bubbleUpSize(); + } + other.calcSize(); + if (other.parent != null) { + other.parent.bubbleUpSize(); + } + } + afterSwap(other); + } + + protected abstract BSTNode concatenateRoots(BSTNode right); + + public static BSTNode barrelRollToLowest(BSTNode target) { + // 1. chop the tree in half, centered at target + // 2. reattach them in the opposite order + // in other words, "cut the deck but don't riffle" - leave "target" as the first node (NOT necessarily the root node) + BSTNodePair pair = target.disconnect(Direction.RIGHT); + if ((target instanceof SplayNode && pair.right != target) || pair.right.parent != null) { // splay to root only happens with a splay tree, obviously + throw new IllegalStateException(); + } + // target is now the lowest (leftmost) element of pair.right + BSTNode ret = BSTNode.concatenate(pair.right, pair.left); // target is now first, and everything else is still in order :D + // use concatenate and not concatenateRoots because pair.left could be null + if (ret == target && pair.left != null) { + throw new IllegalStateException(); + } + return ret; + } + + protected abstract BSTNodePair disconnect(Direction remainOnSide); // chops the tree in half, with "this" remaining on the side "remainOnSide" + + public abstract void remove(); + + protected void bubbleUpSize() { + int ns = calcSize(); + if (loopbackSize != ns) { + loopbackSize = ns; + if (parent != null) { + parent.bubbleUpSize(); + } + } + } + + protected void childUpdated() { + loopbackSize = calcSize(); + } + + protected void sizeMustBeAccurate() { + if (loopbackSize != calcSize()) { + throw new IllegalStateException(); + } + } + + protected int calcSize() { + int size = 0; + if (isLoopback()) { + size++; + } + if (rightChild != null) { + size += rightChild.loopbackSize; + } + if (leftChild != null) { + size += leftChild.loopbackSize; + } + return size; + } + + protected BSTNode getChild(Direction dir) { + return dir == Direction.LEFT ? leftChild : rightChild; + } + + protected void setChild(Direction dir, BSTNode newChild) { + if (newChild == this) { + throw new IllegalStateException(); + } + if (dir == Direction.LEFT) { + leftChild = newChild; + } else { + rightChild = newChild; + } + if (newChild != null) { + newChild.parent = this; + } + } + + protected Direction whichChildAmI() { + if (parent.leftChild == this) { + return Direction.LEFT; + } + if (parent.rightChild == this) { + return Direction.RIGHT; + } + throw new IllegalStateException(); + } + + protected BSTNode walkDescendant(Direction side) { + BSTNode child = getChild(side); + if (child == null) { + return this; + } else { + return child.walkDescendant(side); + } + } + + protected BSTNode walkAncestor() { + BSTNode walk = this; + while (walk.parent != null) { + parentWalks++; + walk = walk.parent; + } + parentCalls++; + return walk; + /*if (parent == null) { + return this; + } else { + return parent.walkAncestor(); + }*/ + } + + protected void walk(Consumer consumer) { + if (leftChild != null) { + leftChild.walk(consumer); + } + consumer.accept(this); + if (rightChild != null) { + rightChild.walk(consumer); + } + } + + protected BSTNode walkNext() { + if (rightChild != null) { + return rightChild.walkDescendant(Direction.LEFT); + } + BSTNode itr = this; + while (itr.parent != null && itr.whichChildAmI() == Direction.RIGHT) { + itr = itr.parent; + } + return itr.parent; + } + + protected boolean isAlone() { + return parent == null && leftChild == null && rightChild == null; + } + + public boolean isLoopback() { + return src == dst; + } + } + + public enum Direction { // TODO check if proguard converts this to an int + LEFT, RIGHT; + + public Direction opposite() { + return this == LEFT ? RIGHT : LEFT; + } + } + + private static class BSTNodePair { + + final BSTNode left; + final BSTNode right; + + private BSTNodePair(BSTNode left, BSTNode right) { + this.left = left; + this.right = right; + if ((left != null && left.parent != null) || (right != null && right.parent != null)) { + throw new IllegalStateException(); + } + } + } + + public class TreeEdge { + + private boolean cut; + final BSTNode left; + final BSTNode right; + + private TreeEdge(BSTNode left, BSTNode right) { + this.left = left; + this.right = right; + } + + private EulerTourForest owner() { + return EulerTourForest.this; + } + } + + private static void mustEq(BSTNode a, BSTNode b) { + if (a != b) { + throw new IllegalStateException(a + " " + b); + } + } + + public static void sanityCheck() { + for (Direction dir : Direction.values()) { + System.out.println("Testing zig " + dir); + // see "Zig step" of https://en.wikipedia.org/wiki/Splay_tree + SplayNode p = new SplayNode(); + SplayNode x = new SplayNode(); + SplayNode A = new SplayNode(); + SplayNode B = new SplayNode(); + SplayNode C = new SplayNode(); + p.setChild(dir, x); + p.setChild(dir.opposite(), C); + x.setChild(dir, A); + x.setChild(dir.opposite(), B); + + x.splay(); + + mustEq(p.parent, x); + mustEq(p.getChild(dir), B); + mustEq(p.getChild(dir.opposite()), C); + mustEq(x.parent, null); + mustEq(x.getChild(dir), A); + mustEq(x.getChild(dir.opposite()), p); + mustEq(A.parent, x); + mustEq(A.getChild(dir), null); + mustEq(A.getChild(dir.opposite()), null); + mustEq(B.parent, p); + mustEq(B.getChild(dir), null); + mustEq(B.getChild(dir.opposite()), null); + mustEq(C.parent, p); + mustEq(C.getChild(dir), null); + mustEq(C.getChild(dir.opposite()), null); + } + for (Direction dir : Direction.values()) { + System.out.println("Testing zig-zig " + dir); + // see "Zig-zig step" of https://en.wikipedia.org/wiki/Splay_tree + SplayNode g = new SplayNode(); + SplayNode p = new SplayNode(); + SplayNode x = new SplayNode(); + SplayNode A = new SplayNode(); + SplayNode B = new SplayNode(); + SplayNode C = new SplayNode(); + SplayNode D = new SplayNode(); + g.setChild(dir, p); + g.setChild(dir.opposite(), D); + p.setChild(dir, x); + p.setChild(dir.opposite(), C); + x.setChild(dir, A); + x.setChild(dir.opposite(), B); + + x.splay(); + + mustEq(g.parent, p); + mustEq(g.getChild(dir), C); + mustEq(g.getChild(dir.opposite()), D); + mustEq(p.parent, x); + mustEq(p.getChild(dir), B); + mustEq(p.getChild(dir.opposite()), g); + mustEq(x.parent, null); + mustEq(x.getChild(dir), A); + mustEq(x.getChild(dir.opposite()), p); + mustEq(A.parent, x); + mustEq(A.getChild(dir), null); + mustEq(A.getChild(dir.opposite()), null); + mustEq(B.parent, p); + mustEq(B.getChild(dir), null); + mustEq(B.getChild(dir.opposite()), null); + mustEq(C.parent, g); + mustEq(C.getChild(dir), null); + mustEq(C.getChild(dir.opposite()), null); + mustEq(D.parent, g); + mustEq(D.getChild(dir), null); + mustEq(D.getChild(dir.opposite()), null); + } + for (Direction dir : Direction.values()) { + System.out.println("Testing zig-zag " + dir); + // see "Zig-zag step" of https://en.wikipedia.org/wiki/Splay_tree + SplayNode g = new SplayNode(); + SplayNode p = new SplayNode(); + SplayNode x = new SplayNode(); + SplayNode A = new SplayNode(); + SplayNode B = new SplayNode(); + SplayNode C = new SplayNode(); + SplayNode D = new SplayNode(); + g.setChild(dir, p); + g.setChild(dir.opposite(), D); + p.setChild(dir, A); + p.setChild(dir.opposite(), x); + x.setChild(dir, B); + x.setChild(dir.opposite(), C); + + x.splay(); + + mustEq(g.parent, x); + mustEq(g.getChild(dir), C); + mustEq(g.getChild(dir.opposite()), D); + mustEq(p.parent, x); + mustEq(p.getChild(dir), A); + mustEq(p.getChild(dir.opposite()), B); + mustEq(x.parent, null); + mustEq(x.getChild(dir), p); + mustEq(x.getChild(dir.opposite()), g); + mustEq(A.parent, p); + mustEq(A.getChild(dir), null); + mustEq(A.getChild(dir.opposite()), null); + mustEq(B.parent, p); + mustEq(B.getChild(dir), null); + mustEq(B.getChild(dir.opposite()), null); + mustEq(C.parent, g); + mustEq(C.getChild(dir), null); + mustEq(C.getChild(dir.opposite()), null); + mustEq(D.parent, g); + mustEq(D.getChild(dir), null); + mustEq(D.getChild(dir.opposite()), null); + } + for (Direction GtoP : Direction.values()) { + for (Direction PtoX : Direction.values()) { + System.out.println("Testing connected swap " + GtoP + " " + PtoX); + SplayNode g = new SplayNode(); + SplayNode p = new SplayNode(); + SplayNode x = new SplayNode(); + SplayNode A = new SplayNode(); + SplayNode B = new SplayNode(); + SplayNode C = new SplayNode(); + SplayNode D = new SplayNode(); + g.setChild(GtoP, p); + g.setChild(GtoP.opposite(), D); + p.setChild(PtoX, x); + p.setChild(PtoX.opposite(), A); + x.setChild(Direction.LEFT, B); + x.setChild(Direction.RIGHT, C); + + /*x.black = true; + p.black = false;*/ + p.swapLocationWith(x); + + /*if (x.black || !p.black) { + throw new IllegalStateException(); + }*/ + mustEq(g.parent, null); + mustEq(g.getChild(GtoP), x); + mustEq(g.getChild(GtoP.opposite()), D); + mustEq(p.parent, x); + mustEq(p.getChild(Direction.LEFT), B); + mustEq(p.getChild(Direction.RIGHT), C); + mustEq(x.parent, g); + mustEq(x.getChild(PtoX), p); + mustEq(x.getChild(PtoX.opposite()), A); + mustEq(A.parent, x); + mustEq(A.getChild(Direction.LEFT), null); + mustEq(A.getChild(Direction.RIGHT), null); + mustEq(B.parent, p); + mustEq(B.getChild(Direction.LEFT), null); + mustEq(B.getChild(Direction.RIGHT), null); + mustEq(C.parent, p); + mustEq(C.getChild(Direction.LEFT), null); + mustEq(C.getChild(Direction.RIGHT), null); + mustEq(D.parent, g); + mustEq(D.getChild(Direction.LEFT), null); + mustEq(D.getChild(Direction.RIGHT), null); + } + } + for (Direction APtoA : Direction.values()) { + for (Direction BPtoB : Direction.values()) { + System.out.println("Testing disconnected swap " + APtoA + " " + BPtoB); + SplayNode ap = new SplayNode(); + SplayNode apoc = new SplayNode(); + SplayNode a = new SplayNode(); + SplayNode alc = new SplayNode(); + SplayNode arc = new SplayNode(); + SplayNode bp = new SplayNode(); + SplayNode bpoc = new SplayNode(); + SplayNode b = new SplayNode(); + SplayNode blc = new SplayNode(); + SplayNode brc = new SplayNode(); + ap.setChild(APtoA, a); + ap.setChild(APtoA.opposite(), apoc); + a.setChild(Direction.LEFT, alc); + a.setChild(Direction.RIGHT, arc); + bp.setChild(BPtoB, b); + bp.setChild(BPtoB.opposite(), bpoc); + b.setChild(Direction.LEFT, blc); + b.setChild(Direction.RIGHT, brc); + + /*a.black = true; + b.black = false;*/ + a.swapLocationWith(b); + + /*if (a.black || !b.black) { + throw new IllegalStateException(); + }*/ + mustEq(ap.parent, null); + mustEq(ap.getChild(APtoA), b); + mustEq(ap.getChild(APtoA.opposite()), apoc); + mustEq(apoc.parent, ap); + mustEq(apoc.getChild(Direction.LEFT), null); + mustEq(apoc.getChild(Direction.RIGHT), null); + mustEq(a.parent, bp); + mustEq(a.getChild(Direction.LEFT), blc); + mustEq(a.getChild(Direction.RIGHT), brc); + mustEq(alc.parent, b); + mustEq(alc.getChild(Direction.LEFT), null); + mustEq(alc.getChild(Direction.RIGHT), null); + mustEq(arc.parent, b); + mustEq(arc.getChild(Direction.LEFT), null); + mustEq(arc.getChild(Direction.RIGHT), null); + mustEq(bp.parent, null); + mustEq(bp.getChild(BPtoB), a); + mustEq(bp.getChild(BPtoB.opposite()), bpoc); + mustEq(bpoc.parent, bp); + mustEq(bpoc.getChild(Direction.LEFT), null); + mustEq(bpoc.getChild(Direction.RIGHT), null); + mustEq(b.parent, ap); + mustEq(b.getChild(Direction.LEFT), alc); + mustEq(b.getChild(Direction.RIGHT), arc); + mustEq(blc.parent, a); + mustEq(blc.getChild(Direction.LEFT), null); + mustEq(blc.getChild(Direction.RIGHT), null); + mustEq(brc.parent, a); + mustEq(brc.getChild(Direction.LEFT), null); + mustEq(brc.getChild(Direction.RIGHT), null); + } + } + { + Random rand = new Random(5021); + List> constructors = Arrays.asList(SplayNode::new/*, SplayNode::new*/); + for (int run = 0; run < 10; run++) { + int NODES = 10000; + Supplier toUse = constructors.get(run % constructors.size()); + List nodes = new ArrayList<>(); + { + BSTNode root = toUse.get(); + nodes.add(root); + for (int i = 1; i < NODES; i++) { + nodes.add(toUse.get()); + root = BSTNode.concatenate(root, nodes.get(i)); + } + } + int shuffledBy = 0; + for (int ii = 0; ii < 10000; ii++) { + if (rand.nextBoolean()) { + BSTNode root = nodes.get(rand.nextInt(NODES)); + if (root instanceof SplayNode) { + ((SplayNode) root).splay(); + if (root != nodes.get(rand.nextInt(NODES)).walkAncestor() || root.loopbackSize != NODES) { + throw new IllegalStateException(); + } + } else { + throw new IllegalStateException(); + } + } + if (rand.nextBoolean()) { + shuffledBy = rand.nextInt(NODES); + BSTNode root = BSTNode.barrelRollToLowest(nodes.get(shuffledBy)); + if (root != nodes.get(rand.nextInt(NODES)).walkAncestor() || root.loopbackSize != NODES) { + throw new IllegalStateException(); + } + } + if (rand.nextBoolean()) { + int pos = rand.nextBoolean() ? (shuffledBy + NODES + rand.nextInt(10) - 5) % NODES : rand.nextInt(NODES); + BSTNode remove = nodes.remove(pos); + NODES--; + remove.remove(); + if (shuffledBy > pos) { + shuffledBy--; + } + } + List order = new ArrayList<>(NODES); + nodes.get(rand.nextInt(NODES)).walkAncestor().walk(order::add); + for (int n = 0; n < NODES; n++) { + if (order.get(n) != nodes.get((n + shuffledBy) % NODES)) { + throw new IllegalStateException(); + } + order.get(n).sizeMustBeAccurate(); + if (order.get(n).walkNext() != (n < NODES - 1 ? order.get(n + 1) : null)) { + throw new IllegalStateException(); + } + } + } + } + } + { + // slide 22 of https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf + EulerTourForest forest = new EulerTourForest(11); + forest.link(0, 1); + forest.link(1, 3); + forest.link(2, 4); + forest.link(1, 2); + TreeEdge toCut = forest.link(0, 5); + forest.link(5, 6); + forest.link(6, 9); + forest.link(9, 10); + forest.link(9, 8); + forest.link(6, 7); + BSTNode.barrelRollToLowest(forest.loopbacks[0]); + if (!forest.checkForest(true).equals("abdbcecbafgjkjijghgf")) { + throw new IllegalStateException(); + } + forest.cut(toCut); + if (!forest.checkForest(true).equals("abdbcecb fgjkjijghg")) { + throw new IllegalStateException(); + } + } + { + // slide 26 of https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf + EulerTourForest forest = new EulerTourForest(11); + forest.link(2, 4); + TreeEdge toCut = forest.link(2, 1); + forest.link(1, 0); + forest.link(1, 3); + forest.link(2, 6); + forest.link(6, 9); + forest.link(9, 10); + forest.link(9, 8); + forest.link(6, 7); + forest.link(5, 6); + BSTNode.barrelRollToLowest(forest.loopbacks[2]); + if (!forest.checkForest(true).equals("cecbabdbcgjkjijghgfg")) { + throw new IllegalStateException(); + } + forest.cut(toCut); + if (!forest.checkForest(true).equals("babd cgjkjijghgfgce")) { + throw new IllegalStateException(); + } + } + } + + public String checkForest(boolean verbose) { + boolean[] seen = new boolean[loopbacks.length]; + StringBuilder ret = new StringBuilder(); + for (int vert = 0; vert < loopbacks.length; vert++) { + if (seen[vert]) { + continue; + } + List order = new ArrayList<>(); + loopbacks[vert].walkAncestor().walk(order::add); + for (int i = 0; i < order.size(); i++) { + if (verbose) { + System.out.print("(" + (char) ('a' + order.get(i).src) + "," + (char) ('a' + order.get(i).dst) + ") "); + } + if (order.get(i).dst != order.get((i + 1) % order.size()).src) { + throw new IllegalStateException(); + } + if (order.get(i).isLoopback()) { + seen[order.get(i).src] = true; + } else { + ret.append((char) ('a' + order.get(i).src)); + } + } + if (verbose) { + System.out.println(); + } + ret.append(" "); + if (!seen[vert]) { + throw new IllegalStateException(); + } + } + if (verbose) { + System.out.println(ret); + } + return ret.toString().trim(); + } +} diff --git a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java index 9c90d981e..50f70ce48 100644 --- a/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java +++ b/src/test/java/com/github/btrekkie/connectivity/test/ConnGraphTest.java @@ -2,6 +2,7 @@ package com.github.btrekkie.connectivity.test; import com.github.btrekkie.connectivity.ConnGraph; import com.github.btrekkie.connectivity.ConnVertex; +import com.github.leijurv.EulerTourForest; import org.junit.Test; import java.util.*; @@ -15,6 +16,7 @@ public class ConnGraphTest { @Test public void testPerformanceOnRepeatedConnectionAndDisconnection() { + EulerTourForest.sanityCheck(); for (int trial = 0; trial < 10; trial++) { try { Thread.sleep(2000); From 59be6b4606f48175df8659625b02629f2487e7b1 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 14:56:13 -0700 Subject: [PATCH 23/29] dead link --- src/main/java/com/github/leijurv/EulerTourForest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/github/leijurv/EulerTourForest.java b/src/main/java/com/github/leijurv/EulerTourForest.java index c2d0b9554..8f938fd85 100644 --- a/src/main/java/com/github/leijurv/EulerTourForest.java +++ b/src/main/java/com/github/leijurv/EulerTourForest.java @@ -14,7 +14,7 @@ public class EulerTourForest { static long parentCalls; // https://web.stanford.edu/class/archive/cs/cs166/cs166.1166/lectures/17/Small17.pdf // https://u.cs.biu.ac.il/~rodittl/p723-holm.pdf - // https://infoscience.epfl.ch/record/99353/files/HenzingerK99.pdf + // https://web.archive.org/web/20180725100607/https://infoscience.epfl.ch/record/99353/files/HenzingerK99.pdf // https://en.wikipedia.org/wiki/Dynamic_connectivity#The_Level_structure public final BSTNode[] loopbacks; // a (v,v) fake edge is created per vertex and maintained at the appropriate location in the tree, to allow fast lookups of where "v" is, without having to rely on the presence or absence of tree edges connected to v From 200e68a1b920069ebcce2d6056886afd499fd64b Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 22:35:40 -0700 Subject: [PATCH 24/29] import from baritone builder-2 --- .../com/github/leijurv/BetterBlockPos.java | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 src/main/java/com/github/leijurv/BetterBlockPos.java diff --git a/src/main/java/com/github/leijurv/BetterBlockPos.java b/src/main/java/com/github/leijurv/BetterBlockPos.java new file mode 100644 index 000000000..d1ea89313 --- /dev/null +++ b/src/main/java/com/github/leijurv/BetterBlockPos.java @@ -0,0 +1,230 @@ +/* + * This file is part of Baritone. + * + * Baritone 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 3 of the License, or + * (at your option) any later version. + * + * Baritone 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Baritone. If not, see . + */ + +package com.github.leijurv; + +import it.unimi.dsi.fastutil.HashCommon; + +/** + * A better BlockPos that has fewer hash collisions (and slightly more performant offsets) + *

+ * Is it really faster to subclass BlockPos and calculate a hash in the constructor like this, taking everything into account? + * Yes. 20% faster actually. It's called BETTER BlockPos for a reason. Source: + * Benchmark Spreadsheet + * + * @author leijurv + */ +public final class BetterBlockPos { + + public static final BetterBlockPos ORIGIN = new BetterBlockPos(0, 0, 0); + + public final int x; + public final int y; + public final int z; + + public BetterBlockPos(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public int hashCode() { + return (int) longHash(x, y, z); + } + + public static long longHash(BetterBlockPos pos) { + return longHash(pos.x, pos.y, pos.z); + } + + public static final int NUM_X_BITS = 26; + public static final int NUM_Z_BITS = NUM_X_BITS; + public static final int NUM_Y_BITS = 9; // note: even though Y goes from 0 to 255, that doesn't mean 8 bits will "just work" because the deserializer assumes signed. i could change it for just Y to assume unsigned and leave X and Z as signed, however, we know that in 1.17 they plan to add negative Y. for that reason, the better approach is to give the extra bits to Y and leave it as signed. + // also, if 1.17 sticks with the current plan which is -64 to +320, we could have 9 bits for Y and a constant offset of -64 to change it to -128 to +256. + // that would result in the packed long representation of any valid coordinate still being a positive integer + // i like that property, so i will keep num_y_bits at 9 and plan for an offset in 1.17 + // it also gives 1 bit of wiggle room in case anything else happens in the future, so we are only using 63 out of 64 bits at the moment + public static final int Z_SHIFT = 0; + public static final int Y_SHIFT = Z_SHIFT + NUM_Z_BITS + 1; // 1 padding bit to make twos complement not overflow + public static final int X_SHIFT = Y_SHIFT + NUM_Y_BITS + 1; // and also here too + public static final long X_MASK = (1L << NUM_X_BITS) - 1L; // X doesn't need padding as the overflow carry bit is just discarded, like a normal long (-1) + (1) = 0 + public static final long Y_MASK = (1L << NUM_Y_BITS) - 1L; + public static final long Z_MASK = (1L << NUM_Z_BITS) - 1L; + + public static final long POST_ADDITION_MASK = X_MASK << X_SHIFT | Y_MASK << Y_SHIFT | Z_MASK << Z_SHIFT; // required to "manually inline" toLong(-1, -1, -1) here so that javac inserts proper ldc2_w instructions at usage points instead of getstatic + // what's this ^ mask for? + // it allows for efficient offsetting and manipulation of a long packed coordinate + // if we had three ints, x y z, it would be easy to do "y += 1" or "x -= 1" + // but how do you do those things if you have a long with x y and z all stuffed into one primitive? + // adding together two long coordinates actually works perfectly if both sides have X, Y, and Z as all positive, no issues at all + // but when Y or Z is negative, we run into an issue. consider 8 bits: negative one is 11111111 and one is 00000001 + // adding them together gives 00000000, zero, **but only because there isn't a 9th bit to carry into** + // if we had, instead, 00000000 11111111 + 00000000 00000001 we would rightly get 00000001 00000000 with the 1 being carried into the 9th position there + // this is exactly what happens. "toLong(0, 1, 0) + toLong(0, -1, 0)" ends up equaling toLong(1, 0, 0) while we'd rather it equal toLong(0, 0, 0) + // so, we simply mask out the unwanted result of the carry by inserting 1 bit of padding space (as added above) between each + // it used to be 000XXXXXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZZZZZ + // and now it is 0XXXXXXXXXXXXXXXXXXXXXXXXXX0YYYYYYYYY0ZZZZZZZZZZZZZZZZZZZZZZZZZZ + // we simply place the X Y and Z in slightly different sections of the long, putting a bit of space between each + // the mask ^ is 0111111111111111111111111110111111111011111111111111111111111111 + // using that example of (0,1,0) + (0,-1,0), here's what happens + // 0000000000000000000000000000000000001000000000000000000000000000 (this is X=0 Y=1 Z=0) + // + 0000000000000000000000000000111111111000000000000000000000000000 (this is X=0 Y=-1 Z=0) + // = 0000000000000000000000000001000000000000000000000000000000000000 + // the unwanted carry bit here ^ is no longer corrupting the least significant bit of X and making it 1! + // now it's just turning on the unused padding bit that we don't care about + // using the mask and bitwise and, we can easily and branchlessly turn off the padding bits just in case something overflow carried into them! + // 0000000000000000000000000001000000000000000000000000000000000000 (the result of the addition from earlier) + // & 0111111111111111111111111110111111111011111111111111111111111111 (this is POST_ADDITION_MASK) + // = 0000000000000000000000000000000000000000000000000000000000000000 + // POST_ADDITION_MASK retains the bits that actually form X, Y, and Z, but intentionally turns off the padding bits + // so, we can simply do "(toLong(0, 1, 0) + toLong(0, -1, 0)) & POST_ADDITION_MASK" and correctly get toLong(0, 0, 0) + // which is incredibly fast and efficient, an add then a bitwise AND against a constant + // and it doesn't require us to pull out X, Y, and Z, modify one of them, and put them all back into the long + // that's what the point of the mask is + + static { + if (POST_ADDITION_MASK != toLong(-1, -1, -1)) { + throw new IllegalStateException(POST_ADDITION_MASK + " " + toLong(-1, -1, -1)); // sanity check + } + } + + public long toLong() { + return toLong(this.x, this.y, this.z); + } + + public static BetterBlockPos fromLong(long serialized) { + return new BetterBlockPos(XfromLong(serialized), YfromLong(serialized), ZfromLong(serialized)); + } + + public static int XfromLong(long serialized) { + return (int) (serialized << (64 - X_SHIFT - NUM_X_BITS) >> (64 - NUM_X_BITS)); + } + + public static int YfromLong(long serialized) { + return (int) (serialized << (64 - Y_SHIFT - NUM_Y_BITS) >> (64 - NUM_Y_BITS)); + } + + public static int ZfromLong(long serialized) { + return (int) (serialized << (64 - Z_SHIFT - NUM_Z_BITS) >> (64 - NUM_Z_BITS)); + } + + public static long toLong(final int x, final int y, final int z) { + return ((long) x & X_MASK) << X_SHIFT | ((long) y & Y_MASK) << Y_SHIFT | ((long) z & Z_MASK) << Z_SHIFT; + } + + public static long offsetBy(long pos, int x, int y, int z) { + return (pos + toLong(x, y, z)) & BetterBlockPos.POST_ADDITION_MASK; + } + + public static final long HASHCODE_MURMUR_MASK = murmur64(-1); + public static final long ZOBRIST_MURMUR_MASK = murmur64(-2); + + public static long longHash(int x, int y, int z) { + return longHash(toLong(x, y, z)); + } + + public static long longHash(long packed) { + return murmur64(HASHCODE_MURMUR_MASK ^ packed); + } + + public static long murmur64(long h) { + return HashCommon.murmurHash3(h); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o instanceof BetterBlockPos) { + BetterBlockPos oth = (BetterBlockPos) o; + return oth.x == x && oth.y == y && oth.z == z; + } + return false; + } + + public BetterBlockPos up() { + // this is unimaginably faster than blockpos.up + // that literally calls + // this.up(1) + // which calls this.offset(EnumFacing.UP, 1) + // which does return n == 0 ? this : new BlockPos(this.getX() + facing.getXOffset() * n, this.getY() + facing.getYOffset() * n, this.getZ() + facing.getZOffset() * n); + + // how many function calls is that? up(), up(int), offset(EnumFacing, int), new BlockPos, getX, getXOffset, getY, getYOffset, getZ, getZOffset + // that's ten. + // this is one function call. + return new BetterBlockPos(x, y + 1, z); + } + + public BetterBlockPos up(int amt) { + // see comment in up() + return amt == 0 ? this : new BetterBlockPos(x, y + amt, z); + } + + public BetterBlockPos down() { + // see comment in up() + return new BetterBlockPos(x, y - 1, z); + } + + public BetterBlockPos down(int amt) { + // see comment in up() + return amt == 0 ? this : new BetterBlockPos(x, y - amt, z); + } + + public BetterBlockPos north() { + return new BetterBlockPos(x, y, z - 1); + } + + public BetterBlockPos north(int amt) { + return amt == 0 ? this : new BetterBlockPos(x, y, z - amt); + } + + public BetterBlockPos south() { + return new BetterBlockPos(x, y, z + 1); + } + + public BetterBlockPos south(int amt) { + return amt == 0 ? this : new BetterBlockPos(x, y, z + amt); + } + + public BetterBlockPos east() { + return new BetterBlockPos(x + 1, y, z); + } + + public BetterBlockPos east(int amt) { + return amt == 0 ? this : new BetterBlockPos(x + amt, y, z); + } + + public BetterBlockPos west() { + return new BetterBlockPos(x - 1, y, z); + } + + public BetterBlockPos west(int amt) { + return amt == 0 ? this : new BetterBlockPos(x - amt, y, z); + } + + @Override + public String toString() { + return String.format( + "BetterBlockPos{x=%d,y=%d,z=%d}", + x, + y, + z + ); + } +} + From fe808a5f8386cd9a0e9dd0adc6980cf7eb604710 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Wed, 15 Mar 2023 22:41:42 -0700 Subject: [PATCH 25/29] test --- src/main/java/com/github/leijurv/Testing.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/github/leijurv/Testing.java diff --git a/src/main/java/com/github/leijurv/Testing.java b/src/main/java/com/github/leijurv/Testing.java new file mode 100644 index 000000000..37a2372ba --- /dev/null +++ b/src/main/java/com/github/leijurv/Testing.java @@ -0,0 +1,7 @@ +package com.github.leijurv; + +public class Testing { + public static void main(String[] args) { + EulerTourForest.sanityCheck(); + } +} From 2cb46e054064949b793e67cffe3d780a9440c8e2 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Thu, 16 Mar 2023 00:18:38 -0700 Subject: [PATCH 26/29] explanatory readme --- README.md | 57 ++++++------------------------------------------------- 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 6b728a197..d7a917212 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,9 @@ -# Description -This provides the `ConnGraph` class, which implements an undirected graph with -dynamic connectivity. It supports adding and removing edges and determining -whether two vertices are connected - whether there is a path between them. -Adding and removing edges take O(log2N) amortized time with high -probability, while checking whether two vertices are connected takes O(log N) -time with high probability. +Original readme --> https://github.com/btrekkie/dynamic-connectivity/ -# Features -* Efficiently add and remove edges and determine whether vertices are connected. -* A vertex can appear in multiple graphs, with a different set of adjacent - vertices in each graph. -* `ConnGraph` supports arbitrary vertex augmentation. Given a vertex V, - `ConnGraph` can quickly report the result of combining the augmentations of - all of the vertices in the connected component containing V, using a combining - function provided to the constructor. For example, if a `ConnGraph` represents - a game map, then given the location of the player, we can quickly determine - the amount of gold the player can access, or the strongest monster that can - reach him. Retrieving the combined augmentation for a connected component - takes O(log N) time with high probability. -* Compatible with Java 7.0 and above. +I am experimenting with using dynamic connectivity to help with Baritone builder 2 -# Limitations -* `ConnGraph` does not directly support augmenting edges. However, this can be - accomplished by imputing each edge's augmentation to an adjacent vertex. For - example, if each edge contains a certain amount of gold, then we can augment - each vertex with the amount of gold in the adjacent edges. We can then - calculate the amount of gold in a connected component by retrieving the - component's augmentation and dividing by two. A more general approach would be - to store the edges adjacent to each vertex in an augmented self-balancing - binary search tree (see - [RedBlackNode](https://github.com/btrekkie/RedBlackNode)), and to use this to - assign an augmentation to each vertex. -* Careful attention has been paid to the asymptotic running time of each method. - However, beyond this, no special effort has been made to optimize performance. - (A big obstacle to optimizing `ConnGraph` is a lack of access to samples from - real-world usage.) +https://en.wikipedia.org/wiki/Dynamic_connectivity -# Example usage -```java -ConnGraph graph = new ConnGraph(); -ConnVertex vertex1 = new ConnVertex(); -ConnVertex vertex2 = new ConnVertex(); -ConnVertex vertex3 = new ConnVertex(); -graph.addEdge(vertex1, vertex2); -graph.addEdge(vertex2, vertex3); -graph.connected(vertex1, vertex3); // Returns true -graph.removeEdge(vertex1, vertex2); -graph.connected(vertex1, vertex3); // Returns false -``` - -# Documentation -See for API -documentation. +If the experiments in this repo go well, I want to integrate it +into https://github.com/cabaletta/baritone/tree/builder-2/src/main/java/baritone/builder (i think MIT is +compatible with LGPL so this is ok) \ No newline at end of file From 6fea22dc9f8812c5ef9b6b91f72814db7b2cbbfc Mon Sep 17 00:00:00 2001 From: Leijurv Date: Thu, 16 Mar 2023 00:42:23 -0700 Subject: [PATCH 27/29] this is incredibly cool and it does figure out the staircase pattern like i hoped --- .../com/github/leijurv/BetterBlockPos.java | 24 +- .../com/github/leijurv/NavigableSurface.java | 147 ++++++++++ src/main/java/com/github/leijurv/Testing.java | 7 - .../github/leijurv/NavigableSurfaceTest.java | 276 ++++++++++++++++++ 4 files changed, 435 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/github/leijurv/NavigableSurface.java delete mode 100644 src/main/java/com/github/leijurv/Testing.java create mode 100644 src/test/java/com/github/leijurv/NavigableSurfaceTest.java diff --git a/src/main/java/com/github/leijurv/BetterBlockPos.java b/src/main/java/com/github/leijurv/BetterBlockPos.java index d1ea89313..8b4bdedd7 100644 --- a/src/main/java/com/github/leijurv/BetterBlockPos.java +++ b/src/main/java/com/github/leijurv/BetterBlockPos.java @@ -157,7 +157,7 @@ public final class BetterBlockPos { return false; } - public BetterBlockPos up() { + public BetterBlockPos upPlusY() { // this is unimaginably faster than blockpos.up // that literally calls // this.up(1) @@ -170,50 +170,50 @@ public final class BetterBlockPos { return new BetterBlockPos(x, y + 1, z); } - public BetterBlockPos up(int amt) { + public BetterBlockPos upPlusY(int amt) { // see comment in up() return amt == 0 ? this : new BetterBlockPos(x, y + amt, z); } - public BetterBlockPos down() { + public BetterBlockPos downMinusY() { // see comment in up() return new BetterBlockPos(x, y - 1, z); } - public BetterBlockPos down(int amt) { + public BetterBlockPos downMinusY(int amt) { // see comment in up() return amt == 0 ? this : new BetterBlockPos(x, y - amt, z); } - public BetterBlockPos north() { + public BetterBlockPos northMinusZ() { return new BetterBlockPos(x, y, z - 1); } - public BetterBlockPos north(int amt) { + public BetterBlockPos northMinusZ(int amt) { return amt == 0 ? this : new BetterBlockPos(x, y, z - amt); } - public BetterBlockPos south() { + public BetterBlockPos southPlusZ() { return new BetterBlockPos(x, y, z + 1); } - public BetterBlockPos south(int amt) { + public BetterBlockPos southPlusZ(int amt) { return amt == 0 ? this : new BetterBlockPos(x, y, z + amt); } - public BetterBlockPos east() { + public BetterBlockPos eastPlusX() { return new BetterBlockPos(x + 1, y, z); } - public BetterBlockPos east(int amt) { + public BetterBlockPos eastPlusX(int amt) { return amt == 0 ? this : new BetterBlockPos(x + amt, y, z); } - public BetterBlockPos west() { + public BetterBlockPos westMinusX() { return new BetterBlockPos(x - 1, y, z); } - public BetterBlockPos west(int amt) { + public BetterBlockPos westMinusX(int amt) { return amt == 0 ? this : new BetterBlockPos(x - amt, y, z); } diff --git a/src/main/java/com/github/leijurv/NavigableSurface.java b/src/main/java/com/github/leijurv/NavigableSurface.java new file mode 100644 index 000000000..784a4f7c8 --- /dev/null +++ b/src/main/java/com/github/leijurv/NavigableSurface.java @@ -0,0 +1,147 @@ +package com.github.leijurv; + +import com.github.btrekkie.connectivity.ConnGraph; + +import java.util.OptionalInt; + +public class NavigableSurface { + // the encapsulation / separation of concerns is not great, but this is better for testing purposes than the fully accurate stuff in https://github.com/cabaletta/baritone/tree/builder-2/src/main/java/baritone/builder lol + public final int sizeX; + public final int sizeY; + public final int sizeZ; + + private final boolean[][][] blocks; + + private final ConnGraph connGraph; + + public NavigableSurface(int x, int y, int z) { + this.sizeX = x; + this.sizeY = y; + this.sizeZ = z; + this.blocks = new boolean[x][y][z]; + this.connGraph = new ConnGraph((a, b) -> (Integer) a + (Integer) b); + } + + public OptionalInt surfaceSize(BetterBlockPos pos) { // how big is the navigable surface from here? how many distinct coordinates can i walk to (in the future, the augmentation will probably have a list of those coordinates or something?) + Object data = connGraph.getComponentAugmentation(pos.toLong()); + if (data != null) { // i disagree with the intellij suggestion here i think it makes it worse + return OptionalInt.of((Integer) data); + } else { + return OptionalInt.empty(); + } + } + + // so the idea is that as blocks are added and removed, we'll maintain where the player can stand, and what connections that has to other places + public void placeOrRemoveBlock(BetterBlockPos where, boolean place) { + // i think it makes sense to only have a single function, as both placing and breaking blocks can create/remove places the player could stand, as well as creating/removing connections between those places + boolean previously = getBlock(where); + if (previously == place) { + return; // this is already the case + } + blocks[where.x][where.y][where.z] = place; + // first let's set some vertex info for where the player can and cannot stand + for (int dy = -1; dy <= 1; dy++) { + BetterBlockPos couldHaveChanged = where.upPlusY(dy); + boolean currentlyAllowed = canPlayerStandIn(couldHaveChanged); + if (currentlyAllowed) { + // i'm sure this will get more complicated later + connGraph.setVertexAugmentation(couldHaveChanged.toLong(), 1); + } else { + connGraph.removeVertexAugmentation(couldHaveChanged.toLong()); + } + } + // then let's set the edges + for (int dy = -2; dy <= 1; dy++) { // -2 because of the jump condition for ascending + // i guess some of these can be skipped based on whether "place" is false or true, but, whatever this is just for testing + BetterBlockPos couldHaveChanged = where.upPlusY(dy); + computePossibleMoves(couldHaveChanged); + } + } + + public boolean canPlayerStandIn(BetterBlockPos where) { + return getBlockOrAir(where.downMinusY()) && !getBlockOrAir(where) && !getBlockOrAir(where.upPlusY()); + } + + public void computePossibleMoves(BetterBlockPos feet) { + boolean anySuccess = canPlayerStandIn(feet); + // even if all are fail, need to remove those edges from the graph, so don't return early + for (int[] move : MOVES) { + BetterBlockPos newFeet = feet.eastPlusX(move[0]).upPlusY(move[1]).southPlusZ(move[2]); + boolean thisSuccess = anySuccess; + thisSuccess &= canPlayerStandIn(newFeet); + if (move[1] == -1) { + // descend movement requires the player head to move through one extra block (newFeet must be 3 high not 2 high) + thisSuccess &= !getBlockOrAir(newFeet.upPlusY(2)); + } + if (move[1] == 1) { + // same idea but ascending instead of descending + thisSuccess &= !getBlockOrAir(feet.upPlusY(2)); + } + if (thisSuccess) { + if (connGraph.addEdge(feet.toLong(), newFeet.toLong())) { + //System.out.println("Player can now move between " + feet + " and " + newFeet); + } + } else { + if (connGraph.removeEdge(feet.toLong(), newFeet.toLong())) { + //System.out.println("Player can no longer move between " + feet + " and " + newFeet); + } + } + } + } + + public int requireSurfaceSize(int x, int y, int z) { + return surfaceSize(new BetterBlockPos(x, y, z)).getAsInt(); + } + + public boolean inRange(int x, int y, int z) { + return (x | y | z | (sizeX - (x + 1)) | (sizeY - (y + 1)) | (sizeZ - (z + 1))) >= 0; // ">= 0" is used here in the sense of "most significant bit is not set" + } + + public boolean getBlock(BetterBlockPos where) { + return blocks[where.x][where.y][where.z]; + } + + public boolean getBlockOrAir(BetterBlockPos where) { + if (!inRange(where.x, where.y, where.z)) { + return false; + } + return getBlock(where); + } + + public boolean connected(BetterBlockPos a, BetterBlockPos b) { + return connGraph.connected(a.toLong(), b.toLong()); + } + + public void placeBlock(BetterBlockPos where) { + placeOrRemoveBlock(where, true); + } + + public void placeBlock(int x, int y, int z) { + placeBlock(new BetterBlockPos(x, y, z)); + } + + public void removeBlock(BetterBlockPos where) { + placeOrRemoveBlock(where, false); + } + + public void removeBlock(int x, int y, int z) { + removeBlock(new BetterBlockPos(x, y, z)); + } + + private static final int[][] MOVES = { + {1, -1, 0}, + {-1, -1, 0}, + {0, -1, 1}, + {0, -1, -1}, + + {1, 0, 0}, + {-1, 0, 0}, + {0, 0, 1}, + {0, 0, -1}, + + {1, 1, 0}, + {-1, 1, 0}, + {0, 1, 1}, + {0, 1, -1}, + }; +} diff --git a/src/main/java/com/github/leijurv/Testing.java b/src/main/java/com/github/leijurv/Testing.java deleted file mode 100644 index 37a2372ba..000000000 --- a/src/main/java/com/github/leijurv/Testing.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.leijurv; - -public class Testing { - public static void main(String[] args) { - EulerTourForest.sanityCheck(); - } -} diff --git a/src/test/java/com/github/leijurv/NavigableSurfaceTest.java b/src/test/java/com/github/leijurv/NavigableSurfaceTest.java new file mode 100644 index 000000000..d3d5e4980 --- /dev/null +++ b/src/test/java/com/github/leijurv/NavigableSurfaceTest.java @@ -0,0 +1,276 @@ +package com.github.leijurv; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalInt; + +import static org.junit.Assert.assertEquals; + + +public class NavigableSurfaceTest { + @Test + public void testBasic() { + NavigableSurface surface = new NavigableSurface(10, 10, 10); + surface.placeBlock(0, 0, 0); + assertEquals(OptionalInt.empty(), surface.surfaceSize(new BetterBlockPos(0, 0, 0))); + assertEquals(1, surface.requireSurfaceSize(0, 1, 0)); + surface.placeBlock(1, 0, 0); + assertEquals(2, surface.requireSurfaceSize(0, 1, 0)); + surface.placeBlock(1, 0, 0); + surface.placeBlock(2, 0, 0); + surface.placeBlock(3, 0, 0); + surface.placeBlock(4, 0, 0); + surface.placeBlock(5, 0, 0); + // XXXXXX + assertEquals(6, surface.requireSurfaceSize(2, 1, 0)); + + surface.placeBlock(2, 1, 0); + assertEquals(OptionalInt.empty(), surface.surfaceSize(new BetterBlockPos(2, 1, 0))); + assertEquals(6, surface.requireSurfaceSize(2, 2, 0)); + + surface.placeBlock(2, 2, 0); + // X + // X + // XXXXXX + assertEquals(2, surface.requireSurfaceSize(0, 1, 0)); + assertEquals(1, surface.requireSurfaceSize(2, 3, 0)); + assertEquals(3, surface.requireSurfaceSize(3, 1, 0)); + + surface.placeBlock(1, 1, 0); + // X + // XX + // XXXXXX + assertEquals(3, surface.requireSurfaceSize(0, 1, 0)); + assertEquals(3, surface.requireSurfaceSize(3, 1, 0)); + + surface.placeBlock(3, 2, 0); + // XX + // XX + // XXXXXX + assertEquals(4, surface.requireSurfaceSize(0, 1, 0)); + assertEquals(2, surface.requireSurfaceSize(4, 1, 0)); + + surface.placeBlock(4, 1, 0); + // XX + // XX X + // XXXXXX + assertEquals(6, surface.requireSurfaceSize(0, 1, 0)); + assertEquals(OptionalInt.empty(), surface.surfaceSize(new BetterBlockPos(3, 1, 0))); + + surface.removeBlock(2, 2, 0); + // X + // XX X + // XXXXXX + assertEquals(6, surface.requireSurfaceSize(2, 2, 0)); + + surface.removeBlock(2, 1, 0); + // X + // X X + // XXXXXX + assertEquals(3, surface.requireSurfaceSize(1, 2, 0)); + assertEquals(3, surface.requireSurfaceSize(3, 3, 0)); + + surface.removeBlock(3, 2, 0); + // X X + // XXXXXX + assertEquals(6, surface.requireSurfaceSize(0, 1, 0)); + + surface.removeBlock(1, 0, 0); + // X X + // X XXXX + assertEquals(6, surface.requireSurfaceSize(0, 1, 0)); + + surface.removeBlock(1, 1, 0); + // X + // X XXXX + assertEquals(1, surface.requireSurfaceSize(0, 1, 0)); + assertEquals(4, surface.requireSurfaceSize(2, 1, 0)); + } + + private NavigableSurface makeFlatSurface(int SZ) { + NavigableSurface surface = new NavigableSurface(SZ, SZ, SZ); + for (int x = 0; x < SZ; x++) { + for (int z = 0; z < SZ; z++) { + surface.placeBlock(new BetterBlockPos(x, 0, z)); + } + } + assertEquals(SZ * SZ, surface.requireSurfaceSize(0, 1, 0)); + return surface; + } + + @Test + public void testSurfaceSmall() { + makeFlatSurface(10); + } + + @Test + public void testSurfaceMed() { + makeFlatSurface(100); + } + + /*@Test + public void testSurfaceBig() { // 10x more on each side, so 100x more nodes total. youd expect this to be about 100x slower than testSurfaceMed, but it's actually 200x slower. this is ideally just because each graph operation is O(log^2 n) so n operations is O(n log^2 n), and hopefully not because of something that otherwise scales superlinearly + makeFlatSurface(1000); + }*/ + // okay but its slow so we dont care + + @Test + public void testStep() { + int SZ = 100; + int lineAt = SZ / 2; + NavigableSurface surface = makeFlatSurface(SZ); + for (int x = 0; x < SZ; x++) { + surface.placeBlock(x, 1, lineAt); // doesn't block the player since you can step over 1 block + } + assertEquals(SZ * SZ, surface.requireSurfaceSize(0, 1, 0)); + } + + @Test + public void testBlocked() { + int SZ = 100; + int lineAt = SZ / 2; + NavigableSurface surface = makeFlatSurface(SZ); + for (int x = 0; x < SZ; x++) { + surface.placeBlock(x, 2, lineAt); // does block the player since you can't step over 2 blocks + } + assertEquals(SZ * lineAt, surface.requireSurfaceSize(0, 1, 0)); + assertEquals(SZ * (SZ - lineAt - 1), surface.requireSurfaceSize(0, 1, SZ - 1)); + assertEquals(SZ, surface.requireSurfaceSize(0, 3, lineAt)); + } + + private void fillSurfaceInOrderMaintainingConnection(NavigableSurface surface, BetterBlockPos maintainConnectionTo, List iterationOrder) { + outer: + while (true) { + for (BetterBlockPos candidate : iterationOrder) { + if (surface.getBlock(candidate)) { + continue; // already placed + } + // let's try placing + surface.placeBlock(candidate); + if (surface.connected(candidate.upPlusY(), maintainConnectionTo)) { + // success, placed a block while retaining the path down to the ground + continue outer; + } + // fail :( + surface.removeBlock(candidate); + } + return; + } + } + + @Test + public void testCastleWall() { + // build a single wall, but, never place a block that disconnects the surface + // (we expect to see a triangle) + int SZ = 20; + NavigableSurface surface = makeFlatSurface(SZ); + BetterBlockPos someOtherBlock = new BetterBlockPos(0, 1, 1); // won't be involved in the wall (since z=1) + List order = new ArrayList<>(); + for (int y = 0; y < SZ; y++) { + for (int x = 0; x < SZ; x++) { + order.add(new BetterBlockPos(x, y, 0)); + } + } + + fillSurfaceInOrderMaintainingConnection(surface, someOtherBlock, order); + + String shouldBe = "" + + "XX | | | \n" + + "XXX | | | \n" + + "XXXX | | | \n" + + "XXXXX | | | \n" + + "XXXXXX | | | \n" + + "XXXXXXX | | | \n" + + "XXXXXXXX | | | \n" + + "XXXXXXXXX | | | \n" + + "XXXXXXXXXX | | | \n" + + "XXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXXXXXXX | | | \n" + + "XXXXXXXXXXXXXXXXXXXX| | | \n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n"; // double row is because we started with a flat surface, so there is another flat row behind this one to step back into + assertEquals(shouldBe, reportAllFourWalls(surface)); + } + + @Test + public void testCastleFourWalls() { + // build four walls, but, never place a block that disconnects the surface + // (we expect to see a zigzag cut out) + int SZ = 20; + NavigableSurface surface = makeFlatSurface(SZ); + BetterBlockPos someOtherBlock = new BetterBlockPos(SZ / 2, 1, SZ / 2); // center of the courtyard + List order = new ArrayList<>(); + for (int y = 0; y < SZ; y++) { + for (int x = 0; x < SZ; x++) { + for (int z = 0; z < SZ; z++) { + boolean xOnEdge = x == 0 || x == SZ - 1; + boolean zOnEdge = z == 0 || z == SZ - 1; + if (!xOnEdge && !zOnEdge) { + continue; // in the courtyard + } + order.add(new BetterBlockPos(x, y, z)); + } + } + } + + fillSurfaceInOrderMaintainingConnection(surface, someOtherBlock, order); + + String shouldBe = "" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXX XXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXX XXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXX XXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXX XXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXX XXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXX XXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXX XXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXX XXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXX XXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXX XXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXX XXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XX XXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|X XXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX| XXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX |XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXX X|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n" + + "XXXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXXX|XXXXXXXXXXXXXXXXXX\n"; + assertEquals(shouldBe, reportAllFourWalls(surface)); + } + + private String reportAllFourWalls(NavigableSurface surface) { + StringBuilder report = new StringBuilder(); + for (int y = surface.sizeY - 1; y >= 0; y--) { + // make a report of what all four walls look like + for (int x = 0; x < surface.sizeX; x++) { + report.append(surface.getBlock(new BetterBlockPos(x, y, 0)) ? 'X' : ' '); + } + report.append('|'); + // start at 1 not 0 so that we don't repeat the last iteration of the previous loop (that would make the report look bad because the staircase would repeat one column for no reason) + for (int z = 1; z < surface.sizeZ; z++) { + report.append(surface.getBlock(new BetterBlockPos(surface.sizeX - 1, y, z)) ? 'X' : ' '); + } + report.append('|'); + // same deal for starting at -2 rather than -1 + for (int x = surface.sizeX - 2; x >= 0; x--) { + report.append(surface.getBlock(new BetterBlockPos(x, y, surface.sizeZ - 1)) ? 'X' : ' '); + } + report.append('|'); + // and same again + for (int z = surface.sizeZ - 2; z > 0; z--) { + report.append(surface.getBlock(new BetterBlockPos(0, y, z)) ? 'X' : ' '); + } + report.append('\n'); + } + return report.toString(); + } +} From e7f54ab81da5cfd62908f870ccbfb68baec42b30 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Mon, 20 Mar 2023 23:51:19 -0700 Subject: [PATCH 28/29] switch from integer to custom tree attachment --- .../com/github/leijurv/NavigableSurface.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/leijurv/NavigableSurface.java b/src/main/java/com/github/leijurv/NavigableSurface.java index 784a4f7c8..f297ccc17 100644 --- a/src/main/java/com/github/leijurv/NavigableSurface.java +++ b/src/main/java/com/github/leijurv/NavigableSurface.java @@ -19,13 +19,29 @@ public class NavigableSurface { this.sizeY = y; this.sizeZ = z; this.blocks = new boolean[x][y][z]; - this.connGraph = new ConnGraph((a, b) -> (Integer) a + (Integer) b); + this.connGraph = new ConnGraph(Attachment::new); + } + + public static class Attachment { + public final int surfaceSize; + + public Attachment(Object a, Object b) { + this((Attachment) a, (Attachment) b); + } + + public Attachment(Attachment a, Attachment b) { + this.surfaceSize = a.surfaceSize + b.surfaceSize; + } + + public Attachment() { + this.surfaceSize = 1; + } } public OptionalInt surfaceSize(BetterBlockPos pos) { // how big is the navigable surface from here? how many distinct coordinates can i walk to (in the future, the augmentation will probably have a list of those coordinates or something?) Object data = connGraph.getComponentAugmentation(pos.toLong()); if (data != null) { // i disagree with the intellij suggestion here i think it makes it worse - return OptionalInt.of((Integer) data); + return OptionalInt.of(((Attachment) data).surfaceSize); } else { return OptionalInt.empty(); } @@ -45,7 +61,7 @@ public class NavigableSurface { boolean currentlyAllowed = canPlayerStandIn(couldHaveChanged); if (currentlyAllowed) { // i'm sure this will get more complicated later - connGraph.setVertexAugmentation(couldHaveChanged.toLong(), 1); + connGraph.setVertexAugmentation(couldHaveChanged.toLong(), new Attachment()); } else { connGraph.removeVertexAugmentation(couldHaveChanged.toLong()); } From 0f5ee85e19b180d0249f47dcd29431b933244602 Mon Sep 17 00:00:00 2001 From: Leijurv Date: Mon, 20 Mar 2023 23:54:02 -0700 Subject: [PATCH 29/29] trim for merge --- .gitignore | 25 --- build.gradle | 21 --- gradle/wrapper/gradle-wrapper.jar | Bin 55190 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 5 - gradlew | 172 ------------------ gradlew.bat | 84 --------- settings.gradle | 2 - .../main/java/com/github/btrekkie/LICENSE | 0 .../main/java/com/github/btrekkie/README.md | 0 .../github/btrekkie/optimization_ideas.txt | 0 10 files changed, 309 deletions(-) delete mode 100644 .gitignore delete mode 100644 build.gradle delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 settings.gradle rename LICENSE => src/main/java/com/github/btrekkie/LICENSE (100%) rename README.md => src/main/java/com/github/btrekkie/README.md (100%) rename optimization_ideas.txt => src/main/java/com/github/btrekkie/optimization_ideas.txt (100%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index abbb1ca40..000000000 --- a/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - -.DS_Store -target/** -!target/dynamic-connectivity*.jar - - -# gradle -build/ -.gradle/ -classes/ -*.class - -# intellij -.idea/ diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 34c70f7e6..000000000 --- a/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id 'java' -} - -group 'org.example' -version '1.0-SNAPSHOT' - -sourceCompatibility = 1.8 - -repositories { - mavenCentral() -} - -dependencies { - testCompile group: 'junit', name: 'junit', version: '4.13.1' - compile 'it.unimi.dsi:fastutil:8.5.12' -} - -test { - maxHeapSize = "8g" -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 87b738cbd051603d91cc39de6cb000dd98fe6b02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55190 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 z 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=$((i+1)) - done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index 0f8d5937c..000000000 --- a/gradlew.bat +++ /dev/null @@ -1,84 +0,0 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index c59f52a5d..000000000 --- a/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -rootProject.name = 'dynamic-connectivity' - diff --git a/LICENSE b/src/main/java/com/github/btrekkie/LICENSE similarity index 100% rename from LICENSE rename to src/main/java/com/github/btrekkie/LICENSE diff --git a/README.md b/src/main/java/com/github/btrekkie/README.md similarity index 100% rename from README.md rename to src/main/java/com/github/btrekkie/README.md diff --git a/optimization_ideas.txt b/src/main/java/com/github/btrekkie/optimization_ideas.txt similarity index 100% rename from optimization_ideas.txt rename to src/main/java/com/github/btrekkie/optimization_ideas.txt