The misleading A*(A-star) algorithm inaccurately produces faulty routes and ultimately collapses

I am currently working on implementing the A*(A-star) algorithm in react.js, but I am facing a problem. Whenever the startNode (green) or destinationNode (blue) have more than one neighbour or if there is a cycle in the graph, my program crashes. There seems to be an issue with adding and deleting neighbours from/to the openList or when updating the parentId in the getPath() function. Unfortunately, I cannot even see the console output because the website goes down. Each node has the following properties: id, name, x, y, connectedToIdsList:[], gcost:Infinity, fcost:0, heuristic:0, parentId:null. I am sending the path to another component called "TodoList" which then prints out the path. However, instead of returning the path, my program keeps updating the path as I add nodes and edges to the list. Any help would be greatly appreciated, as I have been stuck on this for hours:/

Here is my code:

export default class TurnByTurnComponent extends React.PureComponent {
    constructor(props) {
        super(props);
        this.state = { shortestPath: [] }
    }

    render() {
        const {
            destinationLocationId,
            locations,
            originLocationId
        } = this.props;
        let path = [];

        if (destinationLocationId != null && originLocationId != null) {

            if (originLocationId == destinationLocationId) { //check if the startNode node is the end node
                return originLocationId;
            }

            var openList = [];
            let startNode = getNodeById(originLocationId);
            let destinationNode = getNodeById(destinationLocationId)
            if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) { //check if start and destination nodes are connected first

                startNode.gcost = 0;
                startNode.heuristic = manhattanDistance(startNode, destinationNode);
                startNode.fcost = startNode.gcost + startNode.heuristic;

                //perform A*
                openList.push(startNode); //starting with the startNode 
                while (openList.length > 0) {
                    console.log("inside while")
                    var currentNode = getNodeOfMinFscore(openList); //get the node of the minimum f cost of all nodes in the openList
                    if (currentIsEqualDistanation(currentNode)) {
                        path = getPath(currentNode);
                    }
                        deleteCurrentFromOpenList(currentNode, openList);
                    for (let neighbourId of currentNode.connectedToIds) { 
                        var neighbourNode = getNodeById(neighbourId);
                        currentNode.gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
                        if (currentNode.gcost < neighbourNode.gcost) {
                            neighbourNode.parentId = currentNode.id; // keep track of the path
                            // total cost saved in neighbour.g
                            neighbourNode.gcost = currentNode.gcost;
                            neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
                            neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic; //calculate f cost of the neighbourNode
                            addNeighbourNodeToOpenList(neighbourNode, openList);
                        }
                    }

                }
                path = path.reverse().join("->");
            }
        }

        function addNeighbourNodeToOpenList(neighbourNode, openList) {
            //add neighbourNode to the open list to be discovered later
            if (!openList.includes(neighbourNode)) {
                openList.push(neighbourNode);
            }
        }


        function deleteCurrentFromOpenList(currNode, openList) {
            const currIndex = openList.indexOf(currNode);
            openList.splice(currIndex, 1); //deleting currentNode from openList
        }

        function currentIsEqualDistanation(currNode) {
            //check if we reached the destination node
            return (currNode.id == destinationLocationId)
        }

        function getNodeById(nid) {
            var node;
            for (let i = 0; i < locations.length; i++) {
                if (locations[i].id == nid) {
                    node = locations[i]
                }
            }
            return node
        }

       function getPath(destNode) {
        console.log("inside getpath")
        var parentPath = []
        var parent;
        while (destNode.parentId != null) {
            parentPath.push(destNode.name)
            parent = destNode.parentId;
            destNode = getNodeById(parent);
        }
        //adding startNode to the path
        parentPath.push(getNodeById(originLocationId).name)
        return parentPath;
    }

