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

org.jgrapht.io.Graph6Sparse6Importer Maven / Gradle / Ivy

There is a newer version: 1.5.2
Show 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 java.io.*;
import java.util.*;

import org.jgrapht.*;

/**
 * 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; } private String getBitString(int i){ return String.format("%8s", Integer.toBinaryString(i & 0xFF)).replace(' ', '0'); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy