/** * represents an undirected graph */ export class Graph { /** * creates a new empty graph. * */ constructor( ) { this.adjacent = new Map(); this.vertexAttribute = new Map(); } /** * (vertex:integer) => this * @param vertex {Number} a vertex * @return this graph * */ addVertex(vertex) { if (! this.adjacent.has(vertex)) { let [isInteger, i] = toInteger(vertex); if (isInteger) { this.adjacent.set(i, []); } else { throw new TypeError(`Argument ${vertex} is not a valid vertex`) } } return this; } /** * (u: integer, v: integer) => undefined * @param u {Number} a vertex of edge * @param v {Number} a vertex of edge * */ addEdge(u, v) { this.addVertex(u); this.addVertex(v); const vNeighbor = this.adjacent.get(v); if (!vNeighbor.includes(u) ) { vNeighbor.push(u); } const uNeighbor = this.adjacent.get(u); if(!uNeighbor.includes(v)) { uNeighbor.push(v); } return this; } /** * merge a map of attribute to a vertex. Usage example: * ``` * let g = new Graph(); * g.addVertex(1); * g.mergeAttribute(1, {'color': 0}); * ``` * @param v {Number} a vertex of graph * @param p {object|Map} attributes of the vertex, an empty object is allowed, but * undefined, empty array is not allow * */ mergeAttribute(v, p) { if (!this.adjacent.has(v)) { throw new Error(`Graph does not include vertex ${v}`); } if (!p) { throw new Error(`Bad attribute`) } let newAttribute = (p instanceof Map) ? p : new Map(Object.entries(p)); let oldAttribute = this.vertexAttribute.get(v) || new Map(); this.vertexAttribute.set(v, new Map([...oldAttribute, ...newAttribute])); return this; } getAttribute(vertex) { if (this.adjacent.has(vertex)) { return this.vertexAttribute.get(vertex); } else { throw new Error(`Graph does not include vertex ${v}`); } } /** * example usage: * ``` * for (let v of g.vertices() ) { * // do something with v * } * ``` * @return vertices array of the graph * */ vertices() { return this.adjacent.keys(); } forEachVertices(action) { for(const v of this.vertices() ) { action(v); } } /** * @param v {integer} a vertex of graph. * @return neighbor array of v * */ adjacentOf(v) { if (!this.adjacent.has(v)) { throw new Error(`Graph does not include vertex ${v}`); } return this.adjacent.get(v); } forEachEdges(action) { const visitedEdges = new Map(); for(const start of this.vertices() ) { for(const end of this.adjacentOf(start)) { const first = (start > end) ? start : end; const second = (start > end) ? end : start; if(visitedEdges.has(first)) { const visitedAdjacent = visitedEdges.get(first); if( !visitedAdjacent.has(second) ) { visitedAdjacent.add(second); action(start, end); } }else { const adjacent = new Set(); visitedEdges.set(first, adjacent); adjacent.add(second); action(start, end); } } } } toString() { let text = ""; let sortedKeys = [...this.adjacent.keys()].sort(); let lastKey = sortedKeys.pop(); let adjacentFn = (key) => `[${this.adjacent.get(key).join(', ')}]`; for( let key of sortedKeys) { text += `${key} → ${adjacentFn(key)}\n` } text += `${lastKey} → ${adjacentFn(lastKey)}`; return text; } } function toInteger(value) { if (isNaN(value)) { return [false, NaN]; } let x = parseFloat(value); let i = x | 0; return [i === x, i]; }