        function getNodeOfMinFscore(openList) {
            var minFscore = openList[0].fcost; //initValue
            var nodeOfminFscore;
            for (let i = 0; i < openList.length; i++) {
                if (openList[i].fcost <= minFscore) {
                    minFscore = openList[i].fcost //minFvalue
                    nodeOfminFscore = openList[i]
                }
            }

            return nodeOfminFscore
        }

        //manhattan distance is used for heuristic and gScore. Here I am using Manhattan distance instead of Euclidean 
        //because in this example we don't have diagonal paths.
        function manhattanDistance(stNode, dstNode) {
            var x = Math.abs(dstNode.x - stNode.x);
            var y = Math.abs(dstNode.y - stNode.y);
            var dist = x + y;
            return dist;
        }


        return (
            <div className="turn-by-turn-component">
                <TodoList
                    list={"Shortest path: ", [path]}
                />
                <TodoList                   
                    list={[]}
                />
            </div>
        );
    }
}

TurnByTurnComponent.propTypes = {
    destinationLocationId: PropTypes.number,
    locations: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        name: PropTypes.string.isRequired,
        x: PropTypes.number.isRequired,
        y: PropTypes.number.isRequired,
        connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
    })),
    originLocationId: PropTypes.number
};

Update on new issue:

Pictures of before and after linking a new node. When I update the graph and add a new node, the path disappears. Sometimes it comes back, sometimes not, especially when I add new nodes and edges to the graph. As I mentioned earlier, each node has the following properties: id, name, x, y, connectedToIdsList:[], gcost:Infinity, fcost:0, heuristic:0, parentId:null.

https://i.stack.imgur.com/nqNEB.png

https://i.stack.imgur.com/FnrEx.png

export default class TurnByTurnComponent extends React.PureComponent {
  constructor(props) {
        super(props);
        this.state = { shortestPath: [] };
    }

    render() {
        const {
            destinationLocationId,
            locations,
            originLocationId
        } = this.props;
        let path = []

        if (destinationLocationId != null && originLocationId != null) {
            console.log(JSON.stringify(locations))
            path = [getNodeById(originLocationId).name];
            if (originLocationId != destinationLocationId) {
                var openList = [];
                let startNode = getNodeById(originLocationId);
                let destinationNode = getNodeById(destinationLocationId)
                if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) {
                    startNode.gcost = 0
                    startNode.heuristic = manhattanDistance(startNode, destinationNode)
                    startNode.fcost = startNode.gcost + startNode.heuristic;

                    openList.push(startNode); //starting with the startNode 
                    while (openList.length > 0) {
                        var currentNode = getNodeOfMinFscore(openList); //get the node of the minimum f cost of all nodes in the openList
                        if (currentIsEqualDistanation(currentNode)) {
                            path = getPath(currentNode);
                            break;
                        }
                        deleteCurrentFromOpenList(currentNode, openList);
                        for (let neighbourId of currentNode.connectedToIds) {
                            var neighbourNode = getNodeById(neighbourId);
                            let gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
                            if (gcost < (neighbourNode.gcost ?? Infinity)) {
                                neighbourNode.parentId = currentNode.id;
                                // keep track of the path
                                // total cost saved in neighbour.g
                                neighbourNode.gcost = gcost;
                                neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
                                neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic; //calculate f cost of the neighbourNode
                                addNeighbourNodeToOpenList(neighbourNode, openList);
                            }
                        }
                    }
                }

            }
        }
        path = path.reverse().join("->");

        function addNeighbourNodeToOpenList(neighbourNode, openList) {
            //add neighbourNode to the open list to be discovered later
            if (!openList.includes(neighbourNode)) {
                openList.push(neighbourNode);
            }
        }


        function deleteCurrentFromOpenList(currentNode, openList) {
            const currIndex = openList.indexOf(currentNode);
            openList.splice(currIndex, 1); //deleting currentNode from openList
        }

        function currentIsEqualDistanation(currentNode) {
            //check if we reached out the distanation node
            return (currentNode.id == destinationLocationId)
        }

