org.jgrapht.nio.graph6.Graph6Sparse6Exporter Maven / Gradle / Ivy
/*
* (C) Copyright 2017-2020, 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.*;
import org.jgrapht.nio.*;
import java.io.*;
import java.util.*;
/**
* Exporter which exports 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.
*
* In particular, the length of a Graph6 string representation of a graph depends only on the number
* of vertices. However, this also means that graphs with few edges take as much space as graphs
* with many edges. On the other hand, Sparse6 is a variable length format which can use
* dramatically less space for sparse graphs but can have a much larger storage size for dense
* graphs.
*
* @author Joris Kinable
*
* @param graph vertex type
* @param graph edge type
*/
public class Graph6Sparse6Exporter
implements
GraphExporter
{
/**
* Format type: graph6 (g6) or sparse6(s6)
*/
public enum Format
{
GRAPH6,
SPARSE6
}
private Format format;
private ByteArrayOutputStream byteArrayOutputStream;
/**
* The default format used by the exporter.
*/
public static final Format DEFAULT_GRAPH6SPARSE6_FORMAT = Format.GRAPH6;
/**
* Constructs a new exporter with a given vertex ID provider.
*
*/
public Graph6Sparse6Exporter()
{
this(DEFAULT_GRAPH6SPARSE6_FORMAT);
}
/**
* Constructs a new exporter with a given vertex ID provider.
*
* @param format the format to use
*/
public Graph6Sparse6Exporter(Format format)
{
this.format = Objects.requireNonNull(format, "Format cannot be null");
}
@Override
public void exportGraph(Graph g, Writer writer)
throws ExportException
{
GraphTests.requireUndirected(g);
if (format == Format.GRAPH6 && !GraphTests.isSimple(g))
throw new ExportException(
"Graphs exported in graph6 format cannot contain loops or multiple edges.");
// Map all vertices to a unique integer
List vertices = new ArrayList<>(g.vertexSet());
byteArrayOutputStream = new ByteArrayOutputStream();
currentByte = 0;
bitIndex = 0;
try {
if (format == Format.SPARSE6)
writeSparse6(g, vertices);
else
writeGraph6(g, vertices);
} catch (IOException e) {
e.printStackTrace();
}
String g6 = "";
try {
g6 = byteArrayOutputStream.toString("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
PrintWriter out = new PrintWriter(writer);
out.print(g6);
out.flush();
}
private void writeSparse6(Graph g, List vertices)
throws IOException
{
int[][] edges = new int[g.edgeSet().size()][2];
int index = 0;
for (int j = 0; j < vertices.size(); j++) {
for (int i = 0; i <= j; i++) {
if (g.containsEdge(vertices.get(i), vertices.get(j))) {
for (int p = 0; p < g.getAllEdges(vertices.get(i), vertices.get(j)).size();
p++)
{
edges[index][0] = i;
edges[index][1] = j;
index++;
}
}
}
}
// sparse6 format always starts with ":"
byteArrayOutputStream.write(":".getBytes());
writeNumberOfVertices(vertices.size());
// number of bits needed to represent n-1 in binary
int k = (int) Math.ceil(Math.log(vertices.size()) / Math.log(2));
int m = 0;
int v = 0;
while (m < edges.length) {
if (edges[m][1] > v + 1) {
writeBit(true);
writeIntInKBits(edges[m][1], k);
v = edges[m][1];
} else if (edges[m][1] == v + 1) {
writeBit(true);
writeIntInKBits(edges[m][0], k);
v++;
m++;
} else {
writeBit(false);
writeIntInKBits(edges[m][0], k);
m++;
}
}
// Pad right hand side with '1's to fill the last byte. This may not be the 'correct' way of
// padding as
// described in the sparse6 format descr, but it's hard to make sense of the sparse6
// description. This seems to work fine.
if (bitIndex != 0) {
int padding = 6 - bitIndex;
for (int i = 0; i < padding; i++)
writeBit(true);
writeByte(); // push the last byte
}
}
private void writeGraph6(Graph g, List vertices)
throws IOException
{
writeNumberOfVertices(vertices.size());
// Write the lower triangle of the adjacency matrix of G as a bit vector x of length
// n(n-1)/2,
// using the ordering (0,1),(0,2),(1,2),(0,3),(1,3),(2,3),...,(n-1,n).
for (int i = 0; i < vertices.size(); i++)
for (int j = 0; j < i; j++)
writeBit(g.containsEdge(vertices.get(i), vertices.get(j)));
writeByte(); // Finish writing the last byte
}
private void writeNumberOfVertices(int n)
throws IOException
{
assert n >= 0;
if (n <= 62)
byteArrayOutputStream.write(n + 63);
else if (n <= 258047) {
// write number in 4 bytes
writeIntInKBits(63, 6);
writeIntInKBits(n, 18);
} else {
// write number in 8 bytes
writeIntInKBits(63, 6);
writeIntInKBits(63, 6);
writeIntInKBits(n, 36);
}
}
private byte currentByte;
private int bitIndex;
private void writeIntInKBits(int number, int k)
{
for (int i = k - 1; i >= 0; i--)
writeBit((number & (1 << i)) != 0);
}
private void writeBit(boolean bit)
{
if (bitIndex == 6)
writeByte();
if (bit)
currentByte |= 1 << (5 - bitIndex);
bitIndex++;
}
private void writeByte()
{
byteArrayOutputStream.write(currentByte + 63);
currentByte = 0;
bitIndex = 0;
}
}