package uk.ac.rhul.cs.graph;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import com.sosnoski.util.array.DoubleArray;
import com.sosnoski.util.array.IntArray;
import com.sosnoski.util.array.StringArray;
import com.sosnoski.util.hashmap.ObjectIntHashMap;
/**
* Graph class used in ClusterONE
*
* This class uses a simple adjacency list representation in the background.
*/
public class Graph implements Iterable<Edge> {
/**
* Whether the graph is directed
*/
protected boolean directed = false;
/**
* The number of nodes in this graph
*/
protected int numberOfNodes = 0;
/**
* The list of names for each node in this graph
*/
protected StringArray nodeNames = new StringArray();
/**
* The list of source nodes for each edge in this graph. The maximum growth limit of the array is overridden
* to allow us to scale above 2<sup>30</sup> edges; otherwise the array would try to double its size when
* it reaches 2<sup>30</sup> edges and it would fail because the maximum allowed array size in Java is
* less than 2<sup>31</sup>.
*/
protected IntArray edgesOut = new IntArray(32, 536870912 /* = 2^29 */);
/**
* The list of target nodes for each edge in this graph. See the comment for <code>edgesOut</code> for an
* explanation of the growth arguments.
*/
protected IntArray edgesIn = new IntArray(32, 536870912 /* = 2^29 */);
/**
* The list of weights for each edge in this graph. See the comment for <code>edgesOut</code> for an
* explanation of the growth arguments.
*/
protected DoubleArray weights = new DoubleArray(32, 536870912 /* = 2^29 */);
/**
* The list of outgoing edge IDs for each node
*/
protected ArrayList<IntArray> outEdgeAdjacencyLists = new ArrayList<IntArray>();
/**
* The list of incoming edge IDs for each node
*/
protected ArrayList<IntArray> inEdgeAdjacencyLists = new ArrayList<IntArray>();
/**
* Constructs an empty undirected graph
*/
public Graph() {
this(false);
}
/**
* Constructs an empty graph
*
* @param directed whether the graph will be directed or not
*/
public Graph(boolean directed) {
this.directed = directed;
}
/**
* Checks whether the graph is directed
*/
public boolean isDirected() { return directed; }
/**
* Checks whether the graph is weighted
*
* A graph is weighted if not all its weights are equal. Graphs with no
* edges are considered unweighted.
*/
public boolean isWeighted() {
if (weights.size() == 0)
return false;
double firstWeight = weights.get(0);
for (int i = 1; i < weights.size(); i++)
if (weights.get(i) != firstWeight)
return true;
return false;
}
/**
* Returns whether the two given nodes are connected.
*
* @param source the source node
* @param target the target node
*/
public boolean areConnected(int source, int target) {
IntArray edges = outEdgeAdjacencyLists.get(source);
int i, n = edges.size();
for (i = 0; i < n; i++) {
int edge = edges.get(i);
if (edgesIn.get(edge) == target)
return true;
}
return false;
}
/**
* Creates a new unnamed node and returns its index
*/
public int createNode() {
return this.createNode(null);
}
/**
* Creates a new node with the given name and returns its index
*
* @param name the name of the node
*/
public int createNode(String name) {
numberOfNodes++;
outEdgeAdjacencyLists.add(new IntArray());
inEdgeAdjacencyLists.add(new IntArray());
nodeNames.add(name);
return numberOfNodes-1;
}
/**
* Create some new nodes in the graph.
*
* @return an array of length new_node_count containing the indices of the newly created nodes
*/
public int[] createNodes(int new_node_count) {
int[] result = new int[new_node_count];
int n = numberOfNodes;
for (int i = 0; i < new_node_count; i++) {
outEdgeAdjacencyLists.add(new IntArray());
inEdgeAdjacencyLists.add(new IntArray());
nodeNames.add(null);
result[i] = n+i;
}
numberOfNodes += new_node_count;
return result;
}
/**
* Creates a new edge between nodes with the given indices.
*
* @param src the source node
* @param dest the target node
*
* @return the index of the new edge
*/
public int createEdge(int src, int dest) {
return createEdge(src, dest, 1.0);
}
/**
* Creates a new edge between nodes with the given indices and the given weight
*
* @param src the source node
* @param dest the target node
* @param weight the weight of the edge
*
* @return the index of the new edge
*/
public int createEdge(int src, int dest, double weight) {
if (src >= numberOfNodes)
createNodes(src - numberOfNodes + 1);
if (dest >= numberOfNodes)
createNodes(dest - numberOfNodes + 1);
int edgeID = edgesOut.size();
edgesOut.add(src);
edgesIn.add(dest);
weights.add(weight);
outEdgeAdjacencyLists.get(src).add(edgeID);
inEdgeAdjacencyLists.get(dest).add(edgeID);
if (!directed) {
outEdgeAdjacencyLists.get(dest).add(edgeID);
inEdgeAdjacencyLists.get(src).add(edgeID);
}
return edgeID;
}
/**
* Returns the number of nodes in the graph
*/
public int getNodeCount() {
return numberOfNodes;
}
/**
* Returns the number of edges in the graph
*/
public int getEdgeCount() {
return edgesOut.size();
}
/**
* Returns the indices of all nodes adjacent to the node at the specified index
*
* If a node is connected to the query node by multiple edges, the node will
* be returned multiple times.
*
* @param nodeIndex the index of the query node
* @param mode directedness mode. Ignored if the graph is undirected.
*/
public int[] getAdjacentNodeIndicesArray(int nodeIndex, Directedness mode) {
int[] edges = this.getAdjacentEdgeIndicesArray(nodeIndex, mode);
int i, n = edges.length;
for (i = 0; i < n; i++) {
int edge = edges[i];
if (this.edgesIn.get(edge) == nodeIndex)
edges[i] = this.edgesOut.get(edge);
else
edges[i] = this.edgesIn.get(edge);
}
return edges;
}
/**
* Returns the indices of all edges adjacent to the node at the specified index
*
* @param nodeIndex the index of the node
* @param mode directedness mode. Ignored if the graph is undirected.
*/
public int[] getAdjacentEdgeIndicesArray(int nodeIndex, Directedness mode) {
if (!directed || mode == Directedness.OUT) {
return outEdgeAdjacencyLists.get(nodeIndex).toArray();
}
if (mode == Directedness.IN) {
return inEdgeAdjacencyLists.get(nodeIndex).toArray();
}
int[] outEdgesArray = outEdgeAdjacencyLists.get(nodeIndex).toArray();
int[] inEdgesArray = inEdgeAdjacencyLists.get(nodeIndex).toArray();
int i = outEdgesArray.length, n = i + inEdgesArray.length, j = 0;
int[] result = new int[n];
result = Arrays.copyOf(outEdgesArray, n);
while (i < n) {
result[i] = inEdgesArray[j];
i++; j++;
}
return result;
}
/**
* Returns the name of a given node
*
* @param nodeIndex the index of the node
*/
public String getNodeName(int nodeIndex) { return this.nodeNames.get(nodeIndex); }
/**
* Returns the name of all nodes
*/
public String[] getNodeNames() { return this.nodeNames.toArray(); }
/**
* Returns a hash mapping node names to node indices
*/
public ObjectIntHashMap getNodeNameHashMap() {
ObjectIntHashMap map = new ObjectIntHashMap();
String[] nodeNames = this.getNodeNames();
for (int i = 0; i < this.numberOfNodes; i++)
map.add(nodeNames[i], i);
return map;
}
/**
* Returns the weight of a given edge
*
* @param edgeIndex the index of the edge
*/
public double getEdgeWeight(int edgeIndex) { return this.weights.get(edgeIndex); }
/**
* Returns the weight of all edges
*/
public double[] getEdgeWeights() {
return this.weights.toArray();
}
/**
* Returns one endpoint of a given edge
*
* @param edgeIndex the index of the edge
* @param knownVertex the vertex index of a known endpoint of the edge. The method will
* return the vertex index of the other endpoint. The behaviour
* is unspecified if knownVertex is not an endpoint index.
*/
public int getEdgeEndpoint(int edgeIndex, int knownVertex) {
int idx = edgesOut.get(edgeIndex);
if (idx == knownVertex)
return edgesIn.get(edgeIndex);
return idx;
}
/**
* Returns an iterator that iterates over all the edges of this graph
*/
public Iterator<Edge> iterator() {
return new EdgeIterator(this);
}
/**
* Returns the edge list of the graph
*/
public List<Edge> getEdgeList() {
List<Edge> result = new ArrayList<Edge>(this.getEdgeCount());
for (Edge edge: this)
result.add(edge);
return result;
}
/**
* Returns the number of distinct edges incident on the node with the given index
*/
public int getDegree(int nodeIndex) {
return getDegree(nodeIndex, Directedness.ALL);
}
/**
* Returns the number of distinct in/outedges incident on the node with the given index
*/
public int getDegree(int nodeIndex, Directedness mode) {
if (!directed || mode == Directedness.OUT)
return outEdgeAdjacencyLists.get(nodeIndex).size();
if (mode == Directedness.IN)
return inEdgeAdjacencyLists.get(nodeIndex).size();
return outEdgeAdjacencyLists.get(nodeIndex).size() + inEdgeAdjacencyLists.get(nodeIndex).size();
}
/**
* Returns the total weight of edges incident on the node with the given index
*/
public double getStrength(int nodeIndex) {
return getStrength(nodeIndex, Directedness.ALL);
}
/**
* Returns the total weight of in/outedges incident on the node with the given index
*/
public double getStrength(int nodeIndex, Directedness mode) {
IntArray neis = null;
double result = 0.0;
if (!directed || mode != Directedness.IN) {
neis = outEdgeAdjacencyLists.get(nodeIndex);
for (int eidx: neis.toArray())
result += weights.get(eidx);
}
if (directed && mode == Directedness.IN) {
neis = inEdgeAdjacencyLists.get(nodeIndex);
for (int eidx: neis.toArray())
result += weights.get(eidx);
}
return result;
}
}