user9929463
user9929463

Reputation:

A* algorithm returns heuristic value (JavaScript)

I created a A* algorithm for JavaScript. The important thing is that it has to search for the longst path not the shortest. When the path finishes searching I just want to get the depth of the path / its length. The algorithm just returns a maximum length instead of the path.

When debugging the result I think this algorithm just returns the value of the heuristic.

I have stage objects with ids and activity objects which connect two stages by holding a fromId and a toId.

Example:

I have the ids of the status objects

and the activities

I would expect this result:

but the result I get is

result

Why do I think I can use the A* for the longest path?

Well the A* compares distances and takes the lower one. It should be possible to take the higher distance with higher cost by changing the compare statement.

I provide a working code example here

// #########################################
// My initializer script with some fake data
// #########################################

$(document).ready(() => {
  const path = new Path(activities, 1);

  for (let i = 0; i < stages.length; i++) {
    const currentStage = stages[i];
    const currentStageId = currentStage.id;
    const currentDistance = path.getMaximumDistance(currentStage);
    console.log(`id: ${currentStageId} distance: ${currentDistance}`);
  }
});

const stages = [
  new Stage(1),
  new Stage(2),
  new Stage(3),
  new Stage(4),
  new Stage(5)
];

function Stage(id) {
  this.id = id;
}

const activities = [
  new Activity(1, 2),
  new Activity(1, 3),
  new Activity(2, 3),
  new Activity(3, 5),
  new Activity(5, 1)
];

function Activity(fromId, toId) {
  this.fromId = fromId;
  this.toId = toId;
}

// #########################################
// The A* class
// #########################################

function Path(activities, initialId) {

  this.getMaximumDistance = (targetStage) => {
    var me = this;
    var targetId = targetStage.id;
    var openList = [];
    var closedList = [];

    this.addNodeToList(openList, this.createNode(initialId, this.activities));

    let maxDistance = 0;

    while (openList.length > 0) {
      var currentNode = openList[this.getHighestCostIndex(openList)];

      if (currentNode.id === targetId) {
        maxDistance = this.getPathLength(currentNode);
        break;
      }

      this.removeNodeFromList(openList, currentNode);
      this.addNodeToList(closedList, currentNode);

      currentNode.neighbourIds.forEach(id => {
        var neighbour = me.createNode(id, me.activities);

        let lookUpTableElement;
        if (!me.listContainsNode(closedList, neighbour)) {
          var tempIncrementalCost = currentNode.incrementalCost + 1;

          if (me.listContainsNode(openList, neighbour)) {
            if (tempIncrementalCost < neighbour.incrementalCost) {
              neighbour.changeIncrementalCost(tempIncrementalCost);
            }
          } else {
            neighbour.changeIncrementalCost(tempIncrementalCost);
            me.addNodeToList(openList, neighbour);
          }

          for (var i = 0; i < me.lookUpTable.length; i++) {
            var currentLookUpTableElement = me.lookUpTable[i];
            if (currentLookUpTableElement.id === neighbour.id) {
              lookUpTableElement = currentLookUpTableElement;
            }
          }

          var heuristic = lookUpTableElement ? lookUpTableElement.cost : 0;
          neighbour.changeHeuristic(heuristic);
          neighbour.setParent(currentNode);
        }
      });
    }

    return maxDistance;
  };

  this.createNode = (id, activities) => {
    return new PathNode(id, activities);
  };

  this.getHighestCostIndex = (openList) => {
    let highestCostIndex = 0;

    for (let i = 0; i < openList.length; i++) {
      if (openList[i].totalCost > openList[highestCostIndex].totalCost) {
        highestCostIndex = i;
      }
    }

    return highestCostIndex;
  };

  this.addNodeToList = (list, node) => {
    list.push(node);
  };

  this.listContainsNode = (list, node) => {
    return list.some(x => x.id === node.id);
  };

  this.removeNodeFromList = (list, node) => {
    var index = list.indexOf(node);
    list.splice(index, 1);
  };

  this.getPathLength = (node) => {
    let currentNode = node;
    let counter = 0;

    while (currentNode.parent) {
      currentNode = currentNode.parent;
      counter++;
    }

    return counter;
  };

  this.activities = activities;
  this.lookUpTable = new LookUpTable(this.activities, initialId).table;
}

// #########################################
// Node class for the A*
// #########################################

function PathNode(id, activities) {

  this.getNeighbourIds = (activities) => {
    return activities
      .filter(x => x.fromId === id)
      .map(x => x.toId);
  };

  this.setParent = (parentNode) => {
    this.parent = parentNode;
  };

  this.changeHeuristic = (heuristic) => {
    this.heuristic = heuristic;
    this.updateTotalCost();
  };

  this.changeIncrementalCost = (incrementalCost) => {
    this.incrementalCost = incrementalCost;
    this.updateTotalCost();
  };

  this.updateTotalCost = () => {
    this.totalCost = this.incrementalCost + this.heuristic;
  };

  this.id = id;
  this.heuristic = 0;
  this.incrementalCost = 0;
  this.totalCost = 0;
  this.parent = null;
  this.neighbourIds = this.getNeighbourIds(activities);
}

// #########################################
// The heuristic code
// #########################################

function LookUpTable(activities, originId) {
  this.getLookUpTable = (originId) => {
    const me = this;
    const lookUpTable = [];
    let currentLevel = 0;
    let openList = [];

    this.addStage(openList, originId, currentLevel);

    while (openList.length > 0) {
      var lookUpTableElements = openList.filter(openNode => openNode.cost <= currentLevel);

      lookUpTableElements.forEach(lookUpTableElement => {
        this.addStage(lookUpTable, lookUpTableElement.id, currentLevel);

        var neighbours = activities.filter(targetActivity => targetActivity.fromId === lookUpTableElement.id);

        neighbours.forEach(neighbour => {
          var neighbourId = neighbour.toId === lookUpTableElement.id ? neighbour.fromId : neighbour.toId;

          if (!lookUpTable.some(x => x.id === neighbourId) && !openList.some(x => x.id === neighbourId)) {
            me.addStage(openList, neighbourId, currentLevel + 1);
          }
        });
      });

      openList = openList.filter(x => x.cost !== currentLevel);
      currentLevel++;
    }

    return lookUpTable;
  };

  this.addStage = (array, stageId, level) => {
    array.push({
      id: stageId,
      cost: level
    });
  };

  this.activities = activities;
  this.table = this.getLookUpTable(originId);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

What is wrong with my A* algorithm that it returns the correct heuristic value instead of the maximum distance?

Important note: If someone got a better idea for finding the maximum distance between these connections please let me know!

Upvotes: 0

Views: 840

Answers (1)

SaiBot
SaiBot

Reputation: 3755

In general shortest-path algorithms cannot be tweaked to return the (simple) longest path in a given graph.

Additionally, finding a simple (non cyclic) longest path is NP-hard.

In your case the cost of each edge is 1, unfortunately this does not improve things. Even if you knew you could reach all vertices you would end up searching a Hamilton Path which is NP-Complete.

I suggest to look at some approximation algorithms for the longest-path problem.

Upvotes: 4

Related Questions