|
Comp210: Principles of Computing and Programming
|
set!
We saw last time, scheme passes arguments "by value":
When calling a function, the arguments (values) are copied onto a piece of paper, and handed to the function. Even if the function re-writes those pieces of paper, the caller's data isn't modified.
On the other hand,
We previously saw "depth-first search" -- finding an airline path between cities by recurring on its neighbors (with a base case of starting at the destination). We found that we needed to worry about cycles, and saw one way of doing so (by marking each city as it was seen).
We mentioned briefly another way of finding a path: Start at the source, then find the list of all cities which are one step away, and then all the cities which are two steps away, then three steps, etc. At each step, we can see if the destination is included. If you think about it, this ends up guaranteeing that a shortest path is found.
How to write this function? Here's a handy helper:
;; map-append: (alpha --> list-of-beta), list-of-alpha --> list-of-beta ;; Map f to each element of inputs, ;; where f returns a list each time. ;; Return the appended results. ;; (define (map-append f inputs) (foldr (lambda (frst rr) (append (f frst) rr)) empty inputs)) ; An alternate implementation, if you know "apply": ; (apply append (map f inputs))Write the function
;; one-away-from: list-of-city --> list-of-city ;; (define (one-away-from srcs) ...)Then, write a function which repeatedly calls
one-away-from
until
it contains dest
.
;; keep-looking: list-of-city, city --> list-of-city ;; (define (keep-looking srcs dest) (cond [.. ..] ; Base-case [.. ..] ; inductive case: keep-looking one-away-from srcs.How does your code behave in the presence of loops? What if there is no path to the
dest
? How would you fix this? (The way keep-looking indefinitely repeats until a finish-condition, is often called a while-loop.)
The above approach, breadth-first search is nice, except that we don't actually return the path, oops! You can think of modifying the code to return how many steps are needed, to get from a given city to another. How might we go further, and keep track of the path? (How many paths need to be kept track of simultaneously? Where should we store that information? In the depth-first approach, where was this information stored?)
The above code can be found here: lect33.ss
What we want to do is to find all the shortest paths from a source to a destination in our flight paths problem from a couple of lectures ago (Lecture 31).
Our function should return a list of paths, which is a list of list of cities: (path src dest) --> list-of-list-of-cities
Following our basic template for generative recursion (see Lecture 29) our basic plan of attack is:
Let's think about what the function to find all the unseen neighbors of a city ("get-unseen-nhbrs") must accomplish:
Let us consider a function that will generate the next level of cities to work on (" getNextLevel"):
Note that the above specification points out a need for a couple of straightforward utility functions:
Now we can come back around and see if we can write Thus in general, at any given level of our breadth-first traversal of the city-to-city graph, we need a function that will find all the paths from a list of cities to the destination, "pathsToDest". This function derives directly from out main plan of attack above, which is in turn, based on the generative recursion template.
That takes care of the inductive step, including the termination conditions. Now all we have to do is get this process started off.
The first level, i.e the src, is a little different than the other levels in that it starts with a single city, not a list of cities. Unfortunately, the solution is not as simple as just putting src into a list because the next level processing makes certain assumptions about the previous level, e.g. that all the cities have been seen and that they have their pathsTo field set to all the possible paths to that city. We end up with 2 choices on how to proceed:
The first technique is arguably easier to understand and conceptualized, while the second method does not duplicate any functionality.
Putting it all together, here's what we get, having put all of our helper functions inside of locals:
(define-struct city (name nhbrs pathsTo seen?)) ;; ;; A city is a ;; (make-city [symbol] [list-of-cities] [boolean]) ;; where pathsTo is a list of list of cities, that are all the paths to this city from a source city-- to be used later, in path-finding. ;; seen? is a boolean that indicates if this city has already been processed--is used in path-finding as well. ; Initially no neighbors: (define nyc (make-city 'nyc empty empty false)) (define la (make-city 'la empty empty false)) (define slc (make-city 'slc empty empty false)) (define hou (make-city 'hou empty empty false)) (define nome (make-city 'nome empty empty false)) (define reno (make-city 'reno empty empty false)) (define cities (list nyc la slc hou nome reno)) (set-city-nhbrs! nyc (list la slc hou nome)) (set-city-nhbrs! la (list reno slc nyc)) ;; comment out for more pathological hou->reno path ;; (set-city-nhbrs! la (list slc nyc)) ;; uncommentfor more pathological hou->reno path (set-city-nhbrs! hou (list nyc la)) (set-city-nhbrs! slc (list reno)) (set-city-nhbrs! reno (list slc)) ;; path: city city --> list-of-list-of-cities ;; Takes a source city, src, and a destination city, dest, ;; and returns all possible shortest paths from src to dest. ;; The returned value is a list of paths, where each path ;; is a list of cities, starting wtih src and ending with ;; dest, defining a possible path from src to dest. (define (path src dest) (local [;;removeDups: list-of-cities --> list-of-cities ;; remove duplicate cities from the list-of-cities (define (removeDups loc) (local [;; isDup? city list-of-cities --> boolean ;; returns true if c is in cs ;; false otherwise (define(isDup? c cs) (foldr (lambda (a-c rr) (if (eq? c a-c) true rr)) false cs))] (foldr (lambda (a-city rr) (if (isDup? a-city rr) rr (cons a-city rr))) empty loc))) ;; get-unseen-nhbrs: city --> list-of-cities ;; returns a list of all the neighbors of a-city ;; that have yet to be seen (the pathTo field is empty). ;; assumes that a-city has been seen already. ;; All cities in the return list will have their ;; pathTo set to the supplied city's pathTo appended ;; with themselves. (define (get-unseen-nhbrs a-city) (foldr (lambda (a-nhbr rr) (cond [(city-seen? a-nhbr) rr];; neighbor has been seen, ignore [else ;; This neighbor is unseen (begin (set-city-pathsTo! a-nhbr ;; set this neighbor's pathTo (append ;; combine all the possible paths to this city (map (lambda (a-path) ;; put this city at the end of all existing paths to a-city (append a-path (list a-nhbr))) (city-pathsTo a-city) ) (city-pathsTo a-nhbr))) ;; combine exisitng and new paths. (cons a-nhbr rr))])) ;; add it to the list empty (city-nhbrs a-city))) ;; setSeen: list-of-cities --> list-of-cities ;; Sets the seen? field of all the cities in the list to true. ;; returns the list of cities (for chaining). (define (setSeen loc) (map (lambda (a-city) (begin (set-city-seen?! a-city true) ;; set the seen? field to true a-city)) ;; return the city -- map will rebuild the list. loc)) ;; pathsToDest: list-of-cities --> list-of-list-of-cities ;; returns the list of the shortest paths from the supplied ;; list of cities, loc, to the dest. ;; All cities in loc are assumed to have their pathTo already set ;; i.e. non-empty (define (pathsToDest loc) (cond [(empty? loc) empty] ;; Done! -- nowhere to go! return empty path list [else (local [(define result ;; see if we have an answer at this stage (foldr (lambda (a-city rr) (cond [(eq? a-city dest) (append (city-pathsTo a-city) rr)] ;; we have a complete path! [else rr])) ;; keep looking empty ;; initial result is empty list loc))] (cond [(cons? result) result] ;; Done! -- Solution found. Return result. [(empty? result) (pathsToDest (getNextLevel loc))]))])) ;; not done, need recursive call to next level ;;getNextLevel: lict-of-cities --> list-of-cities ;; returns all the unseen neighbors of all the given cities. ;; Sets the given cities seen? to true. ;; Removes any duplicated cities from the returned cities. (define (getNextLevel loc) (removeDups (apply append (map get-unseen-nhbrs (setSeen loc)))))] ;;Create a dummy "zero'th" level city to start off the process. (pathsToDest (getNextLevel (list (make-city "" (list src) (list empty) false)))))) ; Alternative code for the above single line that doesn't reuse existing code. ; (cond ; [(eq? src dest) (list (list src))] ;; trivial sol'n -- we are at dest. Return simple list. ; [else ; (begin ;; non-trivial sol'n ; (set-city-pathsTo! src (list (list src))) ;; the path to the src it itself. ; (pathsToDest (getNextLevel (list src))))]))) ;; go via the neighbors ; ---- We need a function to reset all the mutated fields in the cities ---- ;; reset-cities: --> void ;; resets the pathTo field to empty ;; and seen? to false ;; in all the cities (define (reset-cities) (begin (set-city-pathsTo! nyc empty) (set-city-pathsTo! la empty) (set-city-pathsTo! hou empty) (set-city-pathsTo! slc empty) (set-city-pathsTo! reno empty) (set-city-pathsTo! nome empty) (set-city-seen?! nyc false) (set-city-seen?! la false) (set-city-seen?! hou false) (set-city-seen?! slc false) (set-city-seen?! reno false) (set-city-seen?! nome false)))
The code can be downloaded here: breadthpath.scm (a function to nicely display the results is also included -- check it out!)
Last Revised Tuesday, 24-Aug-2004 13:49:01 CDT
©2004 Stephen Wong and Dung Nguyen