Dijkstra's algorithm implemented in Raku

Run Settings
LanguageRaku
Language Version
Run Command
use v6; =begin comment Implementation of the Dijkstra's algorithm. Based on the Go's implementation by Jonatas B (https://deployeveryday.com/2019/10/16/dijkstra-algorithm-golang.html). The Graph class makes the following methods available: * add-edge * add-node * get-node-edges * Dijkstra Their documentation can be pulled by using `p6doc graph.p6` where graph.p6 is this file. To instatiate either an edge or a node, use Graph::Edge or Graph::Node respectively. =end comment class Graph { class Node { has Str $.name; } class Edge { has Node $.parent; has Node $.child; has Int $.cost; } has Bool %!edges{Edge}; has Node @!nodes; #| Add an edge to the graph. method add-edge(Node:D $parent, Node:D $child, Int:D $cost --> Nil) { my Edge $edge .= new: :$parent, :$child, :$cost; %!edges{$edge} = True; self.add-node: $parent; self.add-node: $child; } #| Add a node to the graph list of nodes, if the the node wasn't already #| added. Return True if added. Otherwise, False. method add-node(Node:D $node --> Bool) { my Bool $is-present = False; for @!nodes -> $n { $is-present = True if $n eqv $node; } unless $is-present { @!nodes.append: $node; return True; } return False; } #| Return all the edges that start with the specified node. In other terms, #| all the edges connecting to the node's neighbors. method get-node-edges(Node:D $node --> Array) { my Edge @edges = %!edges .grep({ $^edge.key.parent eqv $node}) # get pairs of Edge => Bool .map( { $^edge.key }); # get pair's key (i.e., Edge) return @edges } #| Implement Dijkstra algorithm. Return the shortest path from start node to #| all the other nodes. method Dijkstra(Node:D $start-node --> Hash) { =begin comment First, we instantiate a "cost table", which will hold the following information: "From start node, what's is the cost to all the other nodes?". When initialized, it looks like this: NODE COST A 0 # The start node has always the lowest cost to itself, in this case, 0 B Inf # the distance to all the other Nodes are unknown, so we mark as Infinity C Inf ... =end comment my Hash $cost-table = self!new-cost-table($start-node); # An empty list of "visited" nodes. Everytime the algorithm runs on a # node, we add it here. my Node @visited; # A loop to visit all nodes. while @visited.elems != @!nodes.elems { # Get closest non visited node (lower cost) from the cost table. my Node $node = self!get-closest-nonvisited-node($cost-table, @visited); # Mark node as visited. @visited.append($node); # Get node's edges (i.e., its neighbors). my Edge @node-edges = self.get-node-edges($node); for @node-edges -> $edge { my Int $distance-to-neighbor = $cost-table{$node} + $edge.cost; # If the distance above is lesser than the distance currently # in the cost table for that neighbor... if $distance-to-neighbor < $cost-table{$edge.child} { # ... Update the cost table for that neighbor. $cost-table{ $edge.child } = $distance-to-neighbor; } } } return $cost-table; } # Return a string representation of the graph. It kicks in by invoking # .Str on the graph or when outputing with the `say` routine. method gist { my Str $s; $s ~= "Edges:\n"; for %!edges.keys -> $edge { $s ~= "{$edge.parent.name} -> {$edge.child.name} = {$edge.cost}\n"; } $s ~= "\n"; $s ~= "Nodes: "; for @!nodes.kv -> $i, $node { if $i == @!nodes.elems - 1 { $s ~= $node.name } else { $s ~= $node.name ~ ", " } } $s ~= "\n"; return $s } # Return an initialized cost table for the Dijkstra algorithm. # By default, the lowest cost is assigned to the start node. Thus, the # algorithm starts from the and the other nodes in the graph receives the # Inf value. method !new-cost-table(Node:D $start-node --> Hash) { my %cost-table{Node}; %cost-table{ $start-node } = 0; for @!nodes -> $node { %cost-table{$node} = Inf if !($node eqv $start-node); } return %cost-table; } # Return the closest node (with the lower cost) from the cost table. # That is if the node hasn't been visited yet. method !get-closest-nonvisited-node(%cost-table, Node @visited --> Node) { class CostTableToSort { has Node $.node; has $.cost; } my CostTableToSort @sorted; # Check if the node has been visited already. for %cost-table.kv -> $node, $cost { my Bool $is-visited = False; $is-visited = True if $node ∈ @visited; # If not, add them to the sorted slice unless $is-visited { @sorted.append(CostTableToSort.new(:$node, :$cost)); } } # We need the node with the lower cost from the cost table. So, # sort cost table by cost and retrieve it. return @sorted.sort({ $^a.cost <=> $^b.cost }).first.node; } } my Graph::Node $a .= new(name => "a"); my Graph::Node $b .= new(name => "b"); my Graph::Node $c .= new(name => "c"); my Graph::Node $d .= new(name => "d"); my Graph::Node $e .= new(name => "e"); my Graph::Node $f .= new(name => "f"); my Graph::Node $g .= new(name => "g"); my Graph $graph .= new; $graph.add-edge($a, $c, 2); $graph.add-edge($a, $b, 5); $graph.add-edge($c, $b, 1); $graph.add-edge($c, $d, 9); $graph.add-edge($b, $d, 4); $graph.add-edge($d, $e, 2); $graph.add-edge($d, $g, 30); $graph.add-edge($d, $f, 10); $graph.add-edge($f, $g, 1); say $graph; my $cost-table = $graph.Dijkstra($a); for $cost-table.kv -> $node, $cost { "Distance from %s to %s = %d\n".printf($a.name, $node.name, $cost); }
Editor Settings
Theme
Key bindings
Full width
Lines