| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528 |
- const _ = require('./lodash')
- module.exports = Graph
- const DEFAULT_EDGE_NAME = '\x00'
- const GRAPH_NODE = '\x00'
- const EDGE_KEY_DELIM = '\x01'
- // Implementation notes:
- //
- // * Node id query functions should return string ids for the nodes
- // * Edge id query functions should return an "edgeObj", edge object, that is
- // composed of enough information to uniquely identify an edge: {v, w, name}.
- // * Internally we use an "edgeId", a stringified form of the edgeObj, to
- // reference edges. This is because we need a performant way to look these
- // edges up and, object properties, which have string keys, are the closest
- // we're going to get to a performant hashtable in JavaScript.
- function Graph (opts) {
- this._isDirected = _.has(opts, 'directed') ? opts.directed : true
- this._isMultigraph = _.has(opts, 'multigraph') ? opts.multigraph : false
- this._isCompound = _.has(opts, 'compound') ? opts.compound : false
- // Label for the graph itself
- this._label = undefined
- // Defaults to be set when creating a new node
- this._defaultNodeLabelFn = _.constant(undefined)
- // Defaults to be set when creating a new edge
- this._defaultEdgeLabelFn = _.constant(undefined)
- // v -> label
- this._nodes = {}
- if (this._isCompound) {
- // v -> parent
- this._parent = {}
- // v -> children
- this._children = {}
- this._children[GRAPH_NODE] = {}
- }
- // v -> edgeObj
- this._in = {}
- // u -> v -> Number
- this._preds = {}
- // v -> edgeObj
- this._out = {}
- // v -> w -> Number
- this._sucs = {}
- // e -> edgeObj
- this._edgeObjs = {}
- // e -> label
- this._edgeLabels = {}
- }
- /* Number of nodes in the graph. Should only be changed by the implementation. */
- Graph.prototype._nodeCount = 0
- /* Number of edges in the graph. Should only be changed by the implementation. */
- Graph.prototype._edgeCount = 0
- /* === Graph functions ========= */
- Graph.prototype.isDirected = function () {
- return this._isDirected
- }
- Graph.prototype.isMultigraph = function () {
- return this._isMultigraph
- }
- Graph.prototype.isCompound = function () {
- return this._isCompound
- }
- Graph.prototype.setGraph = function (label) {
- this._label = label
- return this
- }
- Graph.prototype.graph = function () {
- return this._label
- }
- /* === Node functions ========== */
- Graph.prototype.setDefaultNodeLabel = function (newDefault) {
- if (!_.isFunction(newDefault)) {
- newDefault = _.constant(newDefault)
- }
- this._defaultNodeLabelFn = newDefault
- return this
- }
- Graph.prototype.nodeCount = function () {
- return this._nodeCount
- }
- Graph.prototype.nodes = function () {
- return _.keys(this._nodes)
- }
- Graph.prototype.sources = function () {
- var self = this
- return _.filter(this.nodes(), function (v) {
- return _.isEmpty(self._in[v])
- })
- }
- Graph.prototype.sinks = function () {
- var self = this
- return _.filter(this.nodes(), function (v) {
- return _.isEmpty(self._out[v])
- })
- }
- Graph.prototype.setNodes = function (vs, value) {
- var args = arguments
- var self = this
- _.each(vs, function (v) {
- if (args.length > 1) {
- self.setNode(v, value)
- } else {
- self.setNode(v)
- }
- })
- return this
- }
- Graph.prototype.setNode = function (v, value) {
- if (_.has(this._nodes, v)) {
- if (arguments.length > 1) {
- this._nodes[v] = value
- }
- return this
- }
- this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v)
- if (this._isCompound) {
- this._parent[v] = GRAPH_NODE
- this._children[v] = {}
- this._children[GRAPH_NODE][v] = true
- }
- this._in[v] = {}
- this._preds[v] = {}
- this._out[v] = {}
- this._sucs[v] = {}
- ++this._nodeCount
- return this
- }
- Graph.prototype.node = function (v) {
- return this._nodes[v]
- }
- Graph.prototype.hasNode = function (v) {
- return _.has(this._nodes, v)
- }
- Graph.prototype.removeNode = function (v) {
- var self = this
- if (_.has(this._nodes, v)) {
- var removeEdge = function (e) { self.removeEdge(self._edgeObjs[e]) }
- delete this._nodes[v]
- if (this._isCompound) {
- this._removeFromParentsChildList(v)
- delete this._parent[v]
- _.each(this.children(v), function (child) {
- self.setParent(child)
- })
- delete this._children[v]
- }
- _.each(_.keys(this._in[v]), removeEdge)
- delete this._in[v]
- delete this._preds[v]
- _.each(_.keys(this._out[v]), removeEdge)
- delete this._out[v]
- delete this._sucs[v]
- --this._nodeCount
- }
- return this
- }
- Graph.prototype.setParent = function (v, parent) {
- if (!this._isCompound) {
- throw new Error('Cannot set parent in a non-compound graph')
- }
- if (_.isUndefined(parent)) {
- parent = GRAPH_NODE
- } else {
- // Coerce parent to string
- parent += ''
- for (var ancestor = parent;
- !_.isUndefined(ancestor);
- ancestor = this.parent(ancestor)) {
- if (ancestor === v) {
- throw new Error('Setting ' + parent + ' as parent of ' + v +
- ' would create a cycle')
- }
- }
- this.setNode(parent)
- }
- this.setNode(v)
- this._removeFromParentsChildList(v)
- this._parent[v] = parent
- this._children[parent][v] = true
- return this
- }
- Graph.prototype._removeFromParentsChildList = function (v) {
- delete this._children[this._parent[v]][v]
- }
- Graph.prototype.parent = function (v) {
- if (this._isCompound) {
- var parent = this._parent[v]
- if (parent !== GRAPH_NODE) {
- return parent
- }
- }
- }
- Graph.prototype.children = function (v) {
- if (_.isUndefined(v)) {
- v = GRAPH_NODE
- }
- if (this._isCompound) {
- var children = this._children[v]
- if (children) {
- return _.keys(children)
- }
- } else if (v === GRAPH_NODE) {
- return this.nodes()
- } else if (this.hasNode(v)) {
- return []
- }
- }
- Graph.prototype.predecessors = function (v) {
- var predsV = this._preds[v]
- if (predsV) {
- return _.keys(predsV)
- }
- }
- Graph.prototype.successors = function (v) {
- var sucsV = this._sucs[v]
- if (sucsV) {
- return _.keys(sucsV)
- }
- }
- Graph.prototype.neighbors = function (v) {
- var preds = this.predecessors(v)
- if (preds) {
- return _.union(preds, this.successors(v))
- }
- }
- Graph.prototype.isLeaf = function (v) {
- var neighbors
- if (this.isDirected()) {
- neighbors = this.successors(v)
- } else {
- neighbors = this.neighbors(v)
- }
- return neighbors.length === 0
- }
- Graph.prototype.filterNodes = function (filter) {
- var copy = new this.constructor({
- directed: this._isDirected,
- multigraph: this._isMultigraph,
- compound: this._isCompound
- })
- copy.setGraph(this.graph())
- var self = this
- _.each(this._nodes, function (value, v) {
- if (filter(v)) {
- copy.setNode(v, value)
- }
- })
- _.each(this._edgeObjs, function (e) {
- if (copy.hasNode(e.v) && copy.hasNode(e.w)) {
- copy.setEdge(e, self.edge(e))
- }
- })
- var parents = {}
- function findParent (v) {
- var parent = self.parent(v)
- if (parent === undefined || copy.hasNode(parent)) {
- parents[v] = parent
- return parent
- } else if (parent in parents) {
- return parents[parent]
- } else {
- return findParent(parent)
- }
- }
- if (this._isCompound) {
- _.each(copy.nodes(), function (v) {
- copy.setParent(v, findParent(v))
- })
- }
- return copy
- }
- /* === Edge functions ========== */
- Graph.prototype.setDefaultEdgeLabel = function (newDefault) {
- if (!_.isFunction(newDefault)) {
- newDefault = _.constant(newDefault)
- }
- this._defaultEdgeLabelFn = newDefault
- return this
- }
- Graph.prototype.edgeCount = function () {
- return this._edgeCount
- }
- Graph.prototype.edges = function () {
- return _.values(this._edgeObjs)
- }
- Graph.prototype.setPath = function (vs, value) {
- const self = this
- const args = arguments
- _.reduce(vs, function (v, w) {
- if (args.length > 1) {
- self.setEdge(v, w, value)
- } else {
- self.setEdge(v, w)
- }
- return w
- })
- return this
- }
- /*
- * setEdge(v, w, [value, [name]])
- * setEdge({ v, w, [name] }, [value])
- */
- Graph.prototype.setEdge = function () {
- let v, w, name, value
- let valueSpecified = false
- const arg0 = arguments[0]
- if (typeof arg0 === 'object' && arg0 !== null && 'v' in arg0) {
- v = arg0.v
- w = arg0.w
- name = arg0.name
- if (arguments.length === 2) {
- value = arguments[1]
- valueSpecified = true
- }
- } else {
- v = arg0
- w = arguments[1]
- name = arguments[3]
- if (arguments.length > 2) {
- value = arguments[2]
- valueSpecified = true
- }
- }
- v = '' + v
- w = '' + w
- if (!_.isUndefined(name)) {
- name = '' + name
- }
- var e = edgeArgsToId(this._isDirected, v, w, name)
- if (_.has(this._edgeLabels, e)) {
- if (valueSpecified) {
- this._edgeLabels[e] = value
- }
- return this
- }
- if (!_.isUndefined(name) && !this._isMultigraph) {
- throw new Error('Cannot set a named edge when isMultigraph = false')
- }
- // It didn't exist, so we need to create it.
- // First ensure the nodes exist.
- this.setNode(v)
- this.setNode(w)
- this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name)
- var edgeObj = edgeArgsToObj(this._isDirected, v, w, name)
- // Ensure we add undirected edges in a consistent way.
- v = edgeObj.v
- w = edgeObj.w
- Object.freeze(edgeObj)
- this._edgeObjs[e] = edgeObj
- incrementOrInitEntry(this._preds[w], v)
- incrementOrInitEntry(this._sucs[v], w)
- this._in[w][e] = edgeObj
- this._out[v][e] = edgeObj
- this._edgeCount++
- return this
- }
- Graph.prototype.edge = function (v, w, name) {
- var e = (arguments.length === 1
- ? edgeObjToId(this._isDirected, arguments[0])
- : edgeArgsToId(this._isDirected, v, w, name))
- return this._edgeLabels[e]
- }
- Graph.prototype.hasEdge = function (v, w, name) {
- var e = (arguments.length === 1
- ? edgeObjToId(this._isDirected, arguments[0])
- : edgeArgsToId(this._isDirected, v, w, name))
- return _.has(this._edgeLabels, e)
- }
- Graph.prototype.removeEdge = function (v, w, name) {
- const e = (arguments.length === 1
- ? edgeObjToId(this._isDirected, arguments[0])
- : edgeArgsToId(this._isDirected, v, w, name))
- const edge = this._edgeObjs[e]
- if (edge) {
- v = edge.v
- w = edge.w
- delete this._edgeLabels[e]
- delete this._edgeObjs[e]
- decrementOrRemoveEntry(this._preds[w], v)
- decrementOrRemoveEntry(this._sucs[v], w)
- delete this._in[w][e]
- delete this._out[v][e]
- this._edgeCount--
- }
- return this
- }
- Graph.prototype.inEdges = function (v, u) {
- var inV = this._in[v]
- if (inV) {
- var edges = _.values(inV)
- if (!u) {
- return edges
- }
- return _.filter(edges, function (edge) { return edge.v === u })
- }
- }
- Graph.prototype.outEdges = function (v, w) {
- var outV = this._out[v]
- if (outV) {
- var edges = _.values(outV)
- if (!w) {
- return edges
- }
- return _.filter(edges, function (edge) { return edge.w === w })
- }
- }
- Graph.prototype.nodeEdges = function (v, w) {
- var inEdges = this.inEdges(v, w)
- if (inEdges) {
- return inEdges.concat(this.outEdges(v, w))
- }
- }
- function incrementOrInitEntry (map, k) {
- if (map[k]) {
- map[k]++
- } else {
- map[k] = 1
- }
- }
- function decrementOrRemoveEntry (map, k) {
- if (!--map[k]) { delete map[k] }
- }
- function edgeArgsToId (isDirected, v_, w_, name) {
- var v = '' + v_
- var w = '' + w_
- if (!isDirected && v > w) {
- var tmp = v
- v = w
- w = tmp
- }
- return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM +
- (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name)
- }
- function edgeArgsToObj (isDirected, v_, w_, name) {
- var v = '' + v_
- var w = '' + w_
- if (!isDirected && v > w) {
- var tmp = v
- v = w
- w = tmp
- }
- var edgeObj = { v: v, w: w }
- if (name) {
- edgeObj.name = name
- }
- return edgeObj
- }
- function edgeObjToId (isDirected, edgeObj) {
- return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name)
- }
|