-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e6c9328
commit c897edf
Showing
7 changed files
with
395 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
@main | ||
def main(): Unit = println("Hello World") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package algorithms.dynamic | ||
|
||
import algorithms.dynamic.LCS | ||
|
||
/// Prints a git-style diff to transform file A into file B using LCS. | ||
/// Shows context lines, added (`+`), and removed (`-`) lines. | ||
/// | ||
/// @param a First file (original) | ||
/// @param b Second file (modified) | ||
def printGitDiff(a: Seq[String], b: Seq[String]): Unit = { | ||
val (_, _, lcsStr) = LCS(a.mkString("\n").toArray, b.mkString("\n").toArray) | ||
val lcsSet = lcsStr.split("\n").toSet | ||
|
||
println("--- Original") | ||
println("+++ Modified") | ||
|
||
var i = 0 | ||
var j = 0 | ||
|
||
while (i < a.length || j < b.length) { | ||
if (i < a.length && j < b.length && a(i) == b(j)) { | ||
// Context line (unchanged) | ||
println(s" ${a(i)}") | ||
i += 1 | ||
j += 1 | ||
} else if (i < a.length && !lcsSet.contains(a(i))) { | ||
// Removed line | ||
println(s"- ${a(i)}") | ||
i += 1 | ||
} else if (j < b.length && !lcsSet.contains(b(j))) { | ||
// Added line | ||
println(s"+ ${b(j)}") | ||
j += 1 | ||
} else { | ||
// Move to next match in LCS | ||
i += 1 | ||
j += 1 | ||
} | ||
} | ||
} | ||
|
||
@main def main(): Unit = { | ||
// Longer C++ programs as strings | ||
val cppProgram1 = | ||
"""#include <iostream> | ||
|using namespace std; | ||
| | ||
|void greet() { | ||
| cout << "Hello, World!" << endl; | ||
|} | ||
| | ||
|int main() { | ||
| greet(); | ||
| return 0; | ||
|}""".stripMargin | ||
|
||
val cppProgram2 = | ||
"""#include <iostream> | ||
|#include <vector> // Added new header | ||
|using namespace std; | ||
| | ||
|void greet() { | ||
| cout << "Hello, Universe!" << endl; // Modified output | ||
|} | ||
| | ||
|int main() { | ||
| vector<int> nums = {1, 2, 3}; // Added new feature | ||
| for (int num : nums) { | ||
| cout << num << " "; | ||
| } | ||
| cout << endl; | ||
| | ||
| greet(); | ||
| return 0; | ||
|}""".stripMargin | ||
|
||
// Convert C++ programs into line sequences | ||
val fileA = cppProgram1.split("\n").toSeq | ||
val fileB = cppProgram2.split("\n").toSeq | ||
|
||
// Compute and print the git diff-style output | ||
printGitDiff(fileA, fileB) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package algorithms.graph | ||
|
||
import datastructures.graph.Graph | ||
import datastructures.basic.PriorityQueue | ||
import scala.collection.mutable | ||
|
||
/// A* Algorithm | ||
/// Finds the shortest path from a starting node to a target node in a weighted graph. | ||
/// Uses a heuristic function to guide the search towards the target node more efficiently. | ||
/// Is proven to be the best algorithm | ||
/// https://github.com/bb4/bb4-A-star/blob/master/scala-source/com/barrybecker4/search/AStarSearch.scala | ||
/// Runs in O((V + E) log V) in the worst case, depending on the quality of the heuristic. | ||
|
||
def aStar[T](graph: Graph[T], start: T, target: T, heuristic: T => Int): Option[Int] = { | ||
// Check if start and target are the same, return 0 if true | ||
if (start == target) return Some(0) | ||
|
||
// Map to store the shortest known distance from the start node to each node | ||
val gScores = mutable.Map[T, Int]().withDefaultValue(Int.MaxValue) | ||
gScores(start) = 0 | ||
|
||
// Map to store the estimated distance from each node to the target | ||
val fScores = mutable.Map[T, Int]().withDefaultValue(Int.MaxValue) | ||
fScores(start) = heuristic(start) | ||
|
||
// Set to keep track of visited nodes | ||
val visited = mutable.Set[T]() | ||
|
||
// Priority queue (min-heap) to select the node with the lowest f-score | ||
val priorityQueue = PriorityQueue[(T, Int)]()(Ordering.by(-_._2)) | ||
priorityQueue.enqueue((start, fScores(start))) | ||
|
||
// Process the queue until it is empty or the target is reached | ||
while (!priorityQueue.isEmpty) { | ||
val (currentNode, currentFScore) = priorityQueue.dequeue().get | ||
|
||
// If we reach the target, return the cost to get here | ||
if (currentNode == target) return Some(gScores(currentNode)) | ||
|
||
// If node has been visited, skip it | ||
if (!visited.contains(currentNode)) { | ||
visited.add(currentNode) | ||
|
||
// For each neighbor of the current node, calculate the potential new g-score and f-score | ||
for ((neighbor, weight) <- graph.getNeighbors(currentNode)) { | ||
val tentativeGScore: Int = gScores(currentNode) + weight.toInt | ||
if (tentativeGScore < gScores(neighbor)) { | ||
gScores(neighbor) = tentativeGScore | ||
fScores(neighbor) = tentativeGScore + heuristic(neighbor) | ||
priorityQueue.enqueue((neighbor, fScores(neighbor))) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Return None if the target is unreachable | ||
None | ||
} |
70 changes: 70 additions & 0 deletions
70
src/main/scala/algorithms/graph/biDirectionalDijkstra.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package algorithms.graph | ||
|
||
import datastructures.graph.Graph | ||
import datastructures.basic.PriorityQueue | ||
import scala.collection.mutable | ||
|
||
/// Bidirectional Dijkstra's Algorithm | ||
/// Finds the shortest path between two nodes by running Dijkstra's algorithm from both the source and target simultaneously | ||
/// This approach can be faster than standard Dijkstra when the target is known | ||
/// Runs in O((V + E) log V) in the worst case, but may terminate earlier | ||
|
||
def bidirectionalDijkstra[T](graph: Graph[T], start: T, target: T): Map[T, Int] = { | ||
if (start == target) return Map(start -> 0) | ||
|
||
val forwardDistances = mutable.Map[T, Int]().withDefaultValue(Int.MaxValue) | ||
val backwardDistances = mutable.Map[T, Int]().withDefaultValue(Int.MaxValue) | ||
val forwardVisited = mutable.Set[T]() | ||
val backwardVisited = mutable.Set[T]() | ||
val forwardQueue = PriorityQueue[(T, Int)]()(Ordering.by(-_._2)) | ||
val backwardQueue = PriorityQueue[(T, Int)]()(Ordering.by(-_._2)) | ||
|
||
forwardDistances(start) = 0 | ||
backwardDistances(target) = 0 | ||
forwardQueue.enqueue((start, 0)) | ||
backwardQueue.enqueue((target, 0)) | ||
|
||
def processQueue( | ||
queue: PriorityQueue[(T, Int)], | ||
distances: mutable.Map[T, Int], | ||
visited: mutable.Set[T], | ||
otherDistances: mutable.Map[T, Int] | ||
): Option[Int] = { | ||
queue.dequeue() match { | ||
case Some((currentNode: T, currentDistance: Int)) => | ||
if (otherDistances.contains(currentNode)) return Some(currentDistance + otherDistances(currentNode)) | ||
|
||
if (!visited.contains(currentNode)) { | ||
visited.add(currentNode) | ||
|
||
for ((neighbor, weight) <- graph.getNeighbors(currentNode)) { | ||
if (!visited.contains(neighbor)) { | ||
val newDistance = currentDistance + weight | ||
if (newDistance < distances(neighbor)) { | ||
distances(neighbor) = newDistance.toInt | ||
queue.enqueue((neighbor, newDistance.toInt)) | ||
} | ||
} | ||
} | ||
} | ||
case None => | ||
} | ||
None | ||
} | ||
|
||
// While both queues are non-empty, continue processing | ||
while (!forwardQueue.isEmpty && !backwardQueue.isEmpty) { | ||
processQueue(forwardQueue, forwardDistances, forwardVisited, backwardDistances) match { | ||
case Some(_) => // Found the shortest path, continue | ||
case None => | ||
} | ||
processQueue(backwardQueue, backwardDistances, backwardVisited, forwardDistances) match { | ||
case Some(_) => // Found the shortest path, continue | ||
case None => | ||
} | ||
} | ||
|
||
// Return the merged distances from both directions | ||
// The final result will be the combination of forward and backward distances | ||
forwardDistances.toMap ++ backwardDistances.toMap | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package algorithms.graph | ||
|
||
import munit.FunSuite | ||
import datastructures.graph.Graph | ||
|
||
class AStarTest extends FunSuite { | ||
|
||
// Example heuristic function (Manhattan Distance for grid-based problems) | ||
def heuristic(node: String): Int = node match { | ||
case "A" => 6 | ||
case "B" => 5 | ||
case "C" => 2 | ||
case "D" => 0 // Target node | ||
case _ => Int.MaxValue | ||
} | ||
|
||
test("A* should find the shortest path in a small graph") { | ||
val graph = new Graph[String](isDirected = true) | ||
graph.addEdge("A", "B", 1) | ||
graph.addEdge("A", "C", 4) | ||
graph.addEdge("B", "C", 2) | ||
graph.addEdge("B", "D", 5) | ||
graph.addEdge("C", "D", 1) | ||
|
||
val result = aStar(graph, "A", "D", heuristic) | ||
|
||
assertEquals(result, Some(4)) // The shortest path from A to D is via B and C: A -> B -> C -> D (1 + 2 + 1) | ||
} | ||
|
||
test("A* should handle the case where the start is the same as the target") { | ||
val graph = new Graph[String](isDirected = true) | ||
graph.addEdge("A", "B", 1) | ||
|
||
val result = aStar(graph, "A", "A", heuristic) | ||
|
||
assertEquals(result, Some(0)) // If start and target are the same, the cost is 0 | ||
} | ||
|
||
test("A* should return None if the target is unreachable") { | ||
val graph = new Graph[String](isDirected = true) | ||
graph.addEdge("A", "B", 1) | ||
graph.addEdge("B", "C", 1) | ||
|
||
val result = aStar(graph, "A", "D", heuristic) | ||
|
||
assertEquals(result, None) // "D" is unreachable from "A" | ||
} | ||
|
||
test("A* should work with different heuristics") { | ||
// Using a different heuristic function for this case | ||
def alternativeHeuristic(node: String): Int = node match { | ||
case "A" => 7 | ||
case "B" => 4 | ||
case "C" => 3 | ||
case "D" => 0 | ||
case _ => Int.MaxValue | ||
} | ||
|
||
val graph = new Graph[String](isDirected = true) | ||
graph.addEdge("A", "B", 3) | ||
graph.addEdge("A", "C") | ||
graph.addEdge("B", "C", 7) | ||
graph.addEdge("B", "D", 5) | ||
graph.addEdge("C", "D", 2) | ||
|
||
val result = aStar(graph, "A", "D", alternativeHeuristic) | ||
|
||
assertEquals(result, Some(3)) // The shortest path from A to D is via C: A -> C -> D (1 + 2) | ||
} | ||
} |
Oops, something went wrong.