@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
1,990 lines (1,922 loc) • 102 kB
JavaScript
import Matrix from './matrix.js'
/**
* Exception for graph class
*/
export class GraphException extends Error {
/**
* @param {string} message Error message
* @param {*} value Some value
*/
constructor(message, value) {
super(message)
this.value = value
this.name = 'GraphException'
}
}
/**
* Edge of graph
*/
export class Edge {
/**
* @param {number} from Index of the starting node of the edge
* @param {number} to Index of the end node of the edge
* @param {unknown} [value] Value of the edge
* @param {boolean} [direct] `true` if the edge is direct
*/
constructor(from, to, value = null, direct = false) {
this[0] = from
this[1] = to
this.value = value ?? 1
this.direct = direct
this.weighted = value != null
}
get from() {
return this[0]
}
get to() {
return this[1]
}
get value0() {
return this.weighted ? this.value : null
}
}
/**
* Graph class
*/
export default class Graph {
/**
* @param {number | unknown[]} [nodes] Number of nodes or values of nodes
* @param {([number, number] | {0?: number, 1?: number, from?: number, to?: number, value?: unknown, direct?: boolean} | Edge)[]} [edges] Edges
*/
constructor(nodes = 0, edges = []) {
if (Array.isArray(nodes)) {
/** @private */
this._nodes = nodes
} else {
this._nodes = Array(nodes)
}
/** @private */
this._edges = edges.map(
e => new Edge(e[0] | e.from, e[1] | e.to, e instanceof Edge ? e.value0 : e.value, e.direct)
)
}
/**
* Returns graph from adjacency matrix.
* @param {number[][] | boolean[][]} mat Adjacency matrix
* @returns {Graph} Graph from adjacency matrix
*/
static fromAdjacency(mat) {
const n = mat.length
const e = []
for (let i = 0; i < n; i++) {
for (let j = 0; j <= i; j++) {
if (!mat[i][j] && !mat[j][i]) {
continue
}
if (mat[i][j] === mat[j][i]) {
e.push(new Edge(j, i, +mat[i][j]))
} else {
if (mat[i][j]) {
e.push(new Edge(i, j, +mat[i][j], true))
}
if (mat[j][i]) {
e.push(new Edge(j, i, +mat[j][i], true))
}
}
}
}
return new Graph(n, e)
}
/**
* Returns complete graph.
* @param {number} n Size of the graph
* @returns {Graph} Complete graph
*/
static complete(n) {
const e = []
for (let i = 0; i < n; i++) {
for (let j = 0; j < i; j++) {
e.push([i, j])
}
}
return new Graph(n, e)
}
/**
* Returns complete bipartite graph.
* @param {number} n Size of the first group
* @param {number} m Size of the second group
* @returns {Graph} Complete bipartite graph
*/
static completeBipartite(n, m) {
const e = []
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
e.push([i, j + n])
}
}
return new Graph(n + m, e)
}
/**
* Returns cycle graph.
* @param {number} n Size of the graph
* @param {boolean} [direct] Direct graph or not
* @returns {Graph} Cycle graph
*/
static cycle(n, direct = false) {
if (n < 3) {
throw new GraphException('Index out of bounds.')
}
const e = []
for (let i = 0; i < n; i++) {
e.push(new Edge(i, (i + 1) % n, null, direct))
}
return new Graph(n, e)
}
/**
* Returns wheel graph.
* @param {number} n Size of the graph
* @returns {Graph} Wheel graph
*/
static wheel(n) {
if (n < 4) {
throw new GraphException('Index out of bounds.')
}
const e = []
for (let i = 1; i < n; i++) {
e.push([i, (i % (n - 1)) + 1])
e.push([0, i])
}
return new Graph(n, e)
}
/**
* Returns windmill graph.
* @param {number} k Size of the sub complete graph
* @param {number} n Number of the sub complete graph
* @returns {Graph} Windmill graph
*/
static windmill(k, n) {
if (k === 1) {
return new Graph(1)
}
const e = []
for (let i = 0; i < n; i++) {
for (let s = 0; s < k; s++) {
for (let t = 0; t < s; t++) {
e.push([t === 0 ? 0 : i * (k - 1) + t, i * (k - 1) + s])
}
}
}
return new Graph(n * (k - 1) + 1, e)
}
/**
* Returns named graph
* @param {'balaban 10 cage' | 'bidiakis cube' | 'biggs smith' | 'brinkmann' | 'bull' | 'butterfly' | 'chvatal' | 'clebsch' | 'coxeter' | 'desargues' | 'diamond' | 'durer' | 'errera' | 'folkman' | 'foster' | 'franklin' | 'frucht' | 'goldner-harary' | 'golomb' | 'gray' | 'grotzsch' | 'harries' | 'heawood' | 'herschel' | 'hoffman' | 'holt' | 'kittell' | 'markstrom' | 'mcgee' | 'meredith' | 'mobius kantor' | 'moser spindle' | 'nauru' | 'pappus' | 'petersen' | 'poussin' | 'robertson' | 'shrikhande' | 'sousselier' | 'sylvester' | 'tutte' | 'tutte coxeter' | 'wagner' | 'wells'} name Name of the graph
* @returns {Graph} Named graph
*/
static fromName(name) {
switch (name) {
case 'balaban 10 cage': {
const edges = []
for (let i = 0; i < 10; i++) {
edges.push([i * 2, ((i + 3) % 10) * 2 + 1])
edges.push([i * 2 + 1, ((i + 3) % 10) * 2])
edges.push([i * 2, i * 3 + 20])
edges.push([i * 2 + 1, i * 3 + 22])
edges.push([i * 3 + 20, i * 3 + 21])
edges.push([i * 3 + 21, i * 3 + 22])
if (i < 5) {
edges.push([i * 3 + 21, (i + 5) * 3 + 21])
}
edges.push([i * 3 + 20, i * 2 + 50])
edges.push([i * 3 + 22, i * 2 + 51])
edges.push([i * 2 + 50, ((i + 1) % 10) * 2 + 50])
edges.push([i * 2 + 51, ((i + 1) % 10) * 2 + 51])
}
return new Graph(70, edges)
}
case 'bidiakis cube': {
const edges = []
for (let i = 0; i < 12; i++) {
edges.push([i, (i + 1) % 12])
}
edges.push([0, 8], [1, 7], [2, 6], [3, 11], [4, 10], [5, 9])
return new Graph(12, edges)
}
case 'biggs smith': {
const edges = []
for (let i = 0; i < 17; i++) {
edges.push([i, (i + 8) % 17])
edges.push([i, i + 17])
edges.push([i + 17, i + 51])
edges.push([i + 17, i + 68])
edges.push([i + 34, ((i + 4) % 17) + 34])
edges.push([i + 34, i + 68])
edges.push([i + 51, ((i + 2) % 17) + 51])
edges.push([i + 68, i + 85])
edges.push([i + 85, ((i + 1) % 17) + 85])
}
return new Graph(102, edges)
}
case 'brinkmann': {
const edges = []
for (let i = 0; i < 7; i++) {
edges.push([i, (i + 3) % 7])
edges.push([i, i + 7])
edges.push([i, ((i + 5) % 7) + 7])
edges.push([i + 7, i + 14])
edges.push([i + 7, ((i + 1) % 7) + 14])
edges.push([i + 14, ((i + 2) % 7) + 14])
}
return new Graph(21, edges)
}
case 'bull':
return new Graph(5, [
[0, 1],
[1, 2],
[1, 3],
[2, 3],
[3, 4],
])
case 'butterfly':
return new Graph(5, [
[0, 1],
[0, 2],
[1, 2],
[2, 3],
[2, 4],
[3, 4],
])
case 'chvatal': {
const edges = []
for (let i = 0; i < 4; i++) {
edges.push([i, (i + 1) % 4])
edges.push([i, i * 2 + 4])
edges.push([i, ((i * 2 + 7) % 8) + 4])
edges.push([i * 2 + 4, i * 2 + 5])
edges.push([i * 2 + 4, ((i * 2 + 3) % 8) + 4])
}
edges.push([4, 8], [5, 9], [6, 10], [7, 11])
return new Graph(12, edges)
}
case 'clebsch': {
const edges = []
for (let i = 0; i < 8; i++) {
edges.push([i, (i + 3) % 8])
edges.push([i, i + 8])
edges.push([i, ((i + 2) % 8) + 8])
edges.push([i + 8, ((i + 1) % 8) + 8])
}
for (let i = 0; i < 4; i++) {
edges.push([i, i + 4])
edges.push([i + 8, i + 12])
}
return new Graph(16, edges)
}
case 'coxeter': {
const edges = []
for (let i = 0; i < 3; i++) {
edges.push([0, i * 8 + 4])
edges.push([i + 1, i * 8 + 8])
edges.push([i + 1, ((i * 8 + 15) % 24) + 4])
edges.push([i + 1, ((i * 8 + 22) % 24) + 4])
edges.push([i * 8 + 5, ((i * 8 + 19) % 24) + 4])
edges.push([i * 8 + 9, ((i * 8 + 18) % 24) + 4])
}
for (let i = 0; i < 24; i++) {
edges.push([i + 4, ((i + 1) % 24) + 4])
}
return new Graph(28, edges)
}
case 'desargues': {
const edges = []
for (let i = 0; i < 10; i++) {
edges.push([i, (i + 3) % 10])
edges.push([i, i + 10])
edges.push([i + 10, ((i + 1) % 10) + 10])
}
return new Graph(20, edges)
}
case 'diamond':
return new Graph(4, [
[0, 1],
[0, 2],
[1, 2],
[1, 3],
[2, 3],
])
case 'durer': {
const edges = []
for (let i = 0; i < 6; i++) {
edges.push([i, (i + 1) % 6])
edges.push([i, i + 6])
edges.push([i + 6, ((i + 2) % 6) + 6])
}
return new Graph(12, edges)
}
case 'dyck': {
const edges = []
for (let i = 0; i < 32; i++) {
edges.push([i, (i + 1) % 32])
}
for (let i = 0; i < 8; i++) {
edges.push([i * 4, (i * 4 + 5) % 32])
edges.push([i * 4 + 2, (i * 4 + 15) % 32])
}
return new Graph(32, edges)
}
case 'errera': {
const edges = [
[0, 1],
[2, 3],
[5, 6],
[9, 10],
[15, 16],
]
for (let i = 0; i < 2; i++) {
edges.push([0, 11 + i])
edges.push([0, 15 + i])
edges.push([1, 5 + i])
edges.push([1, 11 + i])
edges.push([2, 5 + i])
edges.push([2, 7 + i])
edges.push([3, 7 + i])
edges.push([3, 9 + i])
edges.push([4, 9 + i])
edges.push([4, 13 + i])
edges.push([4, 15 + i])
edges.push([5 + i, 7 + i])
edges.push([5 + i, 11 + i])
edges.push([5 + i, 13 + i])
edges.push([7 + i, 9 + i])
edges.push([7 + i, 13 + i])
edges.push([9 + i, 13 + i])
edges.push([11 + i, 13 + i])
edges.push([11 + i, 15 + i])
edges.push([13 + i, 15 + i])
}
return new Graph(17, edges)
}
case 'folkman': {
const edges = []
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 4; j++) {
edges.push([i * 4 + j, i * 4 + ((j + 1) % 4)])
}
}
for (let i = 0; i < 4; i++) {
edges.push([i * 4 + 2, (i + 1) * 4 + 1])
edges.push([i * 4 + 2, (i + 1) * 4 + 3])
}
for (let i = 0; i < 3; i++) {
edges.push([i * 4, (i + 2) * 4 + 1])
edges.push([i * 4, (i + 2) * 4 + 3])
}
edges.push([1, 12], [1, 18], [3, 12], [3, 18], [5, 16], [7, 16])
return new Graph(20, edges)
}
case 'foster': {
const edges = []
for (let i = 0; i < 15; i++) {
for (let j = 0; j < 6; j++) {
edges.push([i * 6 + j, (i * 6 + j + 1) % 90])
}
edges.push([i * 6, ((i + 8) % 15) * 6 + 5])
edges.push([i * 6 + 1, ((i + 1) % 15) * 6 + 4])
edges.push([i * 6 + 2, ((i + 12) % 15) * 6 + 3])
}
return new Graph(90, edges)
}
case 'franklin': {
const edges = []
for (let i = 0; i < 12; i++) {
edges.push([i, (i + 1) % 12])
}
edges.push([0, 7], [1, 6], [2, 9], [3, 8], [4, 11], [5, 10])
return new Graph(12, edges)
}
case 'frucht': {
const edges = []
for (let i = 0; i < 7; i++) {
edges.push([i, (i + 1) % 7])
}
edges.push([0, 7], [1, 8], [2, 8], [3, 9], [4, 9], [5, 10], [6, 10], [7, 10], [7, 11], [8, 11], [9, 11])
return new Graph(12, edges)
}
case 'goldner-harary': {
const edges = []
for (let i = 0; i < 4; i++) {
edges.push([0, i + 1])
edges.push([0, i + 5])
edges.push([i + 1, i + 5])
edges.push([i + 1, ((i + 3) % 4) + 5])
edges.push([i + 5, ((i + 3) % 4) + 5])
}
edges.push([5, 9], [9, 7], [7, 10], [10, 5], [5, 7], [6, 9], [8, 10])
return new Graph(11, edges)
}
case 'golomb': {
const edges = []
for (let i = 0; i < 6; i++) {
edges.push([0, i + 1])
edges.push([i + 1, ((i + 1) % 6) + 1])
}
for (let i = 0; i < 3; i++) {
edges.push([i + 7, ((i + 1) % 3) + 7])
edges.push([i * 2 + 1, i + 7])
}
return new Graph(10, edges)
}
case 'gray': {
const edges = []
for (let i = 0; i < 54; i++) {
edges.push([i, (i + 1) % 54])
}
for (let i = 0; i < 9; i++) {
edges.push([i * 6, ((i + 4) % 9) * 6 + 1])
edges.push([i * 6 + 2, ((i + 2) % 9) * 6 + 3])
edges.push([i * 6 + 4, ((i + 1) % 9) * 6 + 5])
}
return new Graph(54, edges)
}
case 'grotzsch': {
const edges = []
for (let i = 0; i < 5; i++) {
edges.push([0, i + 1])
edges.push([i + 1, i + 6])
edges.push([i + 6, ((i + 4) % 5) + 1])
edges.push([i + 6, ((i + 2) % 5) + 6])
}
return new Graph(11, edges)
}
case 'harries': {
const edges = []
for (let i = 0; i < 10; i++) {
edges.push([i, (i + 3) % 10])
edges.push([i, i * 5 + 20])
if (i % 2 === 0) {
edges.push([i + 10, ((i + 1) % 10) + 10])
edges.push([i + 10, i * 5 + 22])
} else {
edges.push([i + 10, ((i + 3) % 10) + 10])
edges.push([i + 10, i * 5 + 24])
}
}
for (let i = 0; i < 50; i++) {
edges.push([i + 20, ((i + 1) % 50) + 20])
}
for (let i = 0; i < 5; i++) {
edges.push([i * 10 + 24, ((i * 10 + 13) % 50) + 20])
edges.push([i * 10 + 28, ((i * 10 + 17) % 50) + 20])
edges.push([i * 5 + 21, ((i * 5 + 25) % 50) + 21])
}
return new Graph(70, edges)
}
case 'heawood': {
const edges = []
for (let i = 0; i < 7; i++) {
edges.push([i * 2, i * 2 + 1])
edges.push([i * 2 + 1, (i * 2 + 2) % 14])
edges.push([i * 2, (i * 2 + 5) % 14])
}
return new Graph(14, edges)
}
case 'herschel': {
const edges = []
for (let i = 0; i < 4; i++) {
edges.push([0, i + 1])
edges.push([i + 1, i + 5])
edges.push([i + 1, ((i + 3) % 4) + 5])
}
edges.push([5, 9], [5, 10], [6, 9], [7, 9], [7, 10], [8, 10])
return new Graph(11, edges)
}
case 'hoffman': {
const edges = []
for (let i = 0; i < 2; i++) {
edges.push([0, 6 + i])
edges.push([0, 10 + i])
edges.push([1, 8 + i])
edges.push([1, 10 + i])
edges.push([2, 6 + i])
edges.push([2, 14 + i])
edges.push([3, 8 + i])
edges.push([3, 14 + i])
edges.push([4 + i, 6 + i])
edges.push([4 + i, 10 + i])
edges.push([4 + i, 9 - i])
edges.push([4 + i, 15 - i])
edges.push([6 + i, 12 + i])
edges.push([8 + i, 12 + i])
edges.push([10 + i, 12 + i])
edges.push([12 + i, 14 + i])
}
return new Graph(16, edges)
}
case 'holt': {
const edges = []
for (let i = 0; i < 9; i++) {
edges.push([i, (i + 4) % 9])
edges.push([i, ((i + 7) % 9) + 9])
edges.push([i, ((i + 1) % 9) + 18])
edges.push([i + 9, ((i + 1) % 9) + 9])
edges.push([i + 9, i + 18])
edges.push([i + 18, ((i + 2) % 9) + 18])
}
return new Graph(27, edges)
}
case 'kittell':
return new Graph(23, [
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[0, 15],
[0, 16],
[1, 2],
[1, 4],
[1, 5],
[1, 6],
[1, 7],
[1, 12],
[2, 3],
[2, 7],
[2, 8],
[3, 8],
[3, 14],
[3, 15],
[4, 5],
[4, 9],
[4, 16],
[4, 17],
[5, 6],
[5, 9],
[5, 10],
[6, 10],
[6, 11],
[6, 12],
[7, 8],
[7, 12],
[7, 13],
[8, 13],
[8, 14],
[9, 10],
[9, 17],
[9, 18],
[10, 11],
[10, 18],
[10, 19],
[11, 12],
[11, 19],
[11, 20],
[12, 13],
[12, 20],
[12, 21],
[13, 14],
[13, 21],
[14, 15],
[14, 21],
[15, 16],
[15, 21],
[16, 17],
[16, 21],
[16, 22],
[17, 18],
[17, 22],
[18, 19],
[18, 22],
[19, 20],
[19, 22],
[20, 21],
[20, 22],
[21, 22],
])
case 'markstrom': {
const edges = []
for (let i = 0; i < 3; i++) {
edges.push([i * 8, i * 8 + 1])
edges.push([i * 8, i * 8 + 4])
edges.push([i * 8 + 1, i * 8 + 2])
edges.push([i * 8 + 1, i * 8 + 3])
edges.push([i * 8 + 2, i * 8 + 3])
edges.push([i * 8 + 2, ((i + 1) % 3) * 8])
edges.push([i * 8 + 3, i * 8 + 5])
edges.push([i * 8 + 4, i * 8 + 5])
edges.push([i * 8 + 4, i * 8 + 6])
edges.push([i * 8 + 5, i * 8 + 6])
edges.push([i * 8 + 6, i * 8 + 7])
edges.push([i * 8 + 7, ((i + 1) % 3) * 8 + 7])
}
return new Graph(24, edges)
}
case 'mcgee': {
const edges = []
for (let i = 0; i < 24; i++) {
edges.push([i, (i + 1) % 24])
}
for (let i = 0; i < 8; i++) {
edges.push([i * 3 + 1, (i * 3 + 8) % 24])
}
for (let i = 0; i < 4; i++) {
edges.push([i * 3, i * 3 + 12])
}
return new Graph(24, edges)
}
case 'meredith': {
const edges = []
for (let i = 0; i < 5; i++) {
for (let s = 0; s < 4; s++) {
for (let t = 0; t < 3; t++) {
edges.push([i * 14 + s, i * 14 + 4 + t])
edges.push([i * 14 + 7 + s, i * 14 + 11 + t])
}
}
edges.push([i * 14 + 1, i * 14 + 8])
edges.push([i * 14 + 2, i * 14 + 9])
edges.push([i * 14 + 3, ((i + 2) % 5) * 14])
edges.push([i * 14 + 10, ((i + 1) % 5) * 14 + 7])
}
return new Graph(70, edges)
}
case 'mobius kantor': {
const edges = []
for (let i = 0; i < 8; i++) {
edges.push([i, (i + 3) % 8])
edges.push([i, i + 8])
edges.push([i + 8, ((i + 1) % 8) + 8])
}
return new Graph(16, edges)
}
case 'moser spindle':
return new Graph(7, [
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[1, 2],
[1, 5],
[2, 5],
[3, 4],
[3, 6],
[4, 6],
[5, 6],
])
case 'nauru': {
const edges = []
for (let i = 0; i < 24; i++) {
edges.push([i, (i + 1) % 24])
}
for (let i = 0; i < 4; i++) {
edges.push([i * 6, i * 6 + 5])
edges.push([i * 6 + 2, (i * 6 + 9) % 24])
edges.push([i * 6 + 1, (i * 6 + 16) % 24])
}
return new Graph(24, edges)
}
case 'pappus': {
const edges = []
for (let i = 0; i < 6; i++) {
edges.push([i, ((i + 1) % 6) + 6])
edges.push([i, ((i + 5) % 6) + 6])
edges.push([i + 6, i + 12])
edges.push([i + 12, ((i + 1) % 6) + 12])
}
edges.push([0, 3], [1, 4], [2, 5])
return new Graph(18, edges)
}
case 'petersen': {
const edges = []
for (let i = 0; i < 5; i++) {
edges.push([i, (i + 1) % 5])
edges.push([i, i + 5])
edges.push([i + 5, ((i + 2) % 5) + 5])
}
return new Graph(10, edges)
}
case 'poussin': {
const edges = [
[0, 1],
[1, 2],
[3, 4],
[5, 6],
[13, 14],
]
for (let i = 0; i < 2; i++) {
edges.push([0, 7 + i])
edges.push([0, 13 + i])
edges.push([1, 7 + i])
edges.push([1, 9 + i])
edges.push([2, 5 + i])
edges.push([2, 9 + i])
edges.push([3, 5 + i])
edges.push([3, 11 + i])
edges.push([4, 11 + i])
edges.push([4, 13 + i])
edges.push([5 + i, 9 + i])
edges.push([5 + i, 11 + i])
edges.push([7 + i, 9 + i])
edges.push([7 + i, 13 + i])
edges.push([9 + i, 13 + i])
edges.push([9 + i, 11 + i])
edges.push([11 + i, 13 + i])
}
return new Graph(15, edges)
}
case 'robertson': {
const edges = [
[0, 2],
[1, 3],
]
for (let i = 0; i < 4; i++) {
edges.push([i, i * 3 + 4])
edges.push([(i + 3) % 4, i * 3 + 5])
edges.push([(i + 2) % 4, i * 3 + 6])
edges.push([i * 3 + 4, i * 3 + 5])
edges.push([i * 3 + 5, i * 3 + 6])
edges.push([i * 3 + 6, ((i + 1) % 4) * 3 + 4])
}
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 4; j++) {
edges.push([i + 16, ((i * 4 + j * 3) % 12) + 4])
}
}
return new Graph(19, edges)
}
case 'shrikhande': {
const edges = []
for (let i = 0; i < 4; i++) {
edges.push([i * 4, ((i + 1) % 4) * 4])
edges.push([i * 4, i * 4 + 1])
edges.push([i * 4, i * 4 + 2])
edges.push([i * 4, ((i + 1) % 4) * 4 + 1])
edges.push([i * 4, ((i + 1) % 4) * 4 + 2])
edges.push([i * 4 + 1, ((i + 1) % 4) * 4 + 2])
edges.push([i * 4 + 1, i * 4 + 3])
edges.push([i * 4 + 2, ((i + 1) % 4) * 4 + 1])
edges.push([i * 4 + 2, ((i + 1) % 4) * 4 + 3])
edges.push([i * 4 + 2, ((i + 2) % 4) * 4 + 3])
edges.push([i * 4 + 3, ((i + 1) % 4) * 4 + 1])
edges.push([i * 4 + 3, ((i + 1) % 4) * 4 + 3])
}
return new Graph(16, edges)
}
case 'sousselier': {
const edges = []
for (let i = 0; i < 5; i++) {
edges.push([0, i * 3 + 1])
edges.push([i * 3 + 2, ((i * 3 + 5) % 15) + 1])
}
for (let i = 0; i < 15; i++) {
edges.push([i + 1, ((i + 1) % 15) + 1])
}
edges.push([2, 12], [5, 15])
return new Graph(16, edges)
}
case 'sylvester': {
const edges = []
for (let i = 0; i < 6; i++) {
edges.push([i * 6, i * 6 + 2])
edges.push([i * 6, i * 6 + 5])
edges.push([i * 6, ((i + 1) % 6) * 6 + 4])
edges.push([i * 6, ((i + 3) % 6) * 6 + 1])
edges.push([i * 6, ((i + 3) % 6) * 6 + 3])
edges.push([i * 6 + 1, i * 6 + 2])
edges.push([i * 6 + 1, ((i + 2) % 6) * 6 + 4])
edges.push([i * 6 + 1, ((i + 3) % 6) * 6 + 4])
edges.push([i * 6 + 1, ((i + 4) % 6) * 6 + 2])
edges.push([i * 6 + 2, ((i + 1) % 6) * 6 + 5])
edges.push([i * 6 + 2, ((i + 2) % 6) * 6 + 5])
edges.push([i * 6 + 3, i * 6 + 4])
edges.push([i * 6 + 3, ((i + 1) % 6) * 6 + 3])
edges.push([i * 6 + 3, ((i + 1) % 6) * 6 + 5])
if (i < 3) {
edges.push([i * 6 + 4, ((i + 3) % 6) * 6 + 4])
edges.push([i * 6 + 5, ((i + 3) % 6) * 6 + 5])
}
}
return new Graph(36, edges)
}
case 'tutte': {
const edges = []
for (let i = 0; i < 3; i++) {
edges.push([0, i * 15 + 1])
edges.push([i * 15 + 1, i * 15 + 2])
edges.push([i * 15 + 1, i * 15 + 4])
edges.push([i * 15 + 2, i * 15 + 3])
edges.push([i * 15 + 2, i * 15 + 5])
edges.push([i * 15 + 3, i * 15 + 4])
edges.push([i * 15 + 3, i * 15 + 7])
edges.push([i * 15 + 4, i * 15 + 9])
edges.push([i * 15 + 5, i * 15 + 6])
edges.push([i * 15 + 5, i * 15 + 10])
edges.push([i * 15 + 6, i * 15 + 7])
edges.push([i * 15 + 6, i * 15 + 11])
edges.push([i * 15 + 7, i * 15 + 8])
edges.push([i * 15 + 8, i * 15 + 9])
edges.push([i * 15 + 8, i * 15 + 12])
edges.push([i * 15 + 9, i * 15 + 15])
edges.push([i * 15 + 10, i * 15 + 11])
edges.push([i * 15 + 10, i * 15 + 13])
edges.push([i * 15 + 11, i * 15 + 12])
edges.push([i * 15 + 12, i * 15 + 14])
edges.push([i * 15 + 13, i * 15 + 14])
edges.push([i * 15 + 14, i * 15 + 15])
edges.push([i * 15 + 15, ((i + 1) % 3) * 15 + 13])
}
return new Graph(46, edges)
}
case 'tutte coxeter': {
const edges = []
for (let i = 0; i < 30; i++) {
edges.push([i, (i + 1) % 30])
}
for (let i = 0; i < 5; i++) {
edges.push([i * 6, ((i + 1) % 5) * 6 + 3])
edges.push([i * 6 + 1, ((i + 2) % 5) * 6 + 2])
edges.push([i * 6 + 4, ((i + 1) % 5) * 6 + 5])
}
return new Graph(30, edges)
}
case 'wagner': {
const edges = []
for (let i = 0; i < 4; i++) {
edges.push([i, i + 1])
edges.push([i, i + 4])
edges.push([i + 4, (i + 5) % 8])
}
return new Graph(8, edges)
}
case 'wells': {
const edges = []
for (let i = 0; i < 8; i++) {
edges.push([i * 4, ((i + 1) % 8) * 4])
edges.push([i * 4, ((i + 2) % 8) * 4 + 3])
edges.push([i * 4 + 1, ((i + 1) % 8) * 4 + 3])
edges.push([i * 4 + 1, ((i + 2) % 8) * 4])
edges.push([i * 4 + 1, ((i + 2) % 8) * 4 + 3])
edges.push([i * 4 + 1, ((i + 3) % 8) * 4 + 2])
edges.push([i * 4 + 2, ((i + 1) % 8) * 4])
edges.push([i * 4 + 2, ((i + 3) % 8) * 4 + 2])
edges.push([i * 4 + 3, ((i + 1) % 8) * 4 + 1])
edges.push([i * 4 + 3, ((i + 3) % 8) * 4 + 2])
}
return new Graph(32, edges)
}
case 'wiener araya': {
const edges = []
for (let i = 0; i < 4; i++) {
edges.push([i * 10, i * 10 + 1])
edges.push([i * 10, i * 10 + 2])
edges.push([i * 10, ((i + 1) % 4) * 10])
edges.push([i * 10 + 1, i * 10 + 3])
edges.push([i * 10 + 1, i * 10 + 5])
edges.push([i * 10 + 2, i * 10 + 4])
edges.push([i * 10 + 2, ((i + 1) % 4) * 10 + 5])
edges.push([i * 10 + 3, i * 10 + 4])
edges.push([i * 10 + 3, i * 10 + 7])
edges.push([i * 10 + 4, i * 10 + 9])
edges.push([i * 10 + 5, i * 10 + 6])
edges.push([i * 10 + 6, i * 10 + 7])
edges.push([i * 10 + 6, ((i + 3) % 4) * 10 + 9])
edges.push([i * 10 + 7, i * 10 + 8])
edges.push([i * 10 + 8, i * 10 + 9])
if (i % 2 === 1) {
edges.push([i * 10 + 8, ((i + 1) % 4) * 10 + 8])
edges.push([i * 10 + 8, i === 1 ? 40 : 41])
} else {
edges.push([i * 10 + 9, i === 0 ? 40 : 41])
}
}
edges.push([40, 41])
return new Graph(42, edges)
}
}
}
/**
* Number of nodes
* @type {number}
*/
get order() {
return this._nodes.length
}
/**
* Number of edges
* @type {number}
*/
get size() {
return this._edges.length
}
/**
* Nodes
* @type {unknown[]}
*/
get nodes() {
return this._nodes
}
/**
* Edges
* @type {Edge[]}
*/
get edges() {
return this._edges
}
/**
* Returns a string of DOT format.
* @returns {string} String of DOT format
*/
toDot() {
let s = this.isUndirected() ? 'graph' : 'digraph'
s += ' g {\n'
for (let i = 0; i < this._nodes.length; i++) {
s += ` ${i} [label="${JSON.stringify(this._nodes[i] ?? i).replace('"', "'")}"];\n`
}
for (const e of this._edges) {
s += ` ${e[0]} ${e.direct ? '->' : '--'} ${e[1]}`
if (e.weighted) {
s += ` [label="${JSON.stringify(e.value).replace('"', "'")}"]`
}
s += ';\n'
}
return `${s}}`
}
/**
* Returns a string represented this graph.
* @returns {string} String represented this graph
*/
toString() {
let s = `Number of nodes: ${this._nodes.length}\n`
if (this._nodes.some(v => v != null)) {
s += `Node values: ${JSON.stringify(this._nodes)}\n`
}
s += `Number of edges: ${this._edges.length}`
if (this._edges.length > 0) {
s += `\nEdges`
}
for (const e of this._edges) {
s += `\n From ${e[0]} to ${e[1]}, value: ${JSON.stringify(e.value)} (${
e.direct ? 'directed' : 'undirected'
})`
}
return s
}
/**
* Returns a copy of this graph.
* @returns {Graph} Copied grpah
*/
copy() {
const edges = this._edges.map(e => new Edge(e[0], e[1], e.value0, e.direct))
const nodes = this._nodes.concat()
return new Graph(nodes, edges)
}
/**
* Return degree of the node.
* @overload
* @param {number} k Index of target node
* @param {boolean} [undirect] Count undirected edges
* @param {boolean | 'in' | 'out'} [direct] Count directed edges
* @returns {number} Degree of the node
*/
/**
* Return degree of the node.
* @overload
* @param {number} k Index of target node
* @param {'in' | 'out'} direct Count only directed edges.
* @returns {number} Degree of the node
*/
/**
* @param {number} k Index of target node
* @param {boolean | 'in' | 'out'} [undirect] Count undirected edges. If `in` or `out` is specified, only direct edges are counted and `direct` parameter is ignored.
* @param {boolean | 'in' | 'out'} [direct] Count directed edges
* @returns {number} Degree of the node
*/
degree(k, undirect = true, direct = true) {
if (undirect === 'in' || undirect === 'out') {
direct = undirect
undirect = false
}
let c = 0
for (const e of this._edges) {
if (undirect && !e.direct && (e[0] === k || e[1] === k)) {
c++
} else if (direct === true && e.direct && (e[0] === k || e[1] === k)) {
c++
} else if (direct === 'in' && e.direct && e[1] === k) {
c++
} else if (direct === 'out' && e.direct && e[0] === k) {
c++
}
}
return c
}
/**
* Return indexes of adjacency nodes.
* @overload
* @param {number} k Index of target node
* @param {boolean} [undirect] Check undirected edges
* @param {boolean | 'in' | 'out'} [direct] Check directed edges
* @returns {number[]} Indexes of adjacency nodes
*/
/**
* Return indexes of adjacency nodes.
* @overload
* @param {number} k Index of target node
* @param {'in' | 'out'} direct Check only directed edges
* @returns {number[]} Indexes of adjacency nodes
*/
/**
* @param {number} k Index of target node
* @param {boolean | 'in' | 'out'} [undirect] Check undirected edges. If `in` or `out` is specified, only direct edges are checked and `direct` parameter is ignored.
* @param {boolean | 'in' | 'out'} [direct] Check directed edges
* @returns {number[]} Indexes of adjacency nodes
*/
adjacencies(k, undirect = true, direct = true) {
if (undirect === 'in' || undirect === 'out') {
direct = undirect
undirect = false
}
const nodesSet = new Set()
for (const e of this._edges) {
if (undirect && !e.direct && (e[0] === k || e[1] === k)) {
nodesSet.add(e[0] === k ? e[1] : e[0])
} else if (direct === true && e.direct && (e[0] === k || e[1] === k)) {
nodesSet.add(e[0] === k ? e[1] : e[0])
} else if (direct === 'in' && e.direct && e[1] === k) {
nodesSet.add(e[0])
} else if (direct === 'out' && e.direct && e[0] === k) {
nodesSet.add(e[1])
}
}
const nodes = [...nodesSet]
nodes.sort((a, b) => a - b)
return nodes
}
/**
* Returns indexes of each components.
* @returns {number[][]} Indexes of each components
*/
components() {
const checked = Array(this._nodes.length).fill(false)
const stack = [0]
const comps = []
let curcomp = []
while (stack.length > 0 || checked.some(v => !v)) {
if (stack.length === 0) {
curcomp.sort((a, b) => a - b)
comps.push(curcomp)
curcomp = []
}
const i = stack.length > 0 ? stack.pop() : checked.indexOf(false)
if (checked[i]) {
continue
}
checked[i] = true
curcomp.push(i)
for (const e of this._edges) {
if (e[0] === i) {
stack.push(e[1])
}
if (e[1] === i) {
stack.push(e[0])
}
}
}
if (curcomp.length > 0) {
curcomp.sort((a, b) => a - b)
comps.push(curcomp)
}
return comps
}
/**
* Returns indexes of each biconnected components.
* @returns {number[][]} Indexes of each biconnected components
*/
biconnectedComponents() {
const structure = this.toSimple().toUndirected()
const notcheckedComponents = structure.components()
const components = []
while (notcheckedComponents.length > 0) {
const component = notcheckedComponents.pop()
const subg = structure.inducedSub(component)
const a = subg.articulations()
if (a.length === 0) {
components.push(component)
continue
}
const sep = a[0]
subg.removeNode(sep)
const subcomp = subg.components()
for (let i = 0; i < subcomp.length; i++) {
const c = []
for (let j = 0; j < subcomp[i].length; j++) {
if (subcomp[i][j] >= sep) {
c.push(component[subcomp[i][j] + 1])
} else {
c.push(component[subcomp[i][j]])
}
}
c.push(component[sep])
c.sort((a, b) => a - b)
notcheckedComponents.push(c)
}
}
return components
}
/**
* Returns diameter of this graph.
* @returns {number} Diameter
*/
diameter() {
const sp = this.shortestPathFloydWarshall()
let max_len = -Infinity
for (let i = 0; i < this._nodes.length; i++) {
for (let j = 0; j < this._nodes.length; j++) {
if (max_len < sp[i][j].length) {
max_len = sp[i][j].length
}
}
}
return max_len
}
/**
* Returns eccentricity at k of this graph.
* @param {number} k Index of target node
* @returns {number} Eccentricity
*/
eccentricity(k) {
const sp = this.shortestPathBellmanFord(k)
let max_len = -Infinity
for (let i = 0; i < this._nodes.length; i++) {
if (max_len < sp[i].length) {
max_len = sp[i].length
}
}
return max_len
}
/**
* Returns radius of this graph.
* @returns {number} Radius
*/
radius() {
const sp = this.shortestPathFloydWarshall()
let r = Infinity
for (let i = 0; i < this._nodes.length; i++) {
let max_len = -Infinity
for (let j = 0; j < this._nodes.length; j++) {
if (max_len < sp[i][j].length) {
max_len = sp[i][j].length
}
}
if (r > max_len) {
r = max_len
}
}
return r
}
/**
* Returns indexes of center of this graph.
* @returns {number[]} Indexes of center
*/
center() {
const sp = this.shortestPathFloydWarshall()
const ecc = Array(this._nodes.length).fill(-Infinity)
let r = Infinity
for (let i = 0; i < this._nodes.length; i++) {
for (let j = 0; j < this._nodes.length; j++) {
if (ecc[i] < sp[i][j].length) {
ecc[i] = sp[i][j].length
}
}
if (r > ecc[i]) {
r = ecc[i]
}
}
const c = []
for (let i = 0; i < ecc.length; i++) {
if (ecc[i] === r) {
c.push(i)
}
}
return c
}
/**
* Returns girth of this graph.
* @returns {number} Girth
*/
girth() {
let min_loop = Infinity
for (let s = 0; s < this._nodes.length; s++) {
const stack = [[[s], Array(this._edges.length).fill(false)]]
while (stack.length > 0) {
const [path, used] = stack.shift()
const i = path[path.length - 1]
for (let k = 0; k < this._edges.length; k++) {
if (used[k]) {
continue
}
const e = this._edges[k]
if (e[0] === i) {
if (e[1] === s) {
if (path.length < min_loop) {
min_loop = path.length
}
stack.length = 0
break
} else if (path.includes(e[1])) {
continue
}
const u = used.concat()
u[k] = true
stack.push([path.concat(e[1]), u])
}
if (!e.direct && e[1] === i) {
if (e[0] === s) {
if (path.length < min_loop) {
min_loop = path.length
}
stack.length = 0
break
} else if (path.includes(e[0])) {
continue
}
const u = used.concat()
u[k] = true
stack.push([path.concat(e[0]), u])
}
}
}
}
return min_loop
}
/**
* Returns index of all cliques.
* @overload
* @returns {number[][][]} Index of cliques
*/
/**
* Returns index of cliques.
* @overload
* @param {number} k Size of clique
* @returns {number[][]} Index of cliques
*/
/**
* @param {number} [k] Size of clique
* @returns {number[][] | number[][][]} Index of cliques
*/
clique(k) {
const n = this._nodes.length
if (k != null && k > n) {
return []
}
const c0 = Array.from({ length: n }, (_, i) => [i])
if (k === 1) {
return c0
}
if (n === 1) {
return [c0]
}
const con = []
const c1 = []
for (let i = 0; i < n; i++) {
con[i] = []
for (let j = 0; j < i; j++) {
if (this.getEdges(i, j).length > 0) {
con[i][j] = true
con[j][i] = true
c1.push([j, i])
}
}
}
if (k === 2) {
return c1
}
const K = k ?? this._nodes.length
const c = [c0, c1]
for (let i = 2; i < K; i++) {
const ci = []
for (let j = 0; j < c[i - 1].length; j++) {
const cij = c[i - 1][j]
for (let s = cij.at(-1) + 1; s < n; s++) {
if (cij.every(t => con[t][s])) {
ci.push(cij.concat(s))
}
}
}
c.push(ci)
}
return k == null ? c : c[k - 1]
}
/**
* Returns chromatic number of this graph.
* @returns {number} Chromatic number
*/
chromaticNumber() {
const n = this._nodes.length
if (n <= 1) {
return n
}
if (this._edges.length === 0) {
return 1
}
if (this.isBipartite()) {
return 2
}
if (this.isComplete()) {
return this._nodes.length
}
return this.chromaticNumberWelchPowell()
}
/**
* Returns chromatic number of this graph with Welch-Powell algorithm.
* @returns {number} Chromatic number
*/
chromaticNumberWelchPowell() {
const n = this._nodes.length
const alist = this.adjacencyList()
const degrees = []
for (let i = 0; i < n; i++) {
degrees[i] = [i, this.degree(i)]
}
degrees.sort((a, b) => b[1] - a[1])
let c = 0
const colors = Array(n).fill(-1)
while (true) {
const i = colors.indexOf(-1)
if (i < 0) {
break
}
colors[i] = c
for (let k = 0; k < n; k++) {
if (colors[k] >= 0) {
continue
}
if (alist[k].every(v => colors[v] !== c)) {
colors[k] = c
}
}
c++
}
return c
}
/**
* Returns chromatic index of this graph.
* @returns {number} Chromatic index
*/
chromaticIndex() {
const components = this.components()
let maxci = 0
for (let k = 0; k < components.length; k++) {
const g = this.inducedSub(components[k])
let ci = -1
if (g._edges.length <= 1) {
ci = g._edges.length
} else if (g.isBipartite()) {
ci = 0
for (let i = 0; i < g._nodes.length; i++) {
ci = Math.max(ci, g.degree(i))
}
} else if (g.isComplete()) {
ci = g._nodes.length % 2 === 0 ? g._nodes.length - 1 : g._nodes.length
} else {
throw new GraphException('Not implemented')
}
maxci = Math.max(maxci, ci)
}
return maxci
}
/**
* Returns indexes of articulation (cut) nodes.
* @returns {number[]} Indexes of articulation nodes
*/
articulations() {
return this.articulationsLowLink()
}
/**
* Returns indexes of articulation (cut) nodes with checking each node.
* @returns {number[]} Indexes of articulation nodes
*/
articulationsEachNodes() {
const n = this._nodes.length
const a = []
for (let i = 0; i < n; i++) {
const adj = this.adjacencies(i).filter(v => v !== i)
if (adj.length <= 1) {
continue
}
const checked = Array(n).fill(false)
checked[i] = true
const stack = [adj[0]]
while (stack.length > 0) {
const k = stack.pop()
if (checked[k]) {
continue
}
checked[k] = true
for (const e of this._edges) {
if (e[0] === k) {
stack.push(e[1])
}
if (e[1] === k) {
stack.push(e[0])
}
}
}
if (adj.some(k => !checked[k])) {
a.push(i)
}
}
return a
}
/**
* Returns indexes of articulation (cut) nodes with checking lowlinks.
* @returns {number[]} Indexes of articulation nodes
*/
articulationsLowLink() {
const n = this._nodes.length
if (n === 0) {
return []
}
const ord = Array(n).fill(-1)
const low = []
const parent = Array(n).fill(-1)
const alist = this.adjacencyList()
let d = 0
const a = []
const search = u => {
ord[u] = low[u] = d++
let cc = 0
let isArt = false
for (const v of alist[u]) {
if (ord[v] < 0) {
parent[v] = u
search(v)
cc++
if (low[v] >= ord[u]) {
isArt = true
}
low[u] = Math.min(low[u], low[v])
} else if (v !== parent[u]) {
low[u] = Math.min(low[u], ord[v])
}
}
if ((parent[u] >= 0 && isArt) || (parent[u] < 0 && cc > 1)) {
a.push(u)
}
}
search(0)
a.sort((a, b) => a - b)
return a
}
/**
* Returns edges of bridge.
* @returns {Edge[]} Bridge edges
*/
bridges() {
return this.bridgesLowLink()
}
/**
* Returns edges of bridge with checking lowlinks.
* @returns {Edge[]} Bridge edges
*/
bridgesLowLink() {
const n = this._nodes.length
if (n === 0 || this._edges.length === 0) {
return []
}
const ord = Array(n).fill(-1)
const low = []
const parent = Array(n).fill(-1)
const alist = this.adjacencyList()
let d = 0
const search = u => {
ord[u] = low[u] = d++
for (const v of alist[u]) {
if (ord[v] < 0) {
parent[v] = u
search(v)
low[u] = Math.min(low[u], low[v])
} else if (v !== parent[u]) {
low[u] = Math.min(low[u], ord[v])
}
}
}
search(0)
const cons = Array.from({ length: n }, () => Array(n).fill(0))
for (const e of this._edges) {
cons[e[0]][e[1]]++
cons[e[1]][e[0]]++
}
const b = []
for (const e of this._edges) {
if (cons[e[0]][e[1]] === 1 && (ord[e[0]] < low[e[1]] || ord[e[1]] < low[e[0]])) {
b.push(e)
}
}
return b
}
/**
* Add the node.
* @param {unknown} [value] Value of the node
*/
addNode(value) {
this._nodes[this._nodes.length] = value
}
/**
* Returns the node value.
* @overload
* @param {number} k Index of the node
* @returns {unknown} Node value
*/
/**
* Returns the node value.
* @overload
* @param {number[]} [k] Index of the node
* @returns {unknown[]} Node value
*/
/**
* @param {number | number[]} [k] Index of the node
* @returns {unknown | unknown[]} Node value
*/
getNode(k) {
if (k == null) {
return this._nodes
}
if (Array.isArray(k)) {
if (k.some(i => i < 0 || this._nodes.length <= i)) {
throw new GraphException('Index out of bounds.')
}
return k.map(i => this._nodes[i])
}
if (k < 0 || this._nodes.length <= k) {
throw new GraphException('Index out of bounds.')
}
return this._nodes[k]
}
/**
* Remove the node.
* @param {number} k Index of the node
*/
removeNode(k) {
if (k < 0 || this._nodes.length <= k) {
throw new GraphException('Index out of bounds.')
}
this._nodes.splice(k, 1)
for (let i = this._edges.length - 1; i >= 0; i--) {
const e = this._edges[i]
if (e[0] === k || e[1] === k) {
this._edges.splice(i, 1)
} else {
if (e[0] > k) {
e[0]--
}
if (e[1] > k) {
e[1]--
}
}
}
}
/**
* Remove all nodes.
*/
clearNodes() {
this._nodes = []
this._edges = []
}
/**
* Add the edge.
* @param {number} from Index of the starting node of the edge
* @param {number} to Index of the end node of the edge
* @param {unknown} [value] Value of the edge
* @param {boolean} [direct] `true` if the edge is direct
*/
addEdge(from, to, value = null, direct = false) {
if (from < 0 || this._nodes.length <= from || to < 0 || this._nodes.length <= to) {
throw new GraphException('Index out of bounds.')
}
this._edges.push(new Edge(from, to, value, direct))
}
/**
* Returns the edges.
* @overload
* @param {number} from Index of the starting node of the edge
* @param {number} to Index of the end node of the edge
* @param {boolean} [undirect] Get undirected edges or not
* @param {boolean | 'forward' | 'backward'} [direct] Get directed edges or not
* @returns {Edge[]} Edges between `from` and `to`
*/
/**
* Returns the edges.
* @overload
* @param {number} from Index of the starting node of the edge
* @param {number} to Index of the end node of the edge
* @param {'forward' | 'backward'} direct Get only directed edges
* @returns {Edge[]} Edges between `from` and `to`
*/
/**
* @param {number} from Index of the starting node of the edge
* @param {number} to Index of the end node of the edge
* @param {boolean | 'forward' | 'backward'} [undirect] Get undirected edges or not. If `forward` or `backward` is specified, only direct edges are get and `direct` parameter is ignored.
* @param {boolean | 'forward' | 'backward'} [direct] Get directed edges or not
* @returns {Edge[]} Edges between `from` and `to`
*/
getEdges(from, to, undirect = true, direct = true) {
if (from < 0 || this._nodes.length <= from || to < 0 || this._nodes.length <= to) {
throw new GraphException('Index out of bounds.')
}
if (undirect === 'forward' || undirect === 'backward') {
direct = undirect
undirect = false
}
const edges = []
for (const e of this._edges) {
if (
((undirect && !e.direct) || (direct === true && e.direct)) &&
((e[0] === from && e[1] === to) || (e[0] === to && e[1] === from))
) {
edges.push(e)
} else if (direct === 'forward' && e.direct && e[0] === from && e[1] === to) {
edges.push(e)
} else if (direct === 'backward' && e.direct && e[0] === to && e[1] === from) {
edges.push(e)
}
}
return edges
}
/**
* Remove the edges.
* @param {number} from Index of the starting node of the edge
* @param {number} to Index of the end node of the edge
* @param {boolean | null} [direct] `null` to remove direct and undirect edges, `true` to remove only direct edges, `false` to remove only undirect edges.
*/
removeEdges(from, to, direct = null) {
if (from < 0 || this._nodes.length <= from || to < 0 || this._nodes.length <= to) {
throw new GraphException('Index out of bounds.')
}
for (let i = this._edges.length - 1; i >= 0; i--) {
const e = this._edges[i]
if (direct === null || !direct === !e.direct) {
if ((e[0] === from && e[1] === to) || (e[1] === from && e[0] === to)) {
this._edges.splice(i, 1)
}
}
}
}
/**
* Remove all edges.
*/
clearEdges() {
this._edges = []
}
/**
* Returns adjacency matrix
* @returns {number[][]} Adjacency matrix
*/
adjacencyMatrix() {
const a = Array.from({ length: this._nodes.length }, () => Array(this._nodes.length).fill(0))
for (const e of this._edges) {
if (e.direct) {
a[e[0]][e[1]] += e.value
} else {
a[e[0]][e[1]] += e.value
a[e[1]][e[0]] += e.value
}
}
return a
}
/**
* Returns adjacency list
* @param {'both' | 'in' | 'out'} [direct] Indegree or outdegree
* @returns {number[][]} Adjacency list
*/
adjacencyList(direct = 'both') {
const n = this._nodes.length
const a = Array.from({ length: n }, () => new Set())
for (const e of this._edges) {
if (e[0] === e[1]) {
a[e[0]].add(e[1])
} else if (e.direct && direct !== 'both') {
if (direct === 'in') {
a[e[1]].add(e[0])
} else {
a[e[0]].add(e[1])
}
} else {
a[e[0]].add(e[1])
a[e[1]].add(e[0])
}
}
for (let i = 0; i < n; i++) {
a[i] = [...a[i]]
a[i].sort((a, b) => a - b)
}
return a
}
/**
* Returns degree matrix.
* @param {'both' | 'in' | 'out'} [direct] Indegree or outdegree
* @returns {number[][]} Degree matrix
*/
degreeMatrix(direct = 'both') {
const a = Array.from({ length: this._nodes.length }, () => Array(this._nodes.length).fill(0))
for (const e of this._edges) {
if (e.direct && direct !== 'both') {
if (direct === 'in') {
a[e[1]][e[1]] += e.value
} else {
a[e[0]][e[0]] += e.value
}
} else {
a[e[0]][e[0]] += e.value
a[e[1]][e[1]] += e.value
}
}
return a
}
/**
* Returns laplacian matrix.
* @returns {number[][]} Laplacian matrix
*/
laplacianMatrix() {
const amat = this.adjacencyMatrix()
const lm = []
for (let i = 0; i < this._nodes.length; i++) {
lm[i] = amat[i].map(v => -v)
for (let j = 0; j < this._nodes.length; j++) {
lm[i][i] += amat[i][j]
}
}
return lm
}
/**
* Returns if this is null graph or not.
* @returns {boolean} `true` if this is null graph
*/
isNull() {
return this._nodes.length === 0
}
/**
* Returns if this is edgeless graph or not.
* @returns {boolean} `true` if this is edgeless graph
*/
isEdgeless() {
return this._edges.length === 0
}
/**
* Returns if this is undirected graph or not.
* @returns {boolean} `true` if this is undirected graph
*/
isUndirected() {
return this._edges.every(e => !e.direct)
}
/**
* Returns if this is directed graph or not.
* @returns {boolean} `true` if this is directed graph
*/
isDirected() {
return this._edges.every(e => e.direct)
}
/**
* Returns if this is mixed graph or not.
* @returns {boolean} `true` if this is mixed graph
*/
isMixed() {
let hasDirect = false
let hasUndirect = false
for (const e of this._edges) {
if (e.direct) {
hasDirect = true
} else {
hasUndirect = true
}
if (hasDirect && hasUndirect) {
return true
}
}
return false
}
/**
* Returns if this is oriented graph or not.
* @returns {boolean} `true` if this is oriented graph
*/
isOriented() {
const n = this._nodes.length
const amat = Array.from({ length: n }, () => Array(n).fill(false))
for (const e of this._edges) {
if (!e.direct) {
return false
}
if (amat[e[0]][e[1]]) {
return false
}
amat[e[0]][e[1]] = amat[e[1]][e[0]] = true
}
return true
}
/**
* Returns if this is weighted graph or not.
* @returns {boolean} `true` if this is weighted graph
*/
isWeighted() {
return this._edges.some(e => e.weighted)
}
/**
* Returns if this is simple graph or not.
* @returns {boolean} `true` if this is simple graph
*/
isSimple() {
const alist = Array.from({ length: this._nodes.length }, () => [])
for (const e of this._edges) {
if (e[0] === e[1]) {
return false
}
if (alist[e[0]].includes(e[1]) || alist[e[1]].includes(e[0])) {
return false
}
alist[e[0]].push(e[1])
}
return true
}
/**
* Returns if this is connected graph or not.
* @returns {boolean} `true` if this is connected graph
*/
isConnected() {
const con = Array(this._nodes.length).fill(false)
const stack = [0]
while (stack.length > 0) {
const i = stack.pop()
if (con[i]) {
continue
}
con[i] = true
for (const e of this._edges) {
if (e[0] === i && !con[e[1]]) {
stack.push(e[1])
}
if (!e.direct && e[1] === i && !con[e[0]]) {
stack.push(e[0])
}
}
}
return con.every(v => v)
}
/**
* Returns if this is biconnected graph or not.
* @returns {boolean} `true` if this is biconnected graph
*/
isBiconnected() {
return this.articulations().length === 0
}
/**
* Returns if this is tree or not.
* @returns {boolean} `true` if this is tree
*/
isTree() {
return this.isConnected() && !this.hasCycle()
}
/**
* Returns if this is forest or not.
* @returns {boolean} `true` if this is forest
*/
isForest() {
return !this.hasCycle()
}
/**
* Returns if this is bipartite graph or not.
* @returns {boolean} `true` if this is bipartite graph
*/
isBipartite() {
const color = Array(this._nodes.length).fill(false)
const stack = [[0, 1]]
while (stack.length > 0 || color.some(v => !v)) {
const [i, c] = stack.length > 0 ? stack.pop() : [color.indexOf(false), 1]
if (color[i]) {
if (color[i] === c) {
continue
}
return false
}
color[i] = c
for (const e of this._edges) {
if (e[0] === i) {
if (color[e[1]] === c) {
return false
}
stack.push([e[1], (c % 2) + 1])
}
if (!e.direct && e[1] === i) {
if (color[e[0]] === c) {
return false
}
stack.push([e[0], (c % 2) + 1])
}
}
}
return true
}
/**
* Returns if this is complete graph or not.
* @returns {boolean} `true` if this is complete graph
*/
isComplete() {
for (let i = 0; i < this._nodes.length; i++) {
for (let j = 0; j < i; j++) {
if (this.getEdges(i, j).length === 0) {