org.jgrapht.io.Graph6Sparse6Importer Maven / Gradle / Ivy
The newest version!
/*
* (C) Copyright 2017-2017, by Joris Kinable and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* This program and the accompanying materials are dual-licensed under
* either
*
* (a) the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation, or (at your option) any
* later version.
*
* or (per the licensee's choosing)
*
* (b) the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation.
*/
package org.jgrapht.io;
import org.jgrapht.*;
import java.io.*;
import java.util.*;
/**
* 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.
*
* @author Joris Kinable
*
* @param graph vertex type
* @param graph edge type
*/
public class Graph6Sparse6Importer
extends
AbstractBaseImporter
implements
GraphImporter
{
enum Format
{
GRAPH6,
SPARSE6
}
private final double defaultWeight;
/* byte representation of the input string */
private byte[] bytes;
/* pointers which index a specific byte/bit in the vector bytes */
private int byteIndex, bitIndex = 0;
private Format format = Format.GRAPH6;
// ~ Constructors ----------------------------------------------------------
/**
* Construct a new Graph6Sparse6Importer
*
* @param vertexProvider provider for the generation of vertices. Must not be null.
* @param edgeProvider provider for the generation of edges. Must not be null.
* @param defaultWeight default edge weight
*/
public Graph6Sparse6Importer(
VertexProvider vertexProvider, EdgeProvider edgeProvider, double defaultWeight)
{
super(vertexProvider, edgeProvider);
this.defaultWeight = defaultWeight;
}
/**
* Construct a new Graph6Sparse6Importer
*
* @param vertexProvider provider for the generation of vertices. Must not be null.
* @param edgeProvider provider for the generation of edges. Must not be null.
*/
public Graph6Sparse6Importer(VertexProvider vertexProvider, EdgeProvider edgeProvider)
{
this(vertexProvider, edgeProvider, Graph.DEFAULT_EDGE_WEIGHT);
}
@Override
public void importGraph(Graph g, Reader input)
throws ImportException
{
// convert to buffered
BufferedReader in;
if (input instanceof BufferedReader) {
in = (BufferedReader) input;
} else {
in = new BufferedReader(input);
}
String g6 = "";
try {
g6 = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (g6.isEmpty())
throw new ImportException("Failed to read graph");
this.importGraph(g, g6);
}
/**
* Import the graph represented by a String in graph6 or sparse6 encoding.
*
* @param g the graph
* @param g6 String representation of a graph either in graph6 or sparse6 format. WARNING: a
* g6/s6 string may contain backslashes '\'. Escaping is required when invoking this
* method directly. E.g.
*
*
* importgraph(g,":?@MnDA\oi")
*
*
* may result in undefined behavior. This should have been:
*
*
* importgraph(g,":?@MnDA\\oi")
*
*
* @throws ImportException in case any error occurs, such as I/O or parse error
*/
public void importGraph(Graph g, String g6)
throws ImportException
{
g6 = g6.replace("\n", "").replace("\r", ""); // remove any new line characters
// Strip header. By default we assume the input Format is GRAPH6, unless stated otherwise
if (g6.startsWith(":")) {
g6 = g6.substring(1, g6.length());
this.format = Format.SPARSE6;
} else if (g6.startsWith(">>sparse6<<:")) {
g6 = g6.substring(12, g6.length());
this.format = Format.SPARSE6;
} else if (g6.startsWith(">>graph6<<"))
g6 = g6.substring(10, g6.length());
bytes = g6.getBytes();
validateInput();
byteIndex = bitIndex = 0;
// Number of vertices n
final int n = getNumberOfVertices();
// Add vertices to the graph
Map map = new HashMap<>();
for (int i = 0; i < n; i++) {
V vertex = vertexProvider.buildVertex("" + i, new HashMap<>());
map.put(i, vertex);
g.addVertex(vertex);
}
if (format == Format.GRAPH6)
this.readGraph6(g, map);
else
this.readSparse6(g, map);
}
private void readGraph6(Graph g, Map vertexIndexMap)
throws ImportException
{
// check whether there's enough data
int requiredBytes =
(int) Math.ceil(vertexIndexMap.size() * (vertexIndexMap.size() - 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 < vertexIndexMap.size(); i++) {
for (int j = 0; j < i; j++) {
int bit = getBits(1);
if (bit == 1) {
V from = vertexIndexMap.get(i);
V to = vertexIndexMap.get(j);
String label = "e_" + i + "_" + j;
E e = edgeProvider.buildEdge(from, to, label, new HashMap<>());
g.addEdge(from, to, e);
if (g.getType().isWeighted())
g.setEdgeWeight(e, defaultWeight);
}
}
}
}
private void readSparse6(Graph g, Map vertexIndexMap)
throws ImportException
{
int n = vertexIndexMap.size();
// 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 {
V from = vertexIndexMap.get(x);
V to = vertexIndexMap.get(v);
String label = "e_" + x + "_" + v;
E e = edgeProvider.buildEdge(from, to, label, new HashMap<>());
g.addEdge(from, to, e);
if (g.getType().isWeighted())
g.setEdgeWeight(e, defaultWeight);
}
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
*
* @return number of vertices in the graph
* @throws ImportException in case any error occurs, such as I/O or parse error
*/
private int getNumberOfVertices()
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.");
}
return 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 = 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;
}
}