Optimal Route Planning: A Brute Force Solution

Search for a command to run...

good stuff
Sometimes an example says more than 1000 pages of documentation. This series will take specific algorithmns and discuss their implementation in PicoLisp step by step. https://rosettacode.org/
The original task is from the book "Superior Mathematical Puzzles" from Howard P. Dinesman. The Task Baker, Cooper, Fletcher, Miller, and Smith live on different floors of an apartment house that contains only five floors. Baker does not live on t...
In our previous post, we introduced how to utilize Discrete Event Simulation (DES) in PicoLisp. Now, let's explore another application: a railroad model simulation that includes tracks and trains. The visualization is ASCII-based, so feel free to unl...

The Dining Philosophers

In this post, we will introduce the concept of co-routines and how they are handled in PicoLisp. This will be the foundation for the subsequent posts on Discrete Event Simulation and finally a couple of examples, including an ASCII model railway simu...

One of the many nice things of PicoLisp is that it comes with a built-in database. This makes it easy to collect and store our own data, for example via a mobile app. But not all data is best represented in tables and lists: sometimes a colorful litt...

In the last two posts, we have built a little app that shows the current location and displays the nearest ice cream shops: https://picolisp-explored.com/an-openstreetmap-app-written-in-picolisp-pt-1 https://picolisp-explored.com/an-openstreetmap-ap...

This task is actually not included in the Rosetta code, but still it is a quite frequent algorithmic task: How to find the optimal path between two nodes of a graph. In this post, we will show a brute-force solution using Pilog to get from A to B.
An alternative algorithm to solve this kind of task is using Dijkstra's algorithm. You can find an implementation here (only PicoLisp, not in Pilog).
Given a list of cities and the distances between each pair of cities, find the optimal route from city A to city B.
In this task, our salesman frequently needs to visit the following cities: Rheine, Münster, Onsabrück, Warendorf, Rheda, Bielefeld, Paderborn, Soest, Ahlen, Beckum, Rheda, Gütersloh. (Don't worry if you have never heard of some of those, I haven't either).
This is the distance information that we have:

(be vect (Rheine Muenster 39))
(be vect (Rheine Osnabrueck 42))
(be vect (Muenster Osnabrueck 51))
(be vect (Warendorf Muenster 28))
...
In this example, we will first write an algorithm that finds all possible paths between city A and city B. After that we will find the minimum value using the PicoLisp mini function.
We are modeling our city model als simple undirected, weighted graph, where the distances are described by the vect predicate. The nodes are the cities, and the edges show the distance. "Undirected" means that we can travel in both directions. "Weighted" means that the edges are associated with a unit that can be used to optimize the route - in this case, the distance in kilometers.
Let's create a predicate edge that returns T if we have a vector between two cities @A and @B, and NIL if not.
(be edge (@A @B @N) (vect @A @B @N))
(be edge (@A @B @N) (vect @B @A @N))
pathNow we want to create all paths between two nodes. We define a predicate path with start @A, destination @B, path @P and cost @N:
(be path (@A @B @P @N))
where @P is a list of cities. Unfortunately we cannot work well with this definition because in order to implement our backtracking mechanism, it would be helpful to reflect the "middle" stations as well.
Therefore we define a "helper predicate" path1 and split the path @P in two parts: The list of cities required to reach a current stop (@L), and another list to reach from the current stop @A to the final destination @B.
(be path1 (@A @B @L (@A . @P) @N)
What is the connection between path and path1? For initialization, we are only considering the start and destination stop. Therefore @L is equal to the start stop (because we didn't move from there yet), and @P is equal to the remaining distance.
(be path (@A @B @P @N)
(path1 @A @B (@A) @P @N) )
path1Now we can define all the recursive logic in our helper predicate path1. First the base case @B = @A: If start and destination are equal, then our "up to point @A" travel list remains unchanged (= @L), and the "from @A to @B" travel list contains only one element: (@A). The travel cost is zero.
(be path1 (@A @A @L (@A) 0.
Now what if @A not equal to @B? In that case, we need to modify the path "from @A to @B" to (@A . @P): a list starting at @A with all stops until destination @B.
be path1 (@A @B @L (@A . @P ) @N)
Now how do we get from @A to @B? We need to check if there is any city @Z we can reach from @A. The following conditions need to be fulfilled:
@A and @Z at cost @X,@Z is not on our path @L yet. We check this with the built-in predicate member.In Pilog:
(be path1 (@A @B @L (@A . @P) @N)
(edge @A @Z @X)
(not (member @Z @L))
... )
Now we calculate our route from @Z to @B. Since we "increment" the starting point, we need to add it to the head of our travelling path @L.
(path1 @Z @B (@Z . @L) @P @Y)
In the "best case", @Zand @B are equal, which means we are calling the base case and exit the recursion.
The cost is simply the sum of the weighted graphs of the whole path. If we have travel cost of @X from A to Z, and @Y from Z to B, then in total our cost is @X+@Y. So let's set this sum to @N in the last step. This is our final predicate definition:
(be path1 (@A @A @L (@A) 0))
(be path1 (@A @B @L (@A . @P) @N)
(edge @A @Z @X)
(not (member @Z @L))
(path1 @Z @B (@Z . @L) @P @Y)
(^ @N (+ @X @Y)) )
If we run a query like (? (path Bielefeld Muenster @P @N)), we will typically get more than one result. We can process the whole set of return values using the solve function:
(solve 'lst . prg) -> anyEvaluates a Pilog query and, returns the list of result sets. If
prgis given, it is executed for each result set, with all Pilog variables bound to their matching values, and returns a list of the results.
Like this:
(solve '(
@A 'Rheine
@B 'Muenster
(path @A @B @P @N) )
Now we want the optimal result, i.e. with the minimal cost (@N). In order to access it, let's store each result in a cons pair (cons @N @P) (a key-value pair stored in a cell with a value in CAR and CDR each). Read here if you don't know what a cell is.
Then we can find the minimum value using the mini function which applies a function to a list and returns the minimal value. In our case, we get back a list of cons pairs and we're looking for the minimum CAR value which we can get with car.
(mini car
(solve
'( <pilog query>
(cons @N @P) ) )
As last step, let's wrap everything into a function.
(de travel (A B)
(mini car
(solve
...
If we now start the program in debug mode: pil travel.l +, we can interact with it in the REPL:
: (travel 'Muenster 'Warendorf)
-> (28 Muenster Warendorf)
You can find the finished program here.
https://www.irasutoya.com/2014/07/blog-post_8691.html
https://software-lab.de/doc/index.html