package bgu.bio.adt.graphs;
import gnu.trove.list.array.TIntArrayList;
import java.util.ArrayList;
import bgu.bio.io.file.json.JSONArray;
import bgu.bio.io.file.json.JSONException;
import bgu.bio.io.file.json.JSONObject;
/**
* A class representing a <b>undirected</b> graph using array lists
*
* @author milon
*/
public class FlexibleUndirectedGraph {
/**
* Holds the amount of nodes in the graph
*/
protected int nodeNum;
/**
* Holds the amount of edges in a graph
*/
protected int edgeNum;
/**
* Information on outgoing edges in the graph
*/
private ArrayList<TIntArrayList> adjLists;
/**
* Maximal out degree in the graph
*/
private int maxDegree;
public FlexibleUndirectedGraph() {
this.adjLists = new ArrayList<TIntArrayList>();
nodeNum = 0;
edgeNum = 0;
maxDegree = 0;
}
/**
* Add Node to the end of the list
*/
/**
* @return the id of the new node
*/
public int addNode() {
if (this.adjLists.size() == this.nodeNum) {
this.adjLists.add(new TIntArrayList());
}
this.nodeNum++;
if (maxDegree < 0) {
maxDegree = 0;
}
return this.nodeNum;
}
/**
* Add an edge between two nodes
*
* @param u
* first node
* @param v
* second node
*/
public void addEdge(int u, int v) {
if (u >= this.nodeNum || v >= this.nodeNum) {
throw new IndexOutOfBoundsException(
"Node index isn't in the current graph");
}
final TIntArrayList uList = this.adjLists.get(u);
final TIntArrayList vList = this.adjLists.get(v);
uList.add(v);
vList.add(u);
// check if the new edge turns the current node to the one with the
// maximum degree
if (this.maxDegree < uList.size()) {
this.maxDegree = uList.size();
}
if (this.maxDegree < vList.size()) {
this.maxDegree = vList.size();
}
this.edgeNum++;
}
/**
* Remove an edge between two nodes
*
* @param u
* first node
* @param v
* second node
*/
public void removeEdge(int u, int v) {
final TIntArrayList uList = this.adjLists.get(u);
if (uList.size() == 1) {
uList.resetQuick();
} else {
int index = this.getNeighborIx(u, v);
// switch the out edge in the list
uList.set(index, uList.get(uList.size() - 1));
uList.removeAt(index);
}
final TIntArrayList vList = this.adjLists.get(v);
if (vList.size() == 1) {
vList.resetQuick();
} else {
int index = this.getNeighborIx(v, u);
// switch the out edge in the list
vList.set(index, vList.get(vList.size() - 1));
vList.removeAt(index);
}
edgeNum--;
calcMaxDeg();
}
/**
* Removes all the nodes
*
* @param u
*/
public void detachNode(int u) {
while (deg(u) > 0) {
removeEdge(u, getNeighbor(u, 0));
}
}
/**
* Return the number of nodes in the graph
*
* @return the number of nodes in the graph
*/
public int getNodeNum() {
return nodeNum;
}
/**
* Return the degree of a node
*
* @param nodeIx
* the node ix
* @return the number of adjacent nodes
*/
public int deg(int nodeIx) {
return adjLists.get(nodeIx).size();
}
/**
* Gets the neighbor given an index in the list.
*
* @param node
* the node index
* @param neighborIx
* the neighbor index in the list
* @return the neighbor id
*/
public int getNeighbor(int node, int neighborIx) {
return adjLists.get(node).get(neighborIx);
}
/**
* Gets the neighbor index in the out adjacent list.
*
* @param node
* the node
* @param neighbor
* the neighbor id
* @return the neighbor index in the list of the node
*/
public int getNeighborIx(int node, int neighbor) {
final TIntArrayList is = adjLists.get(node);
for (int ix = 0; ix < is.size(); ++ix) {
if (is.get(ix) == neighbor) {
return ix;
}
}
return -1;
}
/**
* Return the number of edges in the graph
*
* @return the number of the edges in the graph
*/
public int getEdgeNum() {
return this.edgeNum;
}
/**
* Return the maximal degree in the graph
*
* @return the maximal degree in the graph
*/
public int getMaxDeg() {
return maxDegree;
}
private void calcMaxDeg() {
this.maxDegree = -1;
for (int i = 0; i < this.adjLists.size(); i++) {
if (this.adjLists.get(i).size() > maxDegree) {
maxDegree = this.adjLists.get(i).size();
}
}
}
/**
* Check if the given nodes are connected in the graph
*
* @param u
* first node
* @param v
* second node
* @return true if and only if u is connected to v
*/
public boolean isNeighbours(int u, int v) {
final TIntArrayList adj = adjLists.get(u);
for (int i = 0; i < adj.size(); i++) {
if (adj.get(i) == v) {
return true;
}
}
return false;
}
public void clear() {
for (int i = 0; i < nodeNum; i++) {
this.adjLists.get(i).resetQuick();
}
this.maxDegree = 0;
this.nodeNum = 0;
this.edgeNum = 0;
}
/**
* Calculate the degeneracy of the graph
*
* @return the degeneracy of the given graph
*/
public int degeneracy() {
return degeneracy(null);
}
/**
* Calculate the degeneracy of the graph
*
* @param list
* if the list is not null it will contain the degeneracy order
* of the nodes
* @return the degeneracy of the given graph
*/
public int degeneracy(TIntArrayList list) {
int deg = 0;
TIntArrayList[] dArray = new TIntArrayList[maxDegree + 1];
int[] currentDeg = new int[nodeNum];
for (int i = 0; i < dArray.length; i++) {
dArray[i] = new TIntArrayList();
}
// add the initial degree of each node, ordered by id
for (int i = 0; i < nodeNum; i++) {
dArray[deg(i)].add(i);
currentDeg[i] = deg(i);
}
int nodesLeft = nodeNum;
int i = 0;
while (nodesLeft > 0) {
while (dArray[i].size() == 0) {
i++;
}
int min = i;
final int v = dArray[i].removeAt(dArray[i].size() - 1);
if (list != null) {
list.add(v);
}
currentDeg[v] = -1; // remove information on the node
// decrease the degree of all the neighbors of node
TIntArrayList adj = adjLists.get(v);
deg = Math.max(deg, i);
for (int ind = 0; ind < adj.size(); ind++) {
final int w = adj.get(ind);
// only check the node if it wasn't removed
if (currentDeg[w] != -1) {
// TODO: make the removal faster
dArray[currentDeg[w]].remove(w);
currentDeg[w] = currentDeg[w] - 1;
if (currentDeg[w] < min) {
min = currentDeg[w];
}
dArray[currentDeg[w]].add(w);
}
}
nodesLeft--;
i = min;
}
if (list != null) {
list.reverse();
}
return deg;
}
/**
* Return a representation of the graph in Graphviz format
*
* @return String representation of the graph in Graphviz format
*/
public String toDOTFormat() {
StringBuilder sb = new StringBuilder();
sb.append("graph graphname {\n");
for (int i = 0; i < nodeNum; i++) {
sb.append("n" + i + ";\n");
}
for (int i = 0; i < nodeNum; i++) {
TIntArrayList outList = this.adjLists.get(i);
for (int oInd = 0; oInd < outList.size(); oInd++) {
if (i < outList.get(oInd)) {
sb.append("n" + i + " -- n" + outList.get(oInd) + ";\n");
}
}
}
sb.append("}\n");
return sb.toString();
}
public String toGMLFormat() {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" "
+ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+ "xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns "
+ "http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">\n");
sb.append("<graph id=\"G\" edgedefault=\"undirected\">\n");
for (int i = 0; i < nodeNum; i++) {
sb.append("<node id=\"n" + i + "\"/>\n");
}
int edge = 0;
for (int i = 0; i < nodeNum; i++) {
TIntArrayList outList = this.adjLists.get(i);
for (int oInd = 0; oInd < outList.size(); oInd++) {
if (i < outList.get(oInd)) {
sb.append("<edge id=\"e" + edge + "\" source=\"n" + i
+ "\" target=\"n" + outList.get(oInd) + "\"/>\n");
edge++;
}
}
}
sb.append("</graph>");
sb.append("</graphml>\n");
return sb.toString();
}
public String toXGMMLFormat() {
StringBuilder sb = new StringBuilder();
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n");
sb.append("<graph label=\"graph\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
+ "xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" "
+ "xmlns:cy=\"http://www.cytoscape.org\" xmlns=\"http://www.cs.rpi.edu/XGMML\" directed=\"0\">\n");
for (int i = 0; i < nodeNum; i++) {
sb.append("<node id=\"n" + i + "\" label=\"n" + i + "\">\n");
sb.append("\t<att name=\"size\" type=\"integer\" value=\"1\"/>\n");
// + "<att name=\"confirmed\" type=\"boolean\" value=\"true\"/>");
sb.append("</node>\n");
}
int edge = 0;
for (int i = 0; i < nodeNum; i++) {
TIntArrayList outList = this.adjLists.get(i);
for (int oInd = 0; oInd < outList.size(); oInd++) {
if (i < outList.get(oInd)) {
sb.append("<edge label=\"d\" id=\"e" + edge
+ "\" source=\"n" + i + "\" target=\"n"
+ outList.get(oInd) + "\"/>\n");
edge++;
}
}
}
sb.append("</graph>\n");
return sb.toString();
}
/**
* Copy the current graph into the given {@link FlexibleUndirectedGraph}.
* The given graph is cleared before the copy using {@link #clear()}.
*
* @param other
* the graph that we want to copy the information to.
*/
public void copyTo(FlexibleUndirectedGraph other) {
other.clear();
for (int i = 0; i < nodeNum; i++) {
other.addNode();
}
for (int u = 0; u < nodeNum; u++) {
TIntArrayList adj = this.adjLists.get(u);
for (int d = 0; d < adj.size(); d++) {
final int v = adj.get(d);
if (u < v) {
other.addEdge(u, v);
}
}
}
}
/**
* Calculate the density of the graph
*
* @return the density of the graph
*/
public double density() {
return (2.0 * this.edgeNum) / (this.nodeNum * (this.nodeNum - 1));
}
/**
* Split the current graph <V,E> into two subgraphs G<sub>1</sub> and
* G<sub>2</sub> according to group W, both subgraphs contains all the nodes
* in V but G<sub>1</sub> contains E<sub>1</sub>⊆E for each edge (u,v)
* in E<sub>1</sub> which both u,v∈W;. G<sub>1</sub> contains only
* edges (u,v) from E where both u,v∈(V\W)
*
* @param list
* - assume that the list is ordered (for searching)
* @param graph1
* - an empty {@link FlexibleUndirectedGraph}.
* @param graph2
* - an empty {@link FlexibleUndirectedGraph}.
*/
public void split(TIntArrayList list, FlexibleUndirectedGraph graph1,
FlexibleUndirectedGraph graph2) {
for (int i = 0; i < nodeNum; i++) {
graph1.addNode();
graph2.addNode();
}
for (int u = 0; u < nodeNum; u++) {
boolean firstInG1 = list != null ? list.binarySearch(u) >= 0 : true;
boolean firstInG2 = !firstInG1;
TIntArrayList adj = adjLists.get(u);
for (int d = 0; d < adj.size(); d++) {
final int v = adj.get(d);
if (u < v) {
boolean secondInG1 = list != null ? list.binarySearch(v) >= 0
: true;
boolean secondInG2 = !secondInG1;
if (firstInG1 && secondInG1) {
graph1.addEdge(u, v);
} else if (firstInG2 && secondInG2) {
graph2.addEdge(u, v);
}
}
}
}
}
public JSONObject toJSON() throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("nodes", this.getNodeNum());
JSONArray array = new JSONArray();
for (int i = 0; i < nodeNum; i++) {
JSONArray inner = new JSONArray();
TIntArrayList adj = adjLists.get(i);
for (int d = 0; d < adj.size(); d++) {
if (adj.get(d) > i) {
inner.put(adj.get(d));
}
}
array.put(inner);
}
jsonObject.put("edges", array);
return jsonObject;
}
public static FlexibleUndirectedGraph fromJSON(JSONObject json)
throws JSONException {
FlexibleUndirectedGraph graph = new FlexibleUndirectedGraph();
for (int i = 0; i < json.getInt("nodes"); i++) {
graph.addNode();
}
JSONArray edges = json.getJSONArray("edges");
for (int i = 0; i < graph.getNodeNum(); i++) {
JSONArray current = edges.getJSONArray(i);
for (int x = 0; x < current.length(); x++) {
graph.addEdge(i, current.getInt(x));
}
}
return graph;
}
public String stats() {
StringBuilder sb = new StringBuilder();
sb.append("Density = " + density());
sb.append("\tNodes = " + nodeNum);
sb.append("\tEdges = " + edgeNum);
sb.append("\tMaxDegree = " + maxDegree);
sb.append("\tDegeneracy = " + degeneracy());
return sb.toString();
}
}