        function getNodeById(id) {
            var node;
            for (let i = 0; i < locations.length; i++) {
                if (locations[i].id == id) {
                    node = locations[i]
                }
            }
            return node
        }

        function getPath(destinationNode) {
            console.log("inside getpath")
            var parentPath = []
            var parent;
            while (destinationNode.parentId != null) {
                parentPath.push(destinationNode.name)
                parent = destinationNode.parentId;
                destinationNode = getNodeById(parent);
            }
            //adding startNode to the path
            parentPath.push(getNodeById(originLocationId).name)
            return parentPath;
        }

       function getNodeOfMinFscore(openList) {
            var minFscore = openList[0].fcost; //initValue
            var nodeOfminFscore;
            for (let i = 0; i < openList.length; i++) {
                if (openList[i].fcost <= minFscore) {
                    minFscore = openList[i].fcost //minFvalue
                    nodeOfminFscore = openList[i]
                }
            }

            return nodeOfminFscore
        }

        function manhattanDistance(startNode, destinationNode) {
            var x = Math.abs(destinationNode.x - startNode.x);
            var y = Math.abs(destinationNode.y - startNode.y);
            var dist = x + y;
            return dist;
        }


        return (

            <div className="turn-by-turn-component">

                <TodoList
                    title="Mandatory work"
                    list={[path]}
                />
                <TodoList
                    title="Optional work"
                    list={[]}
                />
            </div>
        );
    }
}

TurnByTurnComponent.propTypes = {
    destinationLocationId: PropTypes.number,
    locations: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.number.isRequired,
        name: PropTypes.string.isRequired,
        x: PropTypes.number.isRequired,
        y: PropTypes.number.isRequired,
        connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
    })),
    originLocationId: PropTypes.number
};

Answer №1

There are a few issues in your code that need to be addressed:

  • The line return originLocationId; should not be included. If the source and target are equal, you should set the path accordingly and ensure that the final return statement of this function executes, as it is intended to return the div element.

  • When the target node is found within the loop, not only should the path be constructed, but the loop should also be terminated. To accomplish this, add a break statement inside the loop.

  • The line

    currentNode.gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
    is incorrect. Modifying the value of currentNode.gcost based on its outgoing edge to the neighbor node is undesirable. Instead, assign the sum of these values to a temporary variable, such as gcost.

  • The comparison involving neighbourNode.gcost will not work when the neighbor node does not yet possess a gcost attribute. Based on your code, it seems like there is no default value assigned to guarantee that this condition evaluates to true. Therefore, consider using a default value, for example,

    (neighbourNode.gcost ?? Infinity)
    .

  • The line

    path = path.reverse().join("->");
    would ideally be executed under all circumstances, including situations where no solution exists (i.e., the path is an empty string) or when the source and target nodes are identical.

Here is a revised version of your code, slightly modified to function correctly as a runnable snippet:

