7
\$\begingroup\$

I'm just getting my feet wet with F#, by implementing some simple algorithms. First up: Djikstras shortest path.

There is one piece I've written in this code which confuses me as to why it works: the contains function. As I understand it, List.isEmpty returns true when a list is empty; but in this case, I would want to see the opposite of that. However, not(List.isEmpty) appeared to give the opposite of what I was expecting: it still returned true on a list being empty. Why is this the case? Or am I just missing something obvious?

As for the full code listing: have I done anything massively wrong?

open System;
open System.Collections;
open System.Collections.Generic;
open System.IO;
type Node(id: int, neighbours:list<(int*int)>) =
 let mutable _ID = id
 let mutable _neighbours:list<(int * int)> = neighbours
 member this.ID
 with get() = _ID
 and set(value) = _ID <- value
 member this.AddNeighbour(id:int, distance:int) =
 let t:(int*int) = (id, distance)
 _neighbours <- _neighbours @ [t]
 member this.Neighbours
 with get() = _neighbours
 and set(value) = _neighbours <- value;
let nodes:list<Node> = [new Node(1, [(2, 3); (3, 3); (4, 1)]);
 new Node(2, [(3, 1)]);
 new Node(3, [(1, 3); (2, 1); (4, 10)]);
 new Node(4, [(1, 1); (3, 10)])
 ];
let rec remove_first pred lst =
 match lst with
 | h::t when pred h -> t
 | h::t -> h::remove_first pred t
 | _ -> [];
let contains (item:'a) (lst:list<'a>) : bool =
 let filtered = List.filter (fun I -> I = item) lst;
 List.isEmpty(filtered);
let rec Traverse (data:list<Node>, start:int, visited:list<int>):list<int> =
 let startNode = List.find (fun (E:Node) -> E.ID = start) data //Get the starting node
 let newData = remove_first (fun (E:Node) -> E.ID = startNode.ID) data //Get the new data by removing the start node from the current data
 let newVisited = visited@[startNode.ID]
 let neighbourSet = List.filter (fun (i,_) -> contains i newVisited) startNode.Neighbours //Use only the neighbours that aren't visisted
 if(List.isEmpty neighbourSet) then
 newVisited;
 else
 neighbourSet
 |> List.minBy (fun (_,d:int) -> d) //Get neighbour with smallest distance
 |> fun (i, d) -> Traverse(newData, i, newVisited); //Recurse in to the traverse function, starting at the smallest neighbour
let result = Traverse (nodes, 1, []);
let writeList (lst:list<'a>):string =
 let mutable output = "";
 for I in lst do
 output <- String.Concat(output, I);
 output;
let path = "Output.txt";
File.WriteAllText(path , writeList(result));
asked Apr 6, 2012 at 18:37
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

Matthew Podwysocki wrote a nice functional solution for this on his blog a few years ago:

module Map =
 let transposeCombine m =
 (m, m) ||> Map.fold (fun acc k1 m' ->
 (acc, m') ||> Map.fold (fun acc' k2 v ->
 acc'
 |> Map.add k2 (Map.add k1 v (defaultArg (acc' |> Map.tryFind k2) Map.empty))
 ))
type City =
 | Boise | LosAngeles | NewYork | Seattle
 | StLouis | Phoenix | Boston | Chicago
 | Denver
let distanceBetweenCities =
 Map.ofList
 [
 (Boise, Map.ofList [(Seattle, 496);(Denver, 830);(Chicago, 1702)]);
 (Seattle, Map.ofList [(LosAngeles, 1141);(Denver, 1321)]);
 (LosAngeles, Map.ofList [(Denver, 1022);(Phoenix, 371)]);
 (Phoenix, Map.ofList [(Denver, 809);(StLouis, 1504)]);
 (Denver, Map.ofList [(StLouis, 588);(Chicago, 1009)]);
 (Chicago, Map.ofList [(NewYork, 811);(Boston, 986)]);
 (StLouis, Map.ofList [(Chicago, 300)]);
 (Boston, Map.ofList [(StLouis, 986)]);
 (NewYork, Map.ofList [(Boston, 211)])
 ]
 |> Map.transposeCombine
let shortestPathBetween startCity endCity =
 let rec searchForShortestPath currentCity distanceSoFar citiesVisitedSoFar accMap =
 let visitDestinations m =
 (m, distanceBetweenCities.[currentCity])
 ||> Map.fold
 (fun acc city distance ->
 searchForShortestPath city (distance + distanceSoFar) (citiesVisitedSoFar @ [city]) acc)
 match Map.tryFind currentCity accMap with
 | None -> accMap |> Map.add currentCity (distanceSoFar, citiesVisitedSoFar) |> visitDestinations
 | Some x ->
 let (shortestKnownPath, _) = x
 if distanceSoFar < shortestKnownPath then
 accMap |> Map.add currentCity (distanceSoFar, citiesVisitedSoFar) |> visitDestinations
 else accMap
 let shortestPaths = searchForShortestPath startCity 0 [] Map.empty
 shortestPaths.[endCity]

Usage:

shortestPathBetween LosAngeles NewYork //(2721, [Denver; StLouis; Chicago; NewYork])
answered Apr 9, 2012 at 14:39
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.