Hong-Phuc Bui
2025-01-16 2889de7f0c2d587a17fbd322af57c29e84238620
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
 * represents an undirected graph
*/
export class Graph {
    /**
     * creates a new  empty graph.
     * */
    constructor( ) {
        this.adjacent = new Map();
        this.vertexAttribute = new Map();
    }
 
    /**
     * (vertex:integer) => this
     * @param vertex {Number} a vertex
     * @return this graph
     * */
    addVertex(vertex) {
        if (! this.adjacent.has(vertex)) {
            let [isInteger, i] = toInteger(vertex);
            if (isInteger) {
                this.adjacent.set(i, []);
            } else {
                throw new TypeError(`Argument ${vertex} is not a valid vertex`)
            }
        }
        return this;
    }
 
    /**
     * (u: integer, v: integer) => undefined
     * @param u {Number} a vertex of edge
     * @param v {Number} a vertex of edge
     * */
    addEdge(u, v) {
        this.addVertex(u);
        this.addVertex(v);
        const vNeighbor = this.adjacent.get(v);
        if (!vNeighbor.includes(u) ) {
            vNeighbor.push(u);
        }
        const uNeighbor = this.adjacent.get(u);
        if(!uNeighbor.includes(v)) {
            uNeighbor.push(v);
        }
        return this;
    }
 
    /**
     * merge a map of attribute to a vertex. Usage example:
     * ```
     * let g = new Graph();
     * g.addVertex(1);
     * g.mergeAttribute(1, {'color': 0});
     * ```
     * @param v {Number} a vertex of graph
     * @param p {object|Map} attributes of the vertex, an empty object is allowed, but
     * undefined, empty array is not allow
     * */
    mergeAttribute(v, p) {
        if (!this.adjacent.has(v)) {
            throw new Error(`Graph does not include vertex ${v}`);
        }
        if (!p) {
            throw new Error(`Bad attribute`)
        }
        let newAttribute = (p instanceof Map) ? p : new Map(Object.entries(p));
        let oldAttribute = this.vertexAttribute.get(v) || new Map();
        this.vertexAttribute.set(v, new Map([...oldAttribute, ...newAttribute]));
        return this;
    }
 
    getAttribute(vertex) {
        if (this.adjacent.has(vertex)) {
            return this.vertexAttribute.get(vertex);
        } else {
            throw new Error(`Graph does not include vertex ${v}`);
        }
    }
 
    /**
     * example usage:
     * ```
     * for (let v of g.vertices() ) {
     *     // do something with v
     * }
     * ```
     * @return vertices array of the graph
     * */
    vertices() {
        return this.adjacent.keys();
    }
 
    forEachVertices(action) {
        for(const v of this.vertices() ) {
            action(v);
        }
    }
 
    /**
     * @param v {integer} a vertex of graph.
     * @return neighbor array of v
     * */
    adjacentOf(v) {
        if (!this.adjacent.has(v)) {
            throw new Error(`Graph does not include vertex ${v}`);
        }
        return this.adjacent.get(v);
    }
 
    forEachEdges(action) {
        const visitedEdges = new Map();
        for(const start of this.vertices() ) {
            for(const end of this.adjacentOf(start)) {
                const first = (start > end) ? start : end;
                const second = (start > end) ? end : start;
                if(visitedEdges.has(first)) {
                    const visitedAdjacent = visitedEdges.get(first);
                    if( !visitedAdjacent.has(second) ) {
                        visitedAdjacent.add(second);
                        action(start, end);
                    }
                }else {
                    const adjacent = new Set();
                    visitedEdges.set(first, adjacent);
                    adjacent.add(second);
                    action(start, end);
                }
            }
        }
    }
 
 
    toString() {
        let text = "";
        let sortedKeys = [...this.adjacent.keys()].sort();
        let lastKey = sortedKeys.pop();
        let adjacentFn = (key) => `[${this.adjacent.get(key).join(', ')}]`;
        for( let key of sortedKeys) {
            text += `${key} → ${adjacentFn(key)}\n`
        }
        text += `${lastKey} → ${adjacentFn(lastKey)}`;
        return text;
    }
}
 
function toInteger(value) {
    if (isNaN(value)) {
        return [false, NaN];
    }
    let x = parseFloat(value);
    let i = x | 0;
    return [i === x, i];
}