UNPKG

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