I am writing a university project, where I take two algorithms and compare their performance, the main algorithms are Dijkstra and A*, however I am not very experienced with either Vue.js, JS in general and algorithms, and my first iteration of Dijkstra is SUPER slow. I am using a 2D grid of X by Y, with a start point node and an end point node, and running the algorithm. Any grid above 10x10 is completely too slow.
I am at a loss on what to do, I don't see any errors when running the code, it executes, but executes entirely too slow. I am not very experienced, so I don't know where to start to look and what the issue could be. Maybe there's someone with experience that could help out and tell me what I'm doing wrong?
export default ({
name: 'Home',
data() { // global variables init
return {
grid: [], // initializing grid array, rows and columns
todis: "", // to display
output: [], // shortest path array
toCheck: [], // checking neighboring nodes
startx: Number(0), // starting x position of algorithm
starty: Number(0), // starting y position of algorithm
endx: Number(0), // end x position of algorithm
endy: Number(0), // end y position of algorithm
sxy: [0, 0], // start x, y array coordinates
exy: [0, 0], // end x, y array coordinates
wallx: Number(0), // wall x coordinate
wally: Number(0) // wall y coordinate
};
},
methods: { // all needed methods
addToGrid(adding) { // create the grid
this.grid.push(adding)
},
setwall(x, y) {
this.grid[Number(y)][Number(x)].id = 1
},
updates(x, y) { // update start position on button press
this.sxy = [Number(x), Number(y)]
},
updatee(x, y) { // update end position on button press
this.exy = [Number(x), Number(y)]
},
makeGrid(width, height) { // takes two values to make a grid
function Node(x, y, id) { // 0 - unchecked; 1 - Wall; 2 - Start; 3 - End; 4 - Checked; 5 - Shortest Path
this.x = x // initialization of values for class Node
this.y = y
this.id = id
this.visited = false
this.prev = []
}
if (this.grid.length === 0) { // Stops grid being made multiple times
for (let y = 0; y < height; y++) {
let row = [] // Create a row, need new empty array every time we go through a row
for (let x = 0; x < width; x++) { // Create nodes for the width of the grid
var newNode = new Node(x, y, 0) // give values x, y, id to the node
row.push(newNode) // push a node to the row array
}
this.addToGrid(row) // we add row array to grid
}
}
},
Dijkstra(start, end) {
this.grid[start[1]][start[0]].id = 2 // node array coordinates (w, h we got from inputs from the front end) width height values set to id of 2, which is the start of the grid
this.grid[end[1]][end[0]].id = 3 // node array coordinates (w, h) for end point, which has an id of 3
function findNeighbours(grid, x, y) { // scanning the neighboring nodes, takes the whole grid
let Neighbours = [] // initializing values for neighbours
let PosNeighbours = [] // position of new neighbours
let cnode = null // checked node
grid[y][x].visited = true
if (grid[y][x].id === 0) { // if the node at y, x id is 0 (unchecked), set it to checked (for colouring)
grid[y][x].id = 4
}
try { // we go to the right of of the node
cnode = grid[y][x + 1] // x + 1 and if the node:
if (typeof cnode !== "undefined") { // is valid
if (cnode.id !== 1) { // is not a wall
PosNeighbours.push(grid[y][x + 1]) // we add set the neighbour position to that value
}
}
}
catch{
null
}
try { // we go to the left of the node
cnode = grid[y][x - 1] // x - 1 and if the node:
if (typeof cnode !== "undefined") { // is valid
if (cnode.id !== 1) { // is not a wall
PosNeighbours.push(grid[y][x - 1]) // we add set the neighbour position to that value
}
}
}
catch{
null
}
try { // we go to the top of the node
cnode = grid[y + 1][x] // y + 1 and if the node:
if (typeof cnode !== "undefined") { // is valid
if (cnode.id !== 1) { // is not a wall
PosNeighbours.push(grid[y + 1][x]) // we add set the neighbour position to that value
}
}
}
catch{
null
}
try { // we go to the bottom of the node
cnode = grid[y - 1][x] // y - 1 and if the node:
if (typeof cnode !== "undefined") { // is valid
if (cnode.id !== 1) { // is not a wall
PosNeighbours.push(grid[y - 1][x]) // we add set the neighbour position to that value
}
}
}
catch{
null
}
for (let node of PosNeighbours) { // for each neighboring node
if (typeof grid[node.y][node.x] === 'undefined') {
null // if the node is not valid, do nothing
}
else {
if (node.visited === false) { // if the node is not visited, we add the value to the neighbours array
Neighbours.push(node) // we set the new x and new y value to our current node position
let nx = node.x
let ny = node.y
if (grid[ny][nx].prev !== []) { // if the new grid position has no previous value
grid[ny][nx].prev = grid[y][x] // we set the previous value to the old x and y positions
}
}
}
}
return [Neighbours, grid] // ??? return an array of nodes visited and whole grid ???
}
if (start !== end) { // we start the algorithm if the start node is not the same as the end node
let startNode = this.grid[start[1]][start[0]] // set the start node, the checking array, give it a start node and declare what to check next
this.toCheck = []
this.toCheck.push(startNode)
let toChecknext = []
while (this.grid[end[1]][end[0]].visited === false) {
if (this.toCheck.length !== 0) { // while we haven't visited the end node, or if we haven't checked all the nodes
let node = this.toCheck.shift() // we set the node to the first value of our toCheck array
let fn = findNeighbours(this.grid, node.x, node.y)
let nodestoadd = fn[0] // we call the findNeighbours function and set the nodes to add to Neighbours
this.grid = fn[1] // grid generated with grid values of the the function
for (let node of nodestoadd) { // for each node in the nodestoadd array we push the node valeu to the next node to check
toChecknext.push(node)
}
} else { // otherwise, set the next node to check to the node we are currently at
for (let node in toChecknext) {
this.toCheck.push(node)
}
}
}
let currentNode = this.grid[end[1]][end[0]] // we set up our currentNode end, if our current node x, y are not the same
let pathnodes = []
while (currentNode.x !== startNode.x || currentNode.y !== startNode.y) {
pathnodes.push(currentNode.prev) // we create a path of the previous nodes, using a pathnodes array
currentNode = currentNode.prev
}
let pathnodexy = []
for (let node of pathnodes) { // for each node in the pathnodes array (all the nodes for our shortest path)
pathnodexy.push([node.x, node.y]) // we give the pathnodexy array x, y values of the nodes
if (this.grid[node.y][node.x].id == 4) { // if that node has been visited with id 4, we set that to id 5, which is the id of the path
this.grid[node.y][node.x].id = 5
}
}
this.output = pathnodexy // we set the output value and start the algo
} else {
return [start]
}
},
displayGrid(grid) { // we create a string method, that creates a grid with the ID values we defined
this.todis = "<div id='block_container'>"
for(const row of grid) { // for each row, and each node in that row we set the ID value of the node and generate html to create our grid
for (const node of row) {
this.todis += "<button @click.self='changewall' id='block-id-"+node.id+"'></button>"
}
this.todis += "</div><div id='block_container'>"
}
this.todis = this.todis+"</div>"
return this.todis // we return the constructed html
},
changeGrid(thisx, thisy) {
this.grid[thisy][thisx].id = 1
}
},
created: function () {
this.makeGrid(7, 7)
},
updated: function () {
// on update
}
});
Although you seem to suggest that your algorithm works, but too slow, it actually has at least one blocking error:
for (let node in toChecknext)
: this will iterate the indexes of the array, not the values. Yet you treat those values as nodes, which will lead to undesired behaviour. Change to:
for (let node of toChecknext)
There are other, non-breaking issues as well:
if (grid[ny][nx].prev !== [])
will always be true, because a newly created array can never be the same object as an already existing object. I gues you first tried with === []
, then found it was always false
, and then just toggled it. Instead initialise prev
as null
and just check whether prev
is a truthy value. Also just reference node
instead of grid[ny][nx]
, which really is the same object (see more about that in another bullet point further down this list):
this.prev = null; // in Node constructor /*... */ if (node.prev)
toChecknext
is never cleared. It should, otherwise the algorithm will be doing unnecessary work. Whenever you have copied the contents of toChecknext
to this.toCheck
, you should empty toChecknext
so that you will not be verifying nodes that are already visited. So after the copy, add:
toChecknext.length = 0;
There is no provision for the case where there is no path. The algorithm will get into an infinite loop. On the condition you did the previous correction, also add this block before the else
:
} else if (toChecknext.length == 0) { this.output = []; return []; // No solution!
The path pathnodes
lacks the end node. So initialise it with:
let pathnodes = [currentNode];
The path pathnodexy
actually is reversed, as it lists coordinates from the end to the beginning. So do this:
this.output = pathnodexy.reverse();
In the case the start node is the end node, you return
the path, but in all other cases you do not return it, but set a property. This is inconsistent. So in the else
case do this:
this.output = [start];
and outside of the if..else
block do:
return this.output;
It is overkill to do things like this.grid[node.y][node.x].id
, which really is node.id
. You have this happening at several places in your code. Another example is currentNode.x.== startNode.x || currentNode.y !== startNode.y
currentNode.x.== startNode.x || currentNode.y !== startNode.y
, which can be just currentNode != startNode
. There are other cases, which I will not list all...
It is of no use to pass this.grid
to findNeighbours
and then have findNeighbours
return it. Any mutation you did on that grid
argument will be directly on this.grid
. grid
is a reference to the same as this.grid
. So just remove that parameter, and have the function work with this.grid
instead of grid
, and just return the neighbour list.
Don't use try {... } catch { null }
, as you risk to miss out on errors you did not expect.
There is much more to improve in this code, but with these adjustments it should run in a reasonable time.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.