UNPKG

4.06 kBJavaScriptView Raw
1const isequal = require('lodash.isequal')
2const isInteger = require('is-integer')
3const uniqueid = require('lodash.uniqueid')
4const uniqby = require('lodash.uniqby')
5const staticProps = require('static-props')
6
7const getDegree = require('./getDegree')
8const getIncidentEdgeIds = require('./getIncidentEdgeIds')
9const getRank = require('./getRank')
10
11/**
12 * Hypergraph
13 *
14 * http://en.wikipedia.org/wiki/Hypergraph
15 *
16 * @param {Object} [graph]
17 * @param {Object} [graph.edges]
18 * @param {Object} [graph.nodes]
19 * @param {Boolean} [graph.multigraph] can contain duplicated edges
20 * @param {Boolean} [graph.pseudograph] is a multigraph with loops allowed
21 * @param {Number} [graph.uniform] all edges has the same cardinality (i.e. number of nodes)
22 *
23 * @class
24 */
25
26// TODO add options like
27// * rank, maxDegree, etc
28// if it is directed, an a -> b edge is different from b -> a
29class Graph {
30 constructor () {
31 const arg = arguments[0] || {}
32 var obj = {}
33
34 if (isInteger(arg.uniform)) {
35 if (arg.uniform < 2) {
36 throw new TypeError('Argument uniform cannot be less than 2')
37 } else {
38 obj.uniform = arg.uniform
39 }
40 }
41
42 if (arg.multigraph === true) obj.multigraph = true
43
44 if (arg.pseudograph === true) {
45 obj.multigraph = true
46 obj.pseudograph = true
47 }
48
49 obj.edges = arg.edges || {}
50 obj.nodes = arg.nodes || {}
51
52 const enumerable = true
53 staticProps(this)(obj, enumerable)
54 }
55
56 /**
57 * Add an hyperedge that connects given nodeIds.
58 *
59 * @param {Array} nodeIds
60 * @returns {String} id
61 */
62
63 addEdge (nodeIds) {
64 if (nodeIds.length < 2) {
65 throw new Error('An edge must point at two or more nodes')
66 }
67
68 const uniform = this.uniform
69
70 if (uniform) {
71 const cardinality = nodeIds.length
72
73 if (uniform !== cardinality) {
74 throw new Error(`Cannot add an edge with cardinality ${cardinality} to a ${uniform}-uniform graph`)
75 }
76 }
77
78 if (!this.pseudograph) {
79 if (uniqby(nodeIds).length < nodeIds.length) {
80 throw new Error('This is not a pseudograph, it is not allowed to create loops')
81 }
82 }
83
84 if (!this.multigraph) {
85 for (let edgeId in this.edges) {
86 let edge = this.edges[edgeId]
87
88 if (isequal(nodeIds, edge)) {
89 throw new Error('This is not a multigraph, you cannot add duplicated edges')
90 }
91 }
92 }
93
94 const nodeIdsNotFound = nodeIds.filter((id) => {
95 return !this.nodes.hasOwnProperty(id)
96 })
97
98 if (nodeIdsNotFound.length > 0) {
99 throw new Error('Edge points to some nodeId not found in this graph; ' + nodeIdsNotFound.join(','))
100 }
101
102 const id = uniqueid()
103
104 this.edges[id] = nodeIds
105
106 return id
107 }
108
109 /**
110 * Add a node, containing given data.
111 *
112 * @param {*} data
113 * @returns {String} id of the node created
114 */
115
116 addNode (data) {
117 const id = uniqueid()
118
119 this.nodes[id] = data
120
121 return id
122 }
123
124 /**
125 * Returns the degree of a node, that is the number of incident edges with loops counted twice.
126 *
127 * @param {String} nodeId
128 * @returns {Number} degree
129 */
130
131 degreeOf (nodeId) {
132 return getDegree(this.edges, nodeId)
133 }
134
135 /**
136 * Delete edge by given id.
137 *
138 * @param {String} id
139 * @returns {void}
140 */
141
142 delEdge (id) {
143 delete this.edges[id]
144 }
145
146 /**
147 * Delete node by given id.
148 *
149 * @param {String} id
150 * @returns {void}
151 */
152
153 delNode (id) {
154 delete this.nodes[id]
155
156 const incidentEdgeIds = getIncidentEdgeIds(this.edges, id)
157
158 // TODO in an hypergraph it should not remove the edge, but
159 // remove the nodeIds from edges. and remove the edge if it is empty.
160 // Document in the README and the jsdoc above that it removes also edges
161 for (var edgeId in incidentEdgeIds) {
162 this.delEdge(edgeId)
163 }
164 }
165
166 /**
167 * Returns the max cardinality of any of the edges in the hypergraph.
168 *
169 * @returns {Number} rank
170 */
171
172 getRank () {
173 if (this.uniform) return this.uniform
174
175 return getRank(this.edges)
176 }
177}
178
179module.exports = Graph