src.it.unimi.dsi.webgraph.ArrayListMutableGraph Maven / Gradle / Ivy
package it.unimi.dsi.webgraph;
/*
* Copyright (C) 2006-2011 Sebastiano Vigna
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*
*/
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.objects.ObjectArrays;
import it.unimi.dsi.lang.MutableString;
/** A very simple mutable graph class based on {@link it.unimi.dsi.fastutil.ints.IntArrayList}s.
*
* When creating examples for test cases or everyday usage, this class offers practical constructors.
* For instance, a 3-cycle is easily built as
*
* new ArrayListMutableGraph( 3, new int[][] { { 0, 1 }, { 1, 2 }, { 2, 0 } } )
*
*
* Moreover, methods like {@link #addNodes(int)} and {@link #addArc(int, int)} allow to change
* the graph structure after construction, and several static factory methods provides ready-made
* common graphs (see, e.g., {@link #newCompleteBinaryIntree(int)}).
*
*
A mutable graph is not an {@link it.unimi.dsi.webgraph.ImmutableGraph}. However,
* it is possible to obtain an {@linkplain #immutableView() immutable view} of a mutable graph.
* The view is valid until the exposed mutable graph is modified. A modification counter is used
* to cause a fail-fast behaviour in case the immutable view is used after modifications.
*
*
Warning: obtaining a {@link it.unimi.dsi.webgraph.NodeIterator} and using it
* while modifying the graph will lead to unpredictable results.
*/
public class ArrayListMutableGraph {
/** Current number of nodes. */
protected int n;
/** Current number of arcs. */
protected long m;
/** Current list of successor lists. The backing array might be longer than {@link #n}. */
protected IntArrayList successors[];
private final static IntArrayList[] EMPTY_INTARRAYLIST_ARRAY = {};
/** Guarantees that a node index is valid.
*
* @param x a node index.
*/
protected void ensureNode( final int x ) {
if ( x < 0 ) throw new IllegalArgumentException( "Illegal node index " + x );
if ( x >= n ) throw new IllegalArgumentException( "Node index " + x + " is larger than graph order (" + n + ")" );
}
/** Creates a new empty mutable graph. */
public ArrayListMutableGraph() {
successors = EMPTY_INTARRAYLIST_ARRAY;
}
/** Creates a new disconnected mutable graph with specified number of nodes.
* @param numNodes the number of nodes in the graph.
*/
public ArrayListMutableGraph( final int numNodes ) {
n = numNodes;
successors = new IntArrayList[ n ];
for( int i = n; i-- != 0; ) successors[ i ] = new IntArrayList();
}
/** Creates a new mutable graph using a given number of nodes and a given list of arcs.
*
* @param numNodes the number of nodes in the graph.
* @param arc an array of arrays of length 2, specifying the arcs; no sanity checks are performed..
*/
public ArrayListMutableGraph( final int numNodes, final int[][] arc ) {
this( numNodes );
m = arc.length;
// Sanitize
for( int i = arc.length; i-- != 0; ) {
if ( arc[ i ].length != 2 ) throw new IllegalArgumentException( "The arc of index " + i + " has length " + arc[ i ].length );
if ( arc[ i ][ 0 ] < 0 || arc[ i ][ 1 ] < 0 || arc[ i ][ 0 ] >= numNodes || arc[ i ][ 1 ] >= numNodes ) throw new IllegalArgumentException( "The arc of index " + i + " (" + arc[ i ][ 0 ] + ", " + arc[ i ][ 1 ] + ") is illegal" );
}
for( int i = 0; i < arc.length; i++ ) successors[ arc[ i ][ 0 ] ].add( arc[ i ][ 1 ] );
}
/** Creates a new mutable graph copying a given immutable graph.
*
*
This method will not invoke {@link ImmutableGraph#numNodes()}, but rather just create a {@link NodeIterator} and exhaust it.
*
* @param g an immutable graph.
*/
public ArrayListMutableGraph( final ImmutableGraph g ) {
this();
int d, s = -1;
long numArcs = 0;
for( NodeIterator nodeIterator = g.nodeIterator(); nodeIterator.hasNext(); ) {
s = nodeIterator.nextInt();
d = nodeIterator.outdegree();
numArcs += d;
successors = ObjectArrays.grow( successors, s + 1 );
successors[ s ] = new IntArrayList( nodeIterator.successorArray(), 0, d );
}
n = s + 1;
m = numArcs;
}
/** Creates a new mutable graph using a given number of nodes and a given arc filter.
*
* @param numNodes the number of nodes in the graph.
* @param arcFilter an arc filter which will specify which arcs go into the graph.
*/
public ArrayListMutableGraph( final int numNodes, final Transform.ArcFilter arcFilter ) {
this( numNodes );
for( int i = n; i-- != 0; ) {
for( int j = 0; j < n; j++ )
if ( arcFilter.accept( i, j ) ) {
successors[ i ].add( j );
m++;
}
}
}
/** Returns a new mutable graph containing a directed cycle.
*
* @param numNodes the number of nodes in the cycle.
*/
public static ArrayListMutableGraph newDirectedCycle( final int numNodes ) {
return new ArrayListMutableGraph( numNodes, new Transform.ArcFilter() {
public boolean accept( final int i, final int j ) {
return ( i + 1 ) % numNodes == j;
}
});
}
/** Returns a new mutable graph containing a bidirectional cycle.
*
* @param numNodes the number of nodes in the cycle.
*/
public static ArrayListMutableGraph newBidirectionalCycle( final int numNodes ) {
return new ArrayListMutableGraph( numNodes, new Transform.ArcFilter() {
public boolean accept( final int i, final int j ) {
return ( i + 1 ) % numNodes == j || ( j + 1 ) % numNodes == i;
}
});
}
/** Returns a new mutable graph containing a complete graph.
*
* @param numNodes the number of nodes in the graph.
* @param loops true if you want loops, too.
*/
public static ArrayListMutableGraph newCompleteGraph( final int numNodes, final boolean loops ) {
return new ArrayListMutableGraph( numNodes, new Transform.ArcFilter() {
public boolean accept( final int i, final int j ) {
return i != j || loops;
}
});
}
/** Returns a new mutable graph containing a complete binary in-tree of given height.
*
* Warning: starting from version 1.7, the spurious loop
* at the root has been removed.
*
* @param height the height of the tree (0 for the root only).
*/
public static ArrayListMutableGraph newCompleteBinaryIntree( final int height ) {
return new ArrayListMutableGraph( ( 1 << ( height + 1 ) ) - 1, new Transform.ArcFilter() {
public boolean accept( final int i, final int j ) {
return i != j && ( i - 1 ) / 2 == j;
}
});
}
/** Returns a new mutable graph containing a complete binary out-tree of given height.
*
* Warning: starting from version 1.7, the spurious loop
* at the root has been removed.
*
* @param height the height of the tree (0 for the root only).
*/
public static ArrayListMutableGraph newCompleteBinaryOuttree( final int height ) {
return new ArrayListMutableGraph( ( 1 << ( height + 1 ) ) - 1, new Transform.ArcFilter() {
public boolean accept( final int i, final int j ) {
return i != j && ( j - 1 ) / 2 == i;
}
});
}
private static class ImmutableView extends ImmutableGraph {
/** Cached number of nodes. */
final private int n;
/** Cached number of arcs. */
final private long m;
/** Cached successors. */
final private IntArrayList[] successors;
/** A reference to the mutable graph we expose. */
final private ArrayListMutableGraph g;
public ImmutableView( final ArrayListMutableGraph g ) {
this.g = g;
this.n = g.n;
this.m = g.m;
this.successors = g.successors;
}
public ImmutableView copy() { return this; };
private void ensureUnmodified() { if ( g.modificationCount != g.lastModificationCount ) throw new ConcurrentModificationException(); }
public int numNodes() { ensureUnmodified(); return n; }
public int outdegree( final int x ) { ensureUnmodified(); return successors[ x ].size(); }
public long numArcs() { ensureUnmodified(); return m; }
public boolean randomAccess() { return true; }
public int[] successorArray( final int x ) { ensureUnmodified(); return successors[ x ].toIntArray(); }
public LazyIntIterator successors( final int x ) { ensureUnmodified(); return LazyIntIterators.lazy( successors[ x ].iterator() ); }
}
/** A cached copy of the immutable view, if it has ever been requested. */
protected ImmutableView immutableView;
/** The current modification count. */
protected int modificationCount = 0;
/** The modification count at the last call to {@link #immutableView()}. */
protected int lastModificationCount = -1;
/** Returns an immutable view of this mutable graph.
*
*
The view can be used until this mutable graph is modified. Attempt to use
* the view after modifying this mutable graph will cause a {@link ConcurrentModificationException}.
* After modification, a new call to this method will return a new immutable view.
*
* @return an immutable view of this mutable graph.
*/
public ImmutableGraph immutableView() {
if ( modificationCount != lastModificationCount ) {
for( int i = n; i-- != 0; ) Arrays.sort( successors[ i ].elements(), 0, successors[ i ].size() );
immutableView = new ImmutableView( this );
}
lastModificationCount = modificationCount;
return immutableView;
}
public int numNodes() {
return n;
}
public int outdegree( final int x ) {
ensureNode( x );
return successors[ x ].size();
}
public long numArcs() {
return m;
}
public int[] successorArray( final int x ) {
ensureNode( x );
return successors[ x ].toIntArray();
}
public IntIterator successors( final int x ) {
ensureNode( x );
return successors[ x ].iterator();
}
/** Adds the given number of nodes, numbering them from {@link #numNodes()} onwards. The new nodes have no successors.
*
* @param numNewNodes the number of new nodes.
*/
public void addNodes( final int numNewNodes ) {
if ( numNewNodes != 0 ) {
modificationCount++;
final int newN = n + numNewNodes;
successors = ObjectArrays.ensureCapacity( successors, newN, n );
while( n < newN ) successors[ n++ ] = new IntArrayList();
}
}
/** Removes the given node. All arcs incident on the node are removed, too.
*
* @param x the node to be removed.
*/
public void removeNode( final int x ) {
ensureNode( x );
modificationCount++;
System.arraycopy( successors, x + 1, successors, x, --n - x );
int t;
for( int i = n; i-- != 0; )
for( int j = successors[ i ].size(); j-- != 0; ) {
t = successors[ i ].getInt( j );
if ( t == x ) successors[ i ].remove( j );
else if ( t > x ) successors[ i ].set( j, t - 1 );
}
}
/** Adds the given arc.
*
* @param x the start of the arc.
* @param y the end of the arc.
*/
public void addArc( final int x, final int y ) {
ensureNode( x );
ensureNode( y );
if ( successors[ x ].indexOf( y ) != -1 ) throw new IllegalArgumentException( "Node " + y + " is already a successor of node " + x );
modificationCount++;
successors[ x ].add( y );
m++;
}
/** Removes the given arc.
*
* @param x the start of the arc.
* @param y the end of the arc.
*/
public void removeArc( final int x, final int y ) {
ensureNode( x );
ensureNode( y );
final int pos = successors[ x ].indexOf( y );
if ( pos == -1 ) throw new IllegalArgumentException( "Node " + y + " is not a successor of node " + x );
modificationCount++;
successors[ x ].remove( pos );
m--;
}
/** Compare this mutable graph to another object.
*
* @return true iff the given object is a mutable graph the same size, and
* the successor list of every node of this graph is equal to the successor list of the corresponding node of o
.
*/
public boolean equals( final Object o ) {
if ( ! (o instanceof ArrayListMutableGraph ) ) return false;
final ArrayListMutableGraph g = (ArrayListMutableGraph ) o;
int n = numNodes();
if ( n != g.numNodes() ) return false;
int[] s, t;
int d;
while( n-- != 0 ) {
if ( ( d = outdegree( n ) ) != g.outdegree( n ) ) return false;
s = successorArray( n );
t = g.successorArray( n );
while( d-- != 0 ) if ( s[ d ] != t[ d ] ) return false;
}
return true;
}
/** Returns a hash code for this mutable graph.
*
* @return a hash code for this mutable graph.
*/
public int hashCode() {
int n = numNodes(), h = -1;
int[] s;
int d;
for( int i = 0; i < n; i++ ) {
h = h * 31 + i;
s = successorArray( i );
d = outdegree( i );
while( d-- != 0 ) h = h * 31 + s[ d ];
}
return h;
}
public String toString() {
MutableString ms = new MutableString();
IntIterator ii;
ms.append( "Nodes: " + numNodes() + "\nArcs: " + numArcs() + "\n" );
for ( int i = 0; i < numNodes(); i++ ) {
ms.append( "Successors of " + i + " (degree " + outdegree( i ) + "):" );
ii = successors( i );
while ( ii.hasNext() )
ms.append( " " + ii.nextInt() );
ms.append( "\n" );
}
return ms.toString();
}
}