1 | const staticProps = require('static-props')
|
2 |
|
3 | const getDegree = require('./getDegree')
|
4 | const getIncidentEdgeIds = require('./getIncidentEdgeIds')
|
5 | const getRank = require('./getRank')
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | class 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 |
|
55 |
|
56 |
|
57 |
|
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 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 | addNode (data) {
|
119 | const id = this.generateId()
|
120 |
|
121 | this.nodes[id] = data
|
122 |
|
123 | return id
|
124 | }
|
125 |
|
126 | |
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 | degreeOf (nodeId) {
|
134 | return getDegree(this.edges, nodeId)
|
135 | }
|
136 |
|
137 | |
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 | delEdge (id) {
|
145 | delete this.edges[id]
|
146 | }
|
147 |
|
148 | |
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | delNode (id) {
|
156 | delete this.nodes[id]
|
157 |
|
158 | const incidentEdgeIds = getIncidentEdgeIds(this.edges, id)
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | incidentEdgeIds.forEach((edgeId) => {
|
164 | if (this.edges[edgeId].length === 2) {
|
165 | this.delEdge(edgeId)
|
166 | }
|
167 | })
|
168 | }
|
169 |
|
170 | |
171 |
|
172 |
|
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 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | getRank () {
|
193 | if (this.uniform) return this.uniform
|
194 |
|
195 | return getRank(this.edges)
|
196 | }
|
197 | }
|
198 |
|
199 | module.exports = Graph
|