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

org.jgrapht.nio.graph6.Graph6Sparse6EventDrivenImporter Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2017-2023, by Joris Kinable and Contributors.
 *
 * JGraphT : a free Java graph-theory library
 *
 * See the CONTRIBUTORS.md file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the
 * GNU Lesser General Public License v2.1 or later
 * which is available at
 * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
 */
package org.jgrapht.nio.graph6;

import org.jgrapht.alg.util.*;
import org.jgrapht.nio.*;

import java.io.*;

/**
 * Importer which reads graphs in graph6 or sparse6 format.
 * 
 * 

* A description of the format can be found * here. graph6 and sparse6 are * formats for storing undirected graphs in a compact manner, using only printable ASCII characters. * Files in these formats have text format and contain one line per graph. graph6 is suitable for * small graphs, or large dense graphs. sparse6 is more space-efficient for large sparse graphs. * Typically, files storing graph6 graphs have the 'g6' extension. Similarly, files storing sparse6 * graphs have a 's6' file extension. sparse6 graphs support loops and multiple edges, graph6 graphs * do not. * *

* Note that a g6/s6 string may contain backslashes '\'. Thus, escaping is required. E.g. * *

 * ":?@MnDA\oi"
 * 
* * may result in undefined behavior. This should have been: * *
 * ":?@MnDA\\oi"
 * 
* * @author Joris Kinable */ public class Graph6Sparse6EventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final String GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES = "Graph string seems to be corrupt. Invalid number of vertices."; enum Format { GRAPH6, SPARSE6 } // ~ Constructors ---------------------------------------------------------- /** * Construct a new importer */ public Graph6Sparse6EventDrivenImporter() { super(); } @Override public void importInput(Reader input) throws ImportException { // convert to buffered BufferedReader in; if (input instanceof BufferedReader) { in = (BufferedReader) input; } else { in = new BufferedReader(input); } notifyImportEvent(ImportEvent.START); // read line String g6 = null; try { g6 = in.readLine(); } catch (IOException e) { throw new ImportException("Failed to read graph: " + e.getMessage()); } if (g6.isEmpty()) { throw new ImportException("Failed to read graph: empty line"); } // remove any new line characters g6 = g6.replace("\n", "").replace("\r", ""); // do the actual parsing new Parser(g6).parse(); notifyImportEvent(ImportEvent.END); } /** * The actual parser. The parser assumes the input is a single line. */ private class Parser { private Format format; private byte[] bytes; private int byteIndex; private int bitIndex; private int n; /** * Create a new parser. * * @param inputLine an input line */ public Parser(String inputLine) { this.format = Format.GRAPH6; if (inputLine.startsWith(":")) { inputLine = inputLine.substring(1, inputLine.length()); this.format = Format.SPARSE6; } else if (inputLine.startsWith(">>sparse6<<:")) { inputLine = inputLine.substring(12, inputLine.length()); this.format = Format.SPARSE6; } else if (inputLine.startsWith(">>graph6<<")) { inputLine = inputLine.substring(10, inputLine.length()); } this.bytes = inputLine.getBytes(); this.byteIndex = 0; this.bitIndex = 0; this.n = 0; } public void parse() { validateInput(); readNumberOfVertices(); notifyVertexCount(n); for (int i = 0; i < n; i++) { notifyVertex(i); } if (format == Format.GRAPH6) readGraph6(); else readSparse6(); } private void readGraph6() throws ImportException { // check whether there's enough data int requiredBytes = (int) Math.ceil(n * (n - 1) / 12.0) + byteIndex; if (bytes.length < requiredBytes) throw new ImportException( "Graph string seems to be corrupt. Not enough data to read graph6 graph"); // Read the lower triangle of the adjacency matrix of G for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) { int bit = getBits(1); if (bit == 1) { notifyEdge(Pair.of(i, j)); } } } } private void readSparse6() throws ImportException { // number of bits needed to represent n-1 in binary int k = (int) Math.ceil(Math.log(n) / Math.log(2)); // Current vertex int v = 0; // The remaining bytes encode a sequence b[0] x[0] b[1] x[1] b[2] x[2] ... b[m] x[m] // Read blocks. In decoding, an incomplete (b,x) pair at the end is discarded. int dataBits = bytes.length * 6 - (byteIndex * 6 + bitIndex); while (dataBits >= 1 + k) { // while there's data remaining int b = getBits(1); // Read x[i] int x = getBits(k); // Read b[i] if (b == 1) v++; if (v >= n) // Ignore the last bit, this is just padding break; if (x > v) v = x; else notifyEdge(Pair.of(x, v)); dataBits -= 1 + k; } } /** * Check whether the g6 or s6 encoding contains any obvious errors * * @throws ImportException in case any error occurs, such as I/O or parse error */ private void validateInput() throws ImportException { for (byte b : bytes) if (b < 63 || b > 126) throw new ImportException( "Graph string seems to be corrupt. Illegal character detected: " + b); } /** * Read the number of vertices in the graph * * @throws ImportException in case any error occurs, such as I/O or parse error */ private void readNumberOfVertices() throws ImportException { // Determine whether the number of vertices is encoded in 1, 4 or 8 bytes. int n; if (bytes.length > 8 && bytes[0] == 126 && bytes[1] == 126) { byteIndex += 2; // Strip the first 2 garbage bytes n = getBits(36); if (n < 258048) throw new ImportException( GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES); } else if (bytes.length > 4 && bytes[0] == 126) { byteIndex++; // Strip the first garbage byte n = getBits(18); if (n < 63 || n > 258047) throw new ImportException( GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES); } else { n = getBits(6); if (n < 0 || n > 62) throw new ImportException( GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES); } this.n = n; } /** * Converts the next k bits of data to an integer * * @param k number of bits * @return the next k bits of data represented by an integer */ private int getBits(int k) throws ImportException { int value = 0; // Read minimum{bits we need, remaining bits in current byte} if (bitIndex > 0 || k < 6) { int x = Math.min(k, 6 - bitIndex); int mask = (1 << x) - 1; int y = (bytes[byteIndex] - 63) >> (6 - bitIndex - x); y &= mask; value = (value << k) + y; k -= x; bitIndex += x; if (bitIndex == 6) { byteIndex++; bitIndex = 0; } } // Read blocks of 6 bits at a time int blocks = k / 6; for (int j = 0; j < blocks; j++) { value = (value << 6) + bytes[byteIndex] - 63; byteIndex++; k -= 6; } // Read remaining bits if (k > 0) { int y = bytes[byteIndex] - 63; y = y >> (6 - k); value = (value << k) + y; bitIndex = k; } return value; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy