graph.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. const _ = require('./lodash')
  2. module.exports = Graph
  3. const DEFAULT_EDGE_NAME = '\x00'
  4. const GRAPH_NODE = '\x00'
  5. const EDGE_KEY_DELIM = '\x01'
  6. // Implementation notes:
  7. //
  8. // * Node id query functions should return string ids for the nodes
  9. // * Edge id query functions should return an "edgeObj", edge object, that is
  10. // composed of enough information to uniquely identify an edge: {v, w, name}.
  11. // * Internally we use an "edgeId", a stringified form of the edgeObj, to
  12. // reference edges. This is because we need a performant way to look these
  13. // edges up and, object properties, which have string keys, are the closest
  14. // we're going to get to a performant hashtable in JavaScript.
  15. function Graph (opts) {
  16. this._isDirected = _.has(opts, 'directed') ? opts.directed : true
  17. this._isMultigraph = _.has(opts, 'multigraph') ? opts.multigraph : false
  18. this._isCompound = _.has(opts, 'compound') ? opts.compound : false
  19. // Label for the graph itself
  20. this._label = undefined
  21. // Defaults to be set when creating a new node
  22. this._defaultNodeLabelFn = _.constant(undefined)
  23. // Defaults to be set when creating a new edge
  24. this._defaultEdgeLabelFn = _.constant(undefined)
  25. // v -> label
  26. this._nodes = {}
  27. if (this._isCompound) {
  28. // v -> parent
  29. this._parent = {}
  30. // v -> children
  31. this._children = {}
  32. this._children[GRAPH_NODE] = {}
  33. }
  34. // v -> edgeObj
  35. this._in = {}
  36. // u -> v -> Number
  37. this._preds = {}
  38. // v -> edgeObj
  39. this._out = {}
  40. // v -> w -> Number
  41. this._sucs = {}
  42. // e -> edgeObj
  43. this._edgeObjs = {}
  44. // e -> label
  45. this._edgeLabels = {}
  46. }
  47. /* Number of nodes in the graph. Should only be changed by the implementation. */
  48. Graph.prototype._nodeCount = 0
  49. /* Number of edges in the graph. Should only be changed by the implementation. */
  50. Graph.prototype._edgeCount = 0
  51. /* === Graph functions ========= */
  52. Graph.prototype.isDirected = function () {
  53. return this._isDirected
  54. }
  55. Graph.prototype.isMultigraph = function () {
  56. return this._isMultigraph
  57. }
  58. Graph.prototype.isCompound = function () {
  59. return this._isCompound
  60. }
  61. Graph.prototype.setGraph = function (label) {
  62. this._label = label
  63. return this
  64. }
  65. Graph.prototype.graph = function () {
  66. return this._label
  67. }
  68. /* === Node functions ========== */
  69. Graph.prototype.setDefaultNodeLabel = function (newDefault) {
  70. if (!_.isFunction(newDefault)) {
  71. newDefault = _.constant(newDefault)
  72. }
  73. this._defaultNodeLabelFn = newDefault
  74. return this
  75. }
  76. Graph.prototype.nodeCount = function () {
  77. return this._nodeCount
  78. }
  79. Graph.prototype.nodes = function () {
  80. return _.keys(this._nodes)
  81. }
  82. Graph.prototype.sources = function () {
  83. var self = this
  84. return _.filter(this.nodes(), function (v) {
  85. return _.isEmpty(self._in[v])
  86. })
  87. }
  88. Graph.prototype.sinks = function () {
  89. var self = this
  90. return _.filter(this.nodes(), function (v) {
  91. return _.isEmpty(self._out[v])
  92. })
  93. }
  94. Graph.prototype.setNodes = function (vs, value) {
  95. var args = arguments
  96. var self = this
  97. _.each(vs, function (v) {
  98. if (args.length > 1) {
  99. self.setNode(v, value)
  100. } else {
  101. self.setNode(v)
  102. }
  103. })
  104. return this
  105. }
  106. Graph.prototype.setNode = function (v, value) {
  107. if (_.has(this._nodes, v)) {
  108. if (arguments.length > 1) {
  109. this._nodes[v] = value
  110. }
  111. return this
  112. }
  113. this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v)
  114. if (this._isCompound) {
  115. this._parent[v] = GRAPH_NODE
  116. this._children[v] = {}
  117. this._children[GRAPH_NODE][v] = true
  118. }
  119. this._in[v] = {}
  120. this._preds[v] = {}
  121. this._out[v] = {}
  122. this._sucs[v] = {}
  123. ++this._nodeCount
  124. return this
  125. }
  126. Graph.prototype.node = function (v) {
  127. return this._nodes[v]
  128. }
  129. Graph.prototype.hasNode = function (v) {
  130. return _.has(this._nodes, v)
  131. }
  132. Graph.prototype.removeNode = function (v) {
  133. var self = this
  134. if (_.has(this._nodes, v)) {
  135. var removeEdge = function (e) { self.removeEdge(self._edgeObjs[e]) }
  136. delete this._nodes[v]
  137. if (this._isCompound) {
  138. this._removeFromParentsChildList(v)
  139. delete this._parent[v]
  140. _.each(this.children(v), function (child) {
  141. self.setParent(child)
  142. })
  143. delete this._children[v]
  144. }
  145. _.each(_.keys(this._in[v]), removeEdge)
  146. delete this._in[v]
  147. delete this._preds[v]
  148. _.each(_.keys(this._out[v]), removeEdge)
  149. delete this._out[v]
  150. delete this._sucs[v]
  151. --this._nodeCount
  152. }
  153. return this
  154. }
  155. Graph.prototype.setParent = function (v, parent) {
  156. if (!this._isCompound) {
  157. throw new Error('Cannot set parent in a non-compound graph')
  158. }
  159. if (_.isUndefined(parent)) {
  160. parent = GRAPH_NODE
  161. } else {
  162. // Coerce parent to string
  163. parent += ''
  164. for (var ancestor = parent;
  165. !_.isUndefined(ancestor);
  166. ancestor = this.parent(ancestor)) {
  167. if (ancestor === v) {
  168. throw new Error('Setting ' + parent + ' as parent of ' + v +
  169. ' would create a cycle')
  170. }
  171. }
  172. this.setNode(parent)
  173. }
  174. this.setNode(v)
  175. this._removeFromParentsChildList(v)
  176. this._parent[v] = parent
  177. this._children[parent][v] = true
  178. return this
  179. }
  180. Graph.prototype._removeFromParentsChildList = function (v) {
  181. delete this._children[this._parent[v]][v]
  182. }
  183. Graph.prototype.parent = function (v) {
  184. if (this._isCompound) {
  185. var parent = this._parent[v]
  186. if (parent !== GRAPH_NODE) {
  187. return parent
  188. }
  189. }
  190. }
  191. Graph.prototype.children = function (v) {
  192. if (_.isUndefined(v)) {
  193. v = GRAPH_NODE
  194. }
  195. if (this._isCompound) {
  196. var children = this._children[v]
  197. if (children) {
  198. return _.keys(children)
  199. }
  200. } else if (v === GRAPH_NODE) {
  201. return this.nodes()
  202. } else if (this.hasNode(v)) {
  203. return []
  204. }
  205. }
  206. Graph.prototype.predecessors = function (v) {
  207. var predsV = this._preds[v]
  208. if (predsV) {
  209. return _.keys(predsV)
  210. }
  211. }
  212. Graph.prototype.successors = function (v) {
  213. var sucsV = this._sucs[v]
  214. if (sucsV) {
  215. return _.keys(sucsV)
  216. }
  217. }
  218. Graph.prototype.neighbors = function (v) {
  219. var preds = this.predecessors(v)
  220. if (preds) {
  221. return _.union(preds, this.successors(v))
  222. }
  223. }
  224. Graph.prototype.isLeaf = function (v) {
  225. var neighbors
  226. if (this.isDirected()) {
  227. neighbors = this.successors(v)
  228. } else {
  229. neighbors = this.neighbors(v)
  230. }
  231. return neighbors.length === 0
  232. }
  233. Graph.prototype.filterNodes = function (filter) {
  234. var copy = new this.constructor({
  235. directed: this._isDirected,
  236. multigraph: this._isMultigraph,
  237. compound: this._isCompound
  238. })
  239. copy.setGraph(this.graph())
  240. var self = this
  241. _.each(this._nodes, function (value, v) {
  242. if (filter(v)) {
  243. copy.setNode(v, value)
  244. }
  245. })
  246. _.each(this._edgeObjs, function (e) {
  247. if (copy.hasNode(e.v) && copy.hasNode(e.w)) {
  248. copy.setEdge(e, self.edge(e))
  249. }
  250. })
  251. var parents = {}
  252. function findParent (v) {
  253. var parent = self.parent(v)
  254. if (parent === undefined || copy.hasNode(parent)) {
  255. parents[v] = parent
  256. return parent
  257. } else if (parent in parents) {
  258. return parents[parent]
  259. } else {
  260. return findParent(parent)
  261. }
  262. }
  263. if (this._isCompound) {
  264. _.each(copy.nodes(), function (v) {
  265. copy.setParent(v, findParent(v))
  266. })
  267. }
  268. return copy
  269. }
  270. /* === Edge functions ========== */
  271. Graph.prototype.setDefaultEdgeLabel = function (newDefault) {
  272. if (!_.isFunction(newDefault)) {
  273. newDefault = _.constant(newDefault)
  274. }
  275. this._defaultEdgeLabelFn = newDefault
  276. return this
  277. }
  278. Graph.prototype.edgeCount = function () {
  279. return this._edgeCount
  280. }
  281. Graph.prototype.edges = function () {
  282. return _.values(this._edgeObjs)
  283. }
  284. Graph.prototype.setPath = function (vs, value) {
  285. const self = this
  286. const args = arguments
  287. _.reduce(vs, function (v, w) {
  288. if (args.length > 1) {
  289. self.setEdge(v, w, value)
  290. } else {
  291. self.setEdge(v, w)
  292. }
  293. return w
  294. })
  295. return this
  296. }
  297. /*
  298. * setEdge(v, w, [value, [name]])
  299. * setEdge({ v, w, [name] }, [value])
  300. */
  301. Graph.prototype.setEdge = function () {
  302. let v, w, name, value
  303. let valueSpecified = false
  304. const arg0 = arguments[0]
  305. if (typeof arg0 === 'object' && arg0 !== null && 'v' in arg0) {
  306. v = arg0.v
  307. w = arg0.w
  308. name = arg0.name
  309. if (arguments.length === 2) {
  310. value = arguments[1]
  311. valueSpecified = true
  312. }
  313. } else {
  314. v = arg0
  315. w = arguments[1]
  316. name = arguments[3]
  317. if (arguments.length > 2) {
  318. value = arguments[2]
  319. valueSpecified = true
  320. }
  321. }
  322. v = '' + v
  323. w = '' + w
  324. if (!_.isUndefined(name)) {
  325. name = '' + name
  326. }
  327. var e = edgeArgsToId(this._isDirected, v, w, name)
  328. if (_.has(this._edgeLabels, e)) {
  329. if (valueSpecified) {
  330. this._edgeLabels[e] = value
  331. }
  332. return this
  333. }
  334. if (!_.isUndefined(name) && !this._isMultigraph) {
  335. throw new Error('Cannot set a named edge when isMultigraph = false')
  336. }
  337. // It didn't exist, so we need to create it.
  338. // First ensure the nodes exist.
  339. this.setNode(v)
  340. this.setNode(w)
  341. this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name)
  342. var edgeObj = edgeArgsToObj(this._isDirected, v, w, name)
  343. // Ensure we add undirected edges in a consistent way.
  344. v = edgeObj.v
  345. w = edgeObj.w
  346. Object.freeze(edgeObj)
  347. this._edgeObjs[e] = edgeObj
  348. incrementOrInitEntry(this._preds[w], v)
  349. incrementOrInitEntry(this._sucs[v], w)
  350. this._in[w][e] = edgeObj
  351. this._out[v][e] = edgeObj
  352. this._edgeCount++
  353. return this
  354. }
  355. Graph.prototype.edge = function (v, w, name) {
  356. var e = (arguments.length === 1
  357. ? edgeObjToId(this._isDirected, arguments[0])
  358. : edgeArgsToId(this._isDirected, v, w, name))
  359. return this._edgeLabels[e]
  360. }
  361. Graph.prototype.hasEdge = function (v, w, name) {
  362. var e = (arguments.length === 1
  363. ? edgeObjToId(this._isDirected, arguments[0])
  364. : edgeArgsToId(this._isDirected, v, w, name))
  365. return _.has(this._edgeLabels, e)
  366. }
  367. Graph.prototype.removeEdge = function (v, w, name) {
  368. const e = (arguments.length === 1
  369. ? edgeObjToId(this._isDirected, arguments[0])
  370. : edgeArgsToId(this._isDirected, v, w, name))
  371. const edge = this._edgeObjs[e]
  372. if (edge) {
  373. v = edge.v
  374. w = edge.w
  375. delete this._edgeLabels[e]
  376. delete this._edgeObjs[e]
  377. decrementOrRemoveEntry(this._preds[w], v)
  378. decrementOrRemoveEntry(this._sucs[v], w)
  379. delete this._in[w][e]
  380. delete this._out[v][e]
  381. this._edgeCount--
  382. }
  383. return this
  384. }
  385. Graph.prototype.inEdges = function (v, u) {
  386. var inV = this._in[v]
  387. if (inV) {
  388. var edges = _.values(inV)
  389. if (!u) {
  390. return edges
  391. }
  392. return _.filter(edges, function (edge) { return edge.v === u })
  393. }
  394. }
  395. Graph.prototype.outEdges = function (v, w) {
  396. var outV = this._out[v]
  397. if (outV) {
  398. var edges = _.values(outV)
  399. if (!w) {
  400. return edges
  401. }
  402. return _.filter(edges, function (edge) { return edge.w === w })
  403. }
  404. }
  405. Graph.prototype.nodeEdges = function (v, w) {
  406. var inEdges = this.inEdges(v, w)
  407. if (inEdges) {
  408. return inEdges.concat(this.outEdges(v, w))
  409. }
  410. }
  411. function incrementOrInitEntry (map, k) {
  412. if (map[k]) {
  413. map[k]++
  414. } else {
  415. map[k] = 1
  416. }
  417. }
  418. function decrementOrRemoveEntry (map, k) {
  419. if (!--map[k]) { delete map[k] }
  420. }
  421. function edgeArgsToId (isDirected, v_, w_, name) {
  422. var v = '' + v_
  423. var w = '' + w_
  424. if (!isDirected && v > w) {
  425. var tmp = v
  426. v = w
  427. w = tmp
  428. }
  429. return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM +
  430. (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name)
  431. }
  432. function edgeArgsToObj (isDirected, v_, w_, name) {
  433. var v = '' + v_
  434. var w = '' + w_
  435. if (!isDirected && v > w) {
  436. var tmp = v
  437. v = w
  438. w = tmp
  439. }
  440. var edgeObj = { v: v, w: w }
  441. if (name) {
  442. edgeObj.name = name
  443. }
  444. return edgeObj
  445. }
  446. function edgeObjToId (isDirected, edgeObj) {
  447. return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name)
  448. }