function render() {
    const {
        destinationLocationId,
        locations,
        originLocationId
    } = this.props;
    let path = [];
    
    if (destinationLocationId !== null && originLocationId !== null) {
        path = [originLocationId]; 
        if (originLocationId !== destinationLocationId) {
            var openList = [];
            let startNode = getNodeById(originLocationId);
            let destinationNode = getNodeById(destinationLocationId)
            if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) {

                startNode.gcost = 0
                startNode.heuristic = manhattanDistance(startNode, destinationNode)
                startNode.fcost = startNode.gcost + startNode.heuristic;
                
                openList.push(startNode);
                while (openList.length > 0) {
                    var currentNode = getNodeOfMinFscore(openList);
                    if (currentIsEqualDistanation(currentNode)) {
                        path = getPath(currentNode);
                        break; 
                    }
                    deleteCurrentFromOpenList(currentNode, openList);
                    for (let neighbourId of currentNode.connectedToIds) { 
                        var neighbourNode = getNodeById(neighbourId);
                        let gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
                        if (gcost < (neighbourNode.gcost ?? Infinity)) {
                            neighbourNode.parentId = currentNode.id;
                            neighbourNode.gcost = gcost; 
                            neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
                            neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic;
                            addNeighbourNodeToOpenList(neighbourNode, openList);
                        }
                    }
                }
            }
        }
    }
    path = path.reverse().join("->");

    function addNeighbourNodeToOpenList(neighbourNode, openList) {
        if (!openList.includes(neighbourNode)) {
            openList.push(neighbourNode);
        }
    }

    function deleteCurrentFromOpenList(currNode, openList) {
        const currIndex = openList.indexOf(currNode);
        openList.splice(currIndex, 1);
    }

    function currentIsEqualDistanation(currNode) {
        return (currNode.id == destinationLocationId)
    }

    function getNodeById(nid) {
        var node;
        for (let i = 0; i < locations.length; i++) {
            if (locations[i].id === nid) {
                node = locations[i]
            }
        }
        return node
    }

    function getPath(destNode) {
        var parentPath = []
        var parentId;
        while (destNode.parentId !== null) {
            parentPath.push(destNode.name)
            parentId = destNode.parentId;
            destNode = getNodeById(parentId);
        }
        parentPath.push(getNodeById(originLocationId).name)
        return parentPath;
    }

    function getNodeOfMinFscore(openList) {
        var minFscore = openList[0].fcost;
        var nodeOfminFscore;
        for (let i = 0; i < openList.length; i++) {
            if (openList[i].fcost <= minFscore) {
                minFscore = openList[i].fcost
                nodeOfminFscore = openList[i]
            }
        }

        return nodeOfminFscore
    }

    function manhattanDistance(stNode, dstNode) {
        var x = Math.abs(dstNode.x - stNode.x);
        var y = Math.abs(dstNode.y - stNode.y);
        var dist = x + y;
        return dist;
    }

    return "Shortest path: " + path;
}

let props = {
    locations: [
        { id: 1, x: 312, y: 152, connectedToIds: [4,2], name: "Thetaham" },
        { id: 2, x: 590, y: 388, connectedToIds: [1,3], name: "Deltabury" },
        { id: 3, x: 428, y: 737, connectedToIds: [2], name: "Gammation" },
        { id: 4, x: 222, y: 430, connectedToIds: [1], name: "Theta City" },
    ],
    originLocationId: 1,
    destinationLocationId: 3, 
};

console.log(render.call({props}));

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Is it possible for a user to modify the JavaScript code on a website?

As I develop a website that heavily relies on JavaScript, I am curious about the possibility of users being able to edit the JS code themselves. For instance, if I have an ajax function that calls a.php, could a user modify the function using Firebug or a ...

One issue encountered in the AngularJS library is that the default value does not function properly in the select element when the value and

In this AngularJS example, I have created a sample for the select functionality. It is working fine when I set the default value of the select to "$scope.selectedValue = "SureshRaina";". However, when I set it to "$scope.selectedValue = "Arun";", it does n ...

subscribing to multiple observables, such as an observable being nested within another observable related to HTTP requests

Hello, I recently started learning Angular and I am facing a challenge with posting and getting data at the same time. I am currently using the map function and subscribing to the observable while also having an outer observable subscribed in my component. ...

Automatically switch slides and pause the carousel after completing a loop using Bootstrap 5 Carousel

Seeking assistance with customizing the carousel functionality. There seems to be some issues, and I could use a hand in resolving them. Desired Carousel Functionality: Automatically start playing the carousel on page load, and once it reaches the end of ...

Ensure that the alert for an Ajax JSON record count remains active when the count is

Trying out Ajax JSON for the first time has been a bit tricky. Even though I hard coded "Record: 1" on the server side, it keeps alerting me with a total record of 0. I'm not sure where I went wrong. Could it be an issue with how I passed the array da ...

Acquiring mapping information through JSON data retrieval

