diff --git a/dijkstra-prim-visualization/src/algorithms/spDijkstra.js b/dijkstra-prim-visualization/src/algorithms/dijkstra.js similarity index 69% rename from dijkstra-prim-visualization/src/algorithms/spDijkstra.js rename to dijkstra-prim-visualization/src/algorithms/dijkstra.js index 5cfb709..ddc1f7c 100644 --- a/dijkstra-prim-visualization/src/algorithms/spDijkstra.js +++ b/dijkstra-prim-visualization/src/algorithms/dijkstra.js @@ -1,15 +1,16 @@ -import { createGraphFromComponent, buildAdjacencyList } from "./graph.js"; +import { + buildAdjacencyList, + buildAdjacencyListFromComponent, +} from "./graph.js"; import createMinHeap from "./minHeap.js"; -import createLinkedList from "./linkedList.js"; -const dijkstra = (graph) => { - const adjacencyList = buildAdjacencyList(graph); +const dijkstra = (adjacencyList, createFringe) => { const nodesCount = adjacencyList.length; const startNode = 0; const finishNode = nodesCount - 1; - const fringe = new createLinkedList(nodesCount); + const fringe = new createFringe(nodesCount); const keys = new Array(nodesCount); const parents = new Array(nodesCount); @@ -39,11 +40,11 @@ const dijkstra = (graph) => { }); } - const sp = new Array(); + const steps = new Array(); let node = finishNode; while (node != startNode) { - sp.push({ + steps.push({ from: parents[node].key, to: node, weight: parents[node].weight, @@ -52,11 +53,24 @@ const dijkstra = (graph) => { } return { - steps: sp.reverse(), - spTotalWeight: keys[finishNode], + steps: steps, + total: keys[finishNode], + }; +}; + +const transformResult = (result) => { + return { + steps: result.steps.reverse(), + total: result.total, }; }; +const dijkstraWrapper = (nodes, edges) => { + const adjacencyList = buildAdjacencyListFromComponent(nodes, edges); + const result = dijkstra(adjacencyList, createMinHeap); + return transformResult(result); +}; + const displayDijkstraResult = (result) => { console.log("SP steps:"); @@ -87,11 +101,14 @@ const computeSp = () => { ], ); - const result = dijkstra(graph, 0, 5); + const adjacencyList = buildAdjacencyList(graph); + + const result = dijkstra(adjacencyList, createMinHeap); + const transformedResult = transformResult(result); - displayDijkstraResult(result); + displayDijkstraResult(transformedResult); console.log("------- SP DIJKSTRA END -------"); }; -export default dijkstra; +export { dijkstra, dijkstraWrapper }; diff --git a/dijkstra-prim-visualization/src/algorithms/graph.js b/dijkstra-prim-visualization/src/algorithms/graph.js index 6d9780c..5bc43dd 100644 --- a/dijkstra-prim-visualization/src/algorithms/graph.js +++ b/dijkstra-prim-visualization/src/algorithms/graph.js @@ -1,13 +1,21 @@ -const createGraphFromComponent = function (nodes, edges) { - const mappedNodes = nodes.map((node) => node.id); - const mappedEdges = edges.map((edge) => { - return [edge.firstNode.id, edge.secondNode.id, edge.weight]; - }); +// const createGraphFromComponent = function (nodes, edges) { +// const mappedNodes = nodes.map((node) => node.id); +// const mappedEdges = edges.map((edge) => { +// return [edge.firstNode.id, edge.secondNode.id, edge.weight]; +// }); + +// return new createGraph(mappedNodes, mappedEdges); +// }; - return new createGraph(mappedNodes, mappedEdges); +const createAdjacencyListEntry = (node, weight) => { + const entry = { + node: node, + weight: weight, + }; + return entry; }; -const createGraph = function (nodes, edges) { +const createGraph = (nodes, edges) => { this.nodes = nodes; this.edges = new Array(edges.length); @@ -24,14 +32,6 @@ const createGraph = function (nodes, edges) { const buildAdjacencyList = (graph) => { const adjacencyList = new Array(graph.nodes.length).fill(null); - const createAdjacencyListEntry = (node, weight) => { - const entry = { - node: node, - weight: weight, - }; - return entry; - }; - graph.edges.forEach((e) => { if (!Array.isArray(adjacencyList[e.from])) { adjacencyList[e.from] = new Array(); @@ -47,4 +47,35 @@ const buildAdjacencyList = (graph) => { return adjacencyList; }; -export { createGraphFromComponent, buildAdjacencyList }; +const buildAdjacencyListFromComponent = (nodes, edges) => { + const adjacencyList = new Array(nodes.length).fill(null); + + edges + .map((e) => { + return { + from: e.firstNode.id, + to: e.secondNode.id, + weight: e.weight, + }; + }) + .forEach((e) => { + if (!Array.isArray(adjacencyList[e.from])) { + adjacencyList[e.from] = new Array(); + } + adjacencyList[e.from].push(createAdjacencyListEntry(e.to, e.weight)); + + if (!Array.isArray(adjacencyList[e.to])) { + adjacencyList[e.to] = new Array(); + } + adjacencyList[e.to].push(createAdjacencyListEntry(e.from, e.weight)); + }); + + return adjacencyList; +}; + +export { + createAdjacencyListEntry, + createGraph, + buildAdjacencyList, + buildAdjacencyListFromComponent, +}; diff --git a/dijkstra-prim-visualization/src/algorithms/mstPrim.js b/dijkstra-prim-visualization/src/algorithms/prim.js similarity index 55% rename from dijkstra-prim-visualization/src/algorithms/mstPrim.js rename to dijkstra-prim-visualization/src/algorithms/prim.js index 9c54294..fe269db 100644 --- a/dijkstra-prim-visualization/src/algorithms/mstPrim.js +++ b/dijkstra-prim-visualization/src/algorithms/prim.js @@ -1,43 +1,43 @@ -import { createGraphFromComponent, buildAdjacencyList } from "./graph"; -import createMinHeap from "./minHeap"; -import createLinkedList from "./linkedList"; - -const prim = (graph) => { - const adjacencyList = buildAdjacencyList(graph); +import { + buildAdjacencyList, + buildAdjacencyListFromComponent, +} from "./graph.js"; +import createMinHeap from "./minHeap.js"; +const prim = (adjacencyList, createFringe) => { const nodesCount = adjacencyList.length; - const fringe = new createLinkedList(nodesCount); - const isInHeap = new Array(nodesCount); - const results = new Array(nodesCount); + const fringe = new createFringe(nodesCount); + const isInFringe = new Array(nodesCount); + const steps = new Array(nodesCount); const keys = new Array(nodesCount); // Insert node 0 with value 0 fringe.insert(0, 0); - isInHeap[0] = true; + isInFringe[0] = true; keys[0] = Infinity; - results[0] = { + steps[0] = { parent: -1, weight: null, }; for (let index = 1; index < nodesCount; index++) { - isInHeap[index] = true; + isInFringe[index] = true; keys[index] = Infinity; fringe.insert(index, Infinity); } while (!fringe.isEmpty()) { const extractedNode = fringe.extractMin(); - isInHeap[extractedNode.key] = false; + isInFringe[extractedNode.key] = false; const neightbours = adjacencyList[extractedNode.key]; neightbours.forEach((n) => { - if (isInHeap[n.node]) { + if (isInFringe[n.node]) { if (keys[n.node] > n.weight) { fringe.decreaseKey(n.node, n.weight); keys[n.node] = n.weight; - results[n.node] = { + steps[n.node] = { from: extractedNode.key, to: n.node, weight: n.weight, @@ -47,24 +47,35 @@ const prim = (graph) => { }); } - results.sort((a, b) => a.weight - b.weight); + return steps; +}; - let mstTotal = 0; +const transformSteps = (steps) => { + steps.shift(1); + steps.sort((a, b) => a.weight - b.weight); - for (let index = 1; index < nodesCount; index++) { - mstTotal += results[index].weight; + let total = 0; + for (let i = 0; i < steps.length; i++) { + total += steps[i].weight; } return { - steps: results, - mstTotalWeight: mstTotal, + steps: steps, + total: total, }; }; +const primWrapper = (nodes, edges) => { + const adjacencyList = buildAdjacencyListFromComponent(nodes, edges); + const steps = prim(adjacencyList, createMinHeap); + + return transformSteps(steps); +}; + const displayPrimResult = (result) => { console.log("MST steps:"); - for (let index = 1; index < result.steps.length; index++) { + for (let index = 0; index < result.steps.length; index++) { console.log( `From ${result.steps[index].from} to ${result.steps[index].to} with weight ${result.steps[index].weight}`, ); @@ -89,11 +100,14 @@ const computeMst = () => { ], ); - const result = prim(graph); + const adjacencyList = buildAdjacencyList(graph); + + const steps = prim(adjacencyList); + const transformedSteps = transformSteps(steps); - displayPrimResult(result); + displayPrimResult(transformedSteps); console.log("------- MST PRIM END -------"); }; -export default prim; +export { prim, primWrapper }; diff --git a/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx b/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx index b6e26c1..4de23c0 100644 --- a/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx +++ b/dijkstra-prim-visualization/src/components/Navbar/Navbar.jsx @@ -1,8 +1,7 @@ import { useContext } from "react"; import { GraphParamsContext } from "../../GraphParamsContext"; -import { createGraphFromComponent } from "../../algorithms/graph"; -import prim from "../../algorithms/mstPrim"; -import dijkstra from "../../algorithms/spDijkstra"; +import { primWrapper } from "../../algorithms/prim"; +import { dijkstraWrapper } from "../../algorithms/dijkstra"; /** * Navbar component displays buttons to print nodes and edges. @@ -12,14 +11,12 @@ const Navbar = () => { const { nodes, edges } = useContext(GraphParamsContext); const runPrim = () => { - const graph = createGraphFromComponent(nodes, edges); - const result = prim(graph); + const result = primWrapper(nodes, edges); console.log(result); }; const runDijkstra = () => { - const graph = createGraphFromComponent(nodes, edges); - const result = dijkstra(graph); + const result = dijkstraWrapper(nodes, edges); console.log(result); }; diff --git a/dijkstra-prim-visualization/src/measurement/dataset.js b/dijkstra-prim-visualization/src/measurement/dataset.js new file mode 100644 index 0000000..8a55155 --- /dev/null +++ b/dijkstra-prim-visualization/src/measurement/dataset.js @@ -0,0 +1,100 @@ +import fs from "fs"; +import path from "path"; +import { createAdjacencyListEntry } from "../algorithms/graph.js"; + +const directory = "datasets"; + +const generateRandomNumber = (a, b) => Math.floor(Math.random() * (b - a) + a); + +const generateAdjacencyList = ( + nodesCount, + probability, + minWeight, + maxWeight, +) => { + const adjacencyList = new Array(nodesCount); + + for (let i = 0; i < nodesCount; i++) { + adjacencyList[i] = new Array(); + } + + for (let i = 0; i < nodesCount; i++) { + for (let j = i + 1; j < nodesCount; j++) { + if (Math.random() < probability) { + const weight = generateRandomNumber(minWeight, maxWeight); + adjacencyList[i].push(createAdjacencyListEntry(j, weight)); + adjacencyList[j].push(createAdjacencyListEntry(i, weight)); + } + } + } + + return adjacencyList; +}; + +const ensureDirectoryExists = () => { + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } +}; + +const generateFilePath = (nodesCount) => + path.join(directory, `al_${nodesCount}.txt`); + +const saveAdjacencyList = (dataset) => { + ensureDirectoryExists(); + const writer = fs.createWriteStream(generateFilePath(dataset.length), { + flags: "w", + }); + + writer.write(`${dataset.length}\n`); + + dataset.forEach((row) => { + row.forEach((element) => { + const chunk = `${element.node},${element.weight} `; + writer.write(chunk); + }); + writer.write("\n"); + }); + + writer.close(); +}; + +const readAdjacencyList = (nodesCount) => { + ensureDirectoryExists(); + const rawDataset = fs.readFileSync(generateFilePath(nodesCount), "utf-8"); + + const lines = rawDataset.trim().split("\n"); + + const adjacencyList = new Array(nodesCount); + + for (let i = 1; i < lines.length; i++) { + const row = lines[i] + .trim() + .split(" ") + .map((chunk) => { + const [node, weight] = chunk.split(","); + return createAdjacencyListEntry( + Number.parseInt(node), + Number.parseInt(weight), + ); + }); + adjacencyList[i - 1] = row; + } + + return adjacencyList; +}; + +const getDatasets = () => { + ensureDirectoryExists(); + const datasets = fs.readdirSync(directory); + return datasets + .map((x) => Number.parseInt(x.replace(/^al_/, "").replace(/.txt$/))) + .sort((a, b) => a - b); +}; + +export { + generateAdjacencyList, + saveAdjacencyList, + readAdjacencyList, + getDatasets, +}; diff --git a/dijkstra-prim-visualization/src/measurement/measure.js b/dijkstra-prim-visualization/src/measurement/measure.js new file mode 100644 index 0000000..6d6bf78 --- /dev/null +++ b/dijkstra-prim-visualization/src/measurement/measure.js @@ -0,0 +1,115 @@ +import fs from "fs"; +import path from "path"; +import { generateAdjacencyList } from "./dataset.js"; +import { prim } from "../algorithms/prim.js"; +import createMinHeap from "../algorithms/minHeap.js"; +import createLinkedList from "../algorithms/linkedList.js"; + +const args = process.argv.slice(2); + +const config = { + algorithm: "prim", + startNodeCount: Number.parseInt(args[0] ?? 1000), + finishNodeCount: Number.parseInt(args[1] ?? 5000), + stepNodeCount: Number.parseInt(args[2] ?? 100), + edgeProbability: 1, + minEdgeWeight: 1, + maxEdgeWeght: 15, + measurementSteps: 10, +}; + +console.log(config); + +const measureTime = (func) => { + const before = performance.now(); + func(); + const after = performance.now(); + return after - before; +}; + +const measureAverageTime = (func) => { + const deltas = []; + + for ( + let measurementStep = 0; + measurementStep < config.measurementSteps; + measurementStep++ + ) { + const delta = measureTime(func); + deltas.push(delta); + } + + return Math.floor(deltas.reduce((a, b) => a + b, 0) / deltas.length); +}; + +const saveResults = (results, fringe) => { + const directory = "results"; + + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + + const randomTag = (Math.random() + 1).toString(36).substring(7); + const filename = `${config.algorithm}_${fringe}_${config.startNodeCount}_${config.finishNodeCount}_${config.stepNodeCount}_${config.edgeProbability}_${config.minEdgeWeight}_${config.maxEdgeWeght}_${randomTag}.txt`; + + const writer = fs.createWriteStream(path.join(directory, filename), { + flags: "w", + }); + + results.forEach((x) => { + writer.write(`${x.nodesCount} ${x.delta}\n`); + }); + + writer.close(); + + return filename; +}; + +const measure = () => { + console.log("Started measurement"); + + const resultsMinHeap = []; + const resultsLinkedList = []; + + for ( + let nodesCount = config.startNodeCount; + nodesCount <= config.finishNodeCount; + nodesCount += config.stepNodeCount + ) { + const adjacencyList = generateAdjacencyList( + nodesCount, + config.edgeProbability, + config.minEdgeWeight, + config.maxEdgeWeght, + ); + + const deltaMinHeap = measureAverageTime(() => + prim(adjacencyList, createMinHeap), + ); + const deltaLinkedList = measureAverageTime(() => + prim(adjacencyList, createLinkedList), + ); + + resultsMinHeap.push({ + nodesCount: nodesCount, + delta: deltaMinHeap, + }); + resultsLinkedList.push({ + nodesCount: nodesCount, + delta: deltaLinkedList, + }); + + console.log( + `Result (min heap): nodesCount = ${nodesCount}, delta = ${deltaMinHeap} ms`, + ); + console.log( + `Result (linked list): nodesCount = ${nodesCount}, delta = ${deltaLinkedList} ms`, + ); + } + + const filenameMinHeap = saveResults(resultsMinHeap, "minheap"); + const filenameLinkedList = saveResults(resultsLinkedList, "linkedlist"); + console.log(`Saved result to ${filenameMinHeap} and ${filenameLinkedList}`); +}; + +measure(); diff --git a/dijkstra-prim-visualization/src/measurement/prepare.js b/dijkstra-prim-visualization/src/measurement/prepare.js new file mode 100644 index 0000000..5e3a6ad --- /dev/null +++ b/dijkstra-prim-visualization/src/measurement/prepare.js @@ -0,0 +1,31 @@ +import { generateAdjacencyList, saveAdjacencyList } from "./dataset.js"; + +const args = process.argv.slice(2); + +console.log(args); + +const startNodeCount = Number.parseInt(args[0] ?? 1000); +const finishNodeCount = Number.parseInt(args[1] ?? 5000); +const stepNodeCount = Number.parseInt(args[2] ?? 100); + +const edgeProbability = 0.5; + +const minEdgeWeight = 1; +const maxEdgeWeght = 15; + +for ( + let nodesCount = startNodeCount; + nodesCount <= finishNodeCount; + nodesCount += stepNodeCount +) { + const dataset = generateAdjacencyList( + nodesCount, + edgeProbability, + minEdgeWeight, + maxEdgeWeght + ); + saveAdjacencyList(dataset); + console.log( + `Prepared an adjacency list for ${nodesCount} nodes with edge probability of ${edgeProbability}` + ); +}