package edu.hawaii.ics.yucheng; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.Scanner; /** * A weighted, connected, undirected graph. This class is immutable. */ final class Graph implements Iterable<Edge> { /** * This method verifies a graph is connected. * * @param graph The graph. * * @throws GraphException if the graph is invalid. */ private static void verifyConnectedGraph(final Graph graph) { final int numVertices = graph.vertices(); // Create a matrix that marks which vertices are connected. final Matrix<Boolean> matrix = new Matrix<Boolean>(numVertices, numVertices, Boolean.FALSE); for (final Edge e : graph) { matrix.set(e.first(), e.second(), Boolean.TRUE); matrix.set(e.second(), e.first(), Boolean.TRUE); } // Define an array of marked vertices that are initially false. The array // elements are set to true when it is known the vertex is connected. The // first vertex is always considered connected. final boolean[] markedVertices = new boolean[numVertices]; Arrays.fill(markedVertices, false); markedVertices[0] = true; int markCount = 1; // Implement a BFS search with this queue of vertices. In this while loop, // each connected vertex is marked in the boolean array. final LinkedListQueue<Integer> vertexQueue = new LinkedListQueue<Integer>(); vertexQueue.push(Integer.valueOf(0)); while (!vertexQueue.isEmpty()) { final int u = vertexQueue.pop().intValue(); for (int i = 1; i < numVertices; i++) if (matrix.get(u, i).booleanValue()) if (!markedVertices[i]) { markedVertices[i] = true; markCount++; vertexQueue.push(Integer.valueOf(i)); } } // If not all vertices have been marked, the graph is not fully connected. // Throw an exception if this is the case. if (markCount < numVertices) throw new GraphException("Invalid graph (unconnected graph detected)"); } /** * Verifies the graph has no duplicate edges. * * @param edges The collection of edges. * * @throws GraphException if the graph is invalid. */ private static void verifyNoDuplicateEdges(final Collection<Edge> edges) { final Hashtable<Edge, Edge> temp = new Hashtable<Edge, Edge>(); for (final Edge e : edges) { if (temp.containsKey(e)) throw new GraphException("Invalid graph (duplicate edge " + e + " detected)"); temp.put(e, e); } } private final ArrayList<Edge> myEdges; private final URL myUrl; private final float[] myVertices; /** * Initializes a new instance of the class. * * @param vertices The vertices. * * @param edges The edges. */ public Graph(final float[] vertices, final Collection<Edge> edges) { if (vertices == null) throw new NullPointerException("vertices"); if (edges == null) throw new NullPointerException("edges"); if (vertices.length < 1) throw new GraphException("Not enough vertices"); if (vertices.length - 1 > edges.size()) throw new GraphException("Not enough edges"); for (final Edge e : edges) if (e == null) throw new GraphException("Null edge"); // Verify there are no duplicate edges in the collection. verifyNoDuplicateEdges(edges); // Make a copy of the collection to protect the class data. myEdges = new ArrayList<Edge>(edges); // Sort the collection so toString looks good. Collections.sort(myEdges); // Initialize the rest of the class data, and verify the graph is connected. myVertices = vertices; myUrl = null; verifyConnectedGraph(this); } /** * Initializes a new instance of the class. * * @param scanner The scanner. * * @param url The url. * * @throws GraphException Thrown if there are any errors reading from the * scanner. */ public Graph(final Scanner scanner, final URL url) throws GraphException { if (scanner == null) throw new NullPointerException("scanner"); if (url == null) throw new NullPointerException("url"); // Save the URL. myUrl = url; // Rethrow any exceptions as GraphException types. try { // Read the number of vertices. final int vertexCount = Integer.parseInt(scanner.nextLine().trim()); if (vertexCount < 1) throw new GraphException("Invalid graph (no vertices)"); // Read the number of edges. final int edgeCount = Integer.parseInt(scanner.nextLine().trim()); if (edgeCount < vertexCount - 1) throw new GraphException("Invalid graph (not enough edges)"); // Read each vertex weight. final String[] fields = scanner.nextLine().split("\t"); if (fields.length != vertexCount) throw new GraphException("Invalid graph (inconsistent vertex count)"); myVertices = new float[vertexCount]; for (int i = 0; i < vertexCount; i++) myVertices[i] = Float.parseFloat(fields[i].trim()); // Loop over each edge. myEdges = new ArrayList<Edge>(edgeCount); for (int i = 0; i < edgeCount; i++) { // Read and add the next edge, checking for duplicates. if (!scanner.hasNextLine()) throw new GraphException("Invalid graph (inconsistent edge count)"); final Edge edge = new Edge(scanner); myEdges.add(edge); // Check the constraints. if (edge.first() >= vertexCount || edge.second() >= vertexCount) throw new GraphException( "Invalid graph (Vertex index for edge too large)"); } // Verify there are no duplicate edges in the collection. verifyNoDuplicateEdges(myEdges); // Check there are not too many vertices defined. while (scanner.hasNextLine()) if (scanner.nextLine().trim().length() > 0) throw new GraphException("Invalid graph (inconsistent edge count)"); } catch (final GraphException e) { throw e; } catch (final NumberFormatException e) { throw new GraphException("Invalid graph (invalid number format)"); } catch (final Exception e) { final String name = e.getClass().getName(); throw new GraphException("Invalid graph (" + name + ")", e); } verifyConnectedGraph(this); } /** * Returns the edge at the specified index. * * @param index The index. * * @return The edge. */ public Edge edgeAt(final int index) { if (index < 0 || index >= myEdges.size()) throw new IllegalArgumentException("index"); return myEdges.get(index); } /** * Returns the number of edges. * * @return The number of edges. */ public int edges() { return myEdges.size(); } /** * Returns an iterator for the edges. * * @return An Edge iterator. */ public Iterator<Edge> iterator() { return myEdges.iterator(); } /** * Returns the string representation of the class. * * @return The string representation of the class. */ @Override public String toString() { final StringBuilder builder = new StringBuilder(); builder.append("V = {"); for (final float v : myVertices) builder.append(" " + v); builder.append(" }; E = {"); for (final Edge e : myEdges) builder.append(" " + e); builder.append(" }"); return builder.toString(); } /** * Returns the URL, if one was assigned. * * @return The URL. */ public URL url() { return myUrl; } /** * Returns the vertex weight at the specified index. * * @param index The index. * * @return The weight. */ public float vertexAt(final int index) { if (index < 0 || index >= myVertices.length) throw new IllegalArgumentException("index"); return myVertices[index]; } /** * Returns the number of vertices. * * @return The number of vertices. */ public int vertices() { return myVertices.length; } }