Overview : Each levelNum has three corresponding dropdowns. For example, the dropdown for levelNum2 includes (Department-Unit-1, Department-Unit-2 & Department-Unit-3), levelNum3 includes (Division-Unit-1 & Division-Unit-2), and levelNum4 includes ...

Is it possible to utilize the output of a function nested within a method in a different method?

I am currently facing a challenge with my constructor function. It is supposed to return several methods, but I'm having trouble using the value from this section of code: var info = JSON.parse(xhr.responseText); Specifically, I can't figure ou ...

Monitor a user's activity and restrict the use of multiple windows or tabs using a combination of PHP and

I am looking to create a system that restricts visitors to view only one webpage at a time, allowing only one browser window or tab to be open. To achieve this, I have utilized a session variable named "is_viewing". When set to true, access to additional ...

React Native - state hook refreshes and causes the component to re-render, yet no changes are visible

Here is the code snippet: export default () => { const [albums, setAlbums] = useState([]); useEffect(() => { MediaLibrary.getAlbumsAsync().then((tmpAlbums) => { setAlbums(tmpAlbums); }); }, []); return ( ...

Exploring the optimal placement and techniques for testing an ESC keydown event within a modal component

In my latest project, I created a modal component with added functionality to close it when the ESC key is pressed. Here's how I implemented this behavior in a ModalContainer component: componentDidMount() { window.addEventListener('keydown&ap ...

An issue encountered with getServerSideProps in NextJS 12 is causing a HttpError stating that cookies are not iterable, leading

Currently, I have an application running smoothly on my localhost. However, when it comes to production, an unexpected error has popped up: Error: HttpError: cookies is not iterable at handleError (/usr/src/.next/server/chunks/6830.js:163:11) at sendReques ...

How can I create distinct component IDs effectively with the Catberry Framework?

When working with Catberry, it is essential that all components have unique IDs. How can you ensure that each ID remains distinctive even within a complex structure of nested components? ...

Example of fetching Pubnub history using AngularJS

I am not a paid PubNub user. I am utilizing the example code for an Angular JS basic chat application from PubNub, and I want to access the chat history. This specific example can be found on the PubNub website. git clone https://github.com/stephenlb/an ...

Ways to handle errors when using navigator.clipboard.writeText

document.queryCommandSupported('copy') may not be available on all browsers. I experimented with the code below, which successfully copies the link on Firefox but fails on Opera. It displays an alert indicating that the code has been copied, yet ...

Executing Client-Side Function Upon Data Retrieval in a Node.js Application

Within my node.js Express application, there exists a route like this: router.get('/devices/:id/visualize', catchErrors(deviceController.visualizeDevice)); This route points to the following controller: exports.visualizeDevice = async (req, re ...

Exploring the Wonderful World of Styled Components

I have a query regarding styled components and how they interact when one is referenced within another. While I've looked at the official documentation with the Link example, I'm still unclear on the exact behavior when one styled component refe ...

Implementing CSS keyframes when a specified PHP condition is satisfied

I am looking to implement an opening animation on my website that should only play when a user visits the site for the first time. I want to avoid displaying the animation every time the page is reloaded, so it should only run if a cookie indicating the us ...

What is the procedure for verifying the type of an element using chai?

Is there a way to determine if an element is either an "a" tag or a "div" tag? I've tried the following code, but it's not working as expected: it('has no link if required', () => { const wrapper = shallow(<AssetOverlay a ...

React and Redux: Error Alert - A change in state was detected during dispatch operations

While using 'controlled' components (utilizing setState() within the component), I am encountering an intermittent error when trying to save form data. The UserForm's onSave function calls back to the saveUser method in the component code pr ...

Exploring the functionality of closing Material UI Drawer on escape key in a React 16 app with RTL support

I am currently experimenting with the Material UI Drawer component. I expected it to close when pressing the Esc key or clicking outside of it, but unfortunately, it is not behaving as anticipated. I am utilizing the react testing library for my tests an ...