"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { TrafficGraph: () => TrafficGraph, TrafficTraversal: () => TrafficTraversal }); module.exports = __toCommonJS(src_exports); // src/Utils/Array.ts function ensure(array, ...items) { for (const t of items) { if (array.includes(t)) { continue; } array.push(t); } } function has(array, ...items) { for (const t of items) { if (!array.includes(t)) { return false; } } return true; } function first(array) { return array.at(0); } function last(array) { return array.at(-1); } function copy(array) { return [...array]; } // src/Utils/Object.ts function ObjectMap() { } ObjectMap.prototype = /* @__PURE__ */ Object.create(null); function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); } function setObjectMap(t) { t.__proto__ = /* @__PURE__ */ Object.create(null); return t; } // src/TrafficGraph.ts var TrafficGraph = class _TrafficGraph { /** * It is an object that contains weight information between the vertex of the graph. */ _data; /** * Create a new graph instance. You can generate from existing data using `data` parameters. * @param data You can restore it with existing data.This data can be obtained by `TrafficGraph.data`. */ constructor(data = setObjectMap({ vertex: {}, embedded: [] })) { this._data = data; } static Create(...args) { return new _TrafficGraph(...args); } /** * Returns to an array in the form that can serialize the graph information of the current instance. */ get data() { const clone = deepCopy(this._data); for (const k in clone.vertex) { const gv = clone.vertex[k]; setObjectMap(gv); } return setObjectMap(clone); } /** * The current status of the instance is exported to an immutable object. */ get state() { const data = Object.freeze(this.data); const vertices = Object.freeze(this.vertices); const timestamp = Date.now(); const state = Object.freeze({ data, vertices, timestamp }); return state; } /** * Returns all the vertices listed in the current instance in an array. */ get vertices() { const inQueue = setObjectMap({}); const vertices = []; for (const k in this._data.vertex) { if (!(k in inQueue)) { inQueue[k] = true; vertices.push(k); } const gv = this._data.vertex[k]; for (const v in gv) { if (!(v in inQueue)) { inQueue[v] = true; vertices.push(v); } } } return vertices; } /** * Currently copied instance and returns to a new instance. */ get clone() { return new _TrafficGraph(this._data); } _graphVertex(vertex) { return vertex in this._data.vertex ? this._data.vertex[vertex] : setObjectMap({}); } _embed(vertex) { ensure(this._data.embedded, vertex); } _calculate(vertices, vertex, calc) { const before = vertex in vertices ? vertices[vertex] : 0; const symbol = calc.substring(0, 2); const value = Number(calc.substring(2)); switch (symbol) { case "+=": { return before + value; } case "-=": { return before - value; } case "*=": { return before * value; } case "/=": { return before / value; } default: { throw new Error(`The string formula that can be used is as follows: +=, -=, *=, /=`); } } } /** * Create a single direction weight route. It is possible to traverse the `source` to `dest`, but vice versa is impossible. * If you had the same vertex before, the value is overwritten. * @param source The starting vertex. * @param dest This is a list of weights of each vertex. You can specify relative values. If you fill in the prior character `+=`, `-=`, `*=`, `/=`, The target value is calculated based on the current value of the property. * @example * graph.to('a', { b: 1 }) * graph.to('a', { b: '+=1' }) */ to(source, dest) { if (!(source in this._data.vertex)) { this._data.vertex[source] = setObjectMap({}); this._embed(source); } const gv = this._graphVertex(source); for (const v in dest) { if (source === v) { continue; } let w = dest[v]; if (typeof w === "string") { w = this._calculate(gv, v, w); } gv[v] = w; this._embed(v); } return this; } /** * Set the weight route that leads to both directions between the two vertices. 'a' vertex and 'b' vertex can traverse to each other. * For example, `graph.both('a', { b: 1 })` is same as `graph.to('a', { b: 1 }).to('b', { a: 1 })` * @param a The vertex a. * @param b This is a list of weights of each vertex. You can specify relative values. If you fill in the prior character `+=`, `-=`, `*=`, `/=`, The target value is calculated based on the current value of the property. * @example * graph.both('a', { b: 1 }) * graph.both('a', { b: '+=1' }) */ both(a, b) { this.to(a, b); for (const v in b) { const gv = setObjectMap({}); const w = b[v]; gv[a] = w; this.to(v, gv); } return this; } /** * Set the weight between all vertices passed by parameters. * For example, `graph.all({ a: 1, b: 2, c: 3 })` is same as `graph.to('a', { b: 2, c: 3 }).to('b', { a: 1, c: 3 }).to('c', { a: 1, b: 2 })` * @param dest This is a list of weights of each vertex. You can specify relative values. If you fill in the prior character `+=`, `-=`, `*=`, `/=`, The target value is calculated based on the current value of the property. * @example * graph.all({ a: 1, b: 2, c: 3 }) * graph.all({ a: '+=1', b: '+=1', c: '+=1' }) */ all(dest) { for (const v in dest) { this.to(v, dest); } return this; } /** * Delete the single direction weight route created by the `to` method. * @param source The starting vertex. * @param dest The target vertex. */ unlinkTo(source, dest) { const gv = this._graphVertex(source); delete gv[dest]; return this; } /** * Delete the bidirectional weight route created by the `both` method. * @param a The vertex a. * @param b The vertex b. */ unlinkBoth(a, b) { this.unlinkTo(a, b); this.unlinkTo(b, a); return this; } /** * Delete certain vertices. All weight routes connected to the vertex are deleted. * @param vertex The vertex what you want to delete. */ drop(vertex) { for (const v in this._data.vertex) { const gv = this._data.vertex[v]; delete gv[vertex]; } delete this._data.vertex[vertex]; return this; } /** * It returns whether the instance has a vertex. * @param vertex The vertex what you want to check. */ has(vertex) { return this.vertices.includes(vertex); } /** * It returns whether all the vertices exist in that instance. Returns `false` if any of the vertices are missing. * @param vertices The vertices what you want to check. */ hasAll(...vertices) { return has(this.vertices, ...vertices); } /** * Invert all weights in an instance. For example, when A to B has a `2` weight, it will be `-2`. * It's useful for switching the shortest to longest routes or minimum to maximum traffic in a graph. * @example * const inverted = TrafficTraversal.Create(traffic.invert().state) * const longest = invertedTraversal.routes('A', 'B') */ invert() { for (const k in this._data.vertex) { const gv = this._data.vertex[k]; for (const v in gv) { gv[v] *= -1; } } return this; } }; // src/Utils/Hashmap.ts function useHashmap() { const _map = /* @__PURE__ */ new Map(); const set = (key, data) => { _map.set(key, data); return data; }; const has2 = (key) => _map.has(key); const get = (key) => _map.get(key); const ensure2 = (key, callback) => { if (has2(key)) { return get(key); } return set(key, callback()); }; return { set, has: has2, get, ensure: ensure2 }; } // src/TrafficTraversal.ts var TrafficTraversal = class _TrafficTraversal { _trafficGraph; _cEdge; _cStrings; _cNumber; _cNumbers; _cVertex; static Create(...args) { return new _TrafficTraversal(...args); } /** * Create an instance that is responsible for the route and utility functions of the graph instance. It takes a `graph.state` instance as a parameter. * @param trafficGraphState */ constructor(trafficGraphState) { this._trafficGraph = trafficGraphState; this._cEdge = useHashmap(); this._cStrings = useHashmap(); this._cNumber = useHashmap(); this._cNumbers = useHashmap(); this._cVertex = useHashmap(); } _graphVertex(vertex) { return this._cVertex.ensure(`vertices from '${vertex}'`, () => { return this._trafficGraph.data.vertex[vertex] ?? setObjectMap({}); }); } _getTrafficPrev(from) { return this._cEdge.ensure(`traffic map from '${from}'`, () => { const queue = []; const inQueue = setObjectMap({}); const distance = setObjectMap({}); const edge = setObjectMap({}); for (const v of this._trafficGraph.vertices) { distance[v] = Infinity; edge[v] = ""; } distance[from] = 0; inQueue[from] = true; queue.push(from); while (queue.length) { const u = queue.shift(); const vertices = this._graphVertex(u); for (const v in vertices) { const d_u = distance[u]; const d_v = distance[v]; const w_uv = vertices[v]; const cur = d_u + w_uv; if (cur < d_v) { distance[v] = cur; edge[v] = u; if (!inQueue[v]) { inQueue[v] = true; queue.push(v); } } } } return edge; }); } _getRoutes(from, to) { const routes = this._cStrings.ensure(`route ${from} to ${to}`, () => { const inQueue = setObjectMap({}); const prev = this._getTrafficPrev(from); const routes2 = [to]; let v = prev[to]; while (v) { inQueue[v] = true; routes2.push(v); v = prev[v]; if (v === from) { routes2.push(from); break; } else if (inQueue[v]) { break; } } routes2.reverse(); return routes2; }); return routes; } _reach(routes, from, to) { return first(routes) === from && last(routes) === to; } /** * Finds the route with the lowest weight between two vertices and returns it as an array. * @param from This is the starting vertex. * @param to This is the target vertex. */ routes(from, to) { const routes = this._getRoutes(from, to); if (!this._reach(routes, from, to)) { throw new Error(`It is a structure that cannot be reached from vertex '${from}' to '${to}'.`); } return copy(routes); } _addEdges(vertex, depth, curDepth, queue, inQueue) { const gv = this._graphVertex(vertex); const done = curDepth === depth; if (done) { return; } for (const v in gv) { if (inQueue[v]) { continue; } queue.push(v); inQueue[v] = true; this._addEdges(v, depth, curDepth + 1, queue, inQueue); } } /** * Returns a list of vertices adjacent to that vertex as an array. You can set a depth limit using the `depth` parameter. * @param vertex The vertex from which to start the search. * @param depth Set how deep to search from the vertex. If you specify this value as a negative number, the search is unrestricted. Default is `-1`. */ edges(vertex, depth = -1) { return this._cStrings.ensure(`edges from '${vertex}' with '${depth}' depth`, () => { const queue = []; this._addEdges(vertex, depth, 0, queue, setObjectMap({})); return queue; }); } /** * Returns whether the target vertex can be reached from the starting vertex. * @param from This is the starting vertex. * @param to This is the target vertex. */ reachable(from, to) { return this._reach(this._getRoutes(from, to), from, to); } /** * Returns the sum of the least weighted routes from the starting vertex to the target vertex. * @param from This is the starting vertex. * @param to This is the target vertex. */ traffic(from, to) { return this._cNumber.ensure(`traffic from '${from}' to '${to}'`, () => { const routes = this._getRoutes(from, to); if (!this._reach(routes, from, to)) { return Infinity; } let vertex = first(routes); let traffic = 0; let i = 0; while (vertex) { i++; const next = routes[i]; const gv = this._graphVertex(vertex); if (!(next in gv)) { break; } traffic += gv[next]; vertex = next; } return traffic; }); } weight(vertex, mode) { const tuple = this._cNumbers.ensure(`weight tuple from '${vertex}'`, () => { let weight = 0; let num = 0; for (const k in this._trafficGraph.data.vertex) { const gv = this._trafficGraph.data.vertex[k]; if (!(vertex in gv)) { continue; } weight += gv[vertex]; num++; } return [weight, num]; }); switch (mode) { case "traffic": return tuple[0]; case "number": return tuple[1]; case "mean": { const mean = tuple[0] / tuple[1]; return Number.isNaN(mean) ? 0 : mean; } default: { throw new Error(`The '${mode}' mode is unsupported.`); } } } /** * Returns the shortest distance from the starting vertex to the target vertex. This is similar to the `distance` method, but takes direction into account. If unreachable, returns `Infinity`. * @param from This is the starting vertex. * @param to This is the target vertex. * @returns */ depth(from, to) { return this._cNumber.ensure(`depth from '${from}' to '${to}'`, () => { const routes = this._getRoutes(from, to); if (!this._reach(routes, from, to)) { return Infinity; } return routes.length - 1; }); } /** * Returns the shortest distance between two vertices. This is similar to the `depth` method, but does not take direction into account. * @param a The vertex a. * @param b The vertex b. */ distance(a, b) { return Math.min(this.depth(a, b), this.depth(b, a)); } };