We will follow the contents of the notes from '01 spring (pdf); the code shown in lecture has a couple of differences from the code in those notes:
The pdf notes use
ormap
, a function
which takes a function and a list,
applies the function to each element of the list successively (like
map
does, but as soon as that function returns a
non-false
it stops and returns that value.
It returns false
if
false
was returned for every item in the list.
The code given in lecture went ahead and did the same
thing in a different way: it used map
,
and then filter
ed away all the false
,
and then returned the first non-false
element
(or returned false
if
false
was returned for every item in the list).
path-v3
fixes this.
;; A name is a symbol (a city's name). ;; ;; A city is ;; (make-struct [name] [list-of names]) ;; (define-struct city (name neighbors)) ; Examples of cities: ; No routes at all: (define pre1905 (list (make-city 'hou '()) (make-city 'nyc '()) (make-city 'dc '()) (make-city 'la '()) (make-city 'slc '()) (make-city 'reno '()) (make-city 'sf '()) (make-city 'nome '()))) ; Routes but no cycles ; (no flights from a city to one preceding it alphabetically) (define dag (list (make-city 'dc '(nyc sf)) (make-city 'hou '(nyc la sf)) (make-city 'la '(nyc slc sf reno)) (make-city 'nome '()))) (make-city 'nyc '(sf)) (make-city 'reno '(slc)) (make-city 'slc '(sf)) (make-city 'sf '()) ; Routes with cycles: (define routes-1 (list (make-city 'slc '(nyc)) (make-city 'nyc '(slc)))) ; This inspires an even more basic test case: (define routes-0 (list (make-city 'slc '(slc)))) ; (Note that empty isn't a fun test case, since src ; and dest are requried to be valid city names.) (define routes-big (list (make-city 'hou '(la slc nyc)) (make-city 'nyc '(dc hou la slc sf)) (make-city 'dc '(dc hou nyc)) (make-city 'la '(sf reno)) (make-city 'slc '(reno)) (make-city 'reno '(slc)) (make-city 'sf '(slc)) (make-city 'nome '()))) ;; neighbors-of: symbol list-of-city --> list-of-symbol ;; Return the list of names of neighbors of "where". ;; Causes an error if symbol names no cities, or more than one city. ;; (define (neighbors-of where routes) (local [(define lookup-results (filter (lambda (c) (symbol=? where (city-name c))) routes))] (cond [(empty? lookup-results) (error 'neighbors-of (format "City ~s not in ~s~n." where routes))] [(cons? (rest lookup-results)) (error 'neighbors-of (format "City ~s occurs more than once in ~s.~n" where routes)]) ; Hopefully we always take this cond-branch: [else (city-neighbors (first lookup-results))]))) ;;(neighbors-of 'slc empty) ;(neighbors-of 'hou pre1905) = empty ;(neighbors-of 'hou dag) = '(nyc la sf) ;; path: symbol, symbol, list-of-city --> list-of-symbol or false. ;; Return false if no path possible; otherwise return a list of names ;; leading from src to dest (including both those endpoints). ;; (define (path src dest routes) (cond [(symbol=? src dest) (list src)] [else (local [(define other-ways (map (lambda (c1) (path c1 dest routes)) (neighbors-of src routes))) (define other-successes (filter list? other-ways))] (if (empty? other-successes) false (cons src (first other-successes))))])) ;;;;;; Lecture note: try this, but "(filter list ...)". ;;;;;; What is happening? ;;;;;; Apologize for using advanced level. (path 'hou 'nyc (list (make-city 'hou '()) (make-city 'nyc '()))) = false (path 'slc 'nyc (list (make-city 'slc '(nyc reno nyc)) (make-city 'reno '(nyc)) (make-city 'nyc '()))) = '(slc nyc) ;(path 'slc 'nyc routes) = false ;(path 'hou 'reno routes) = '(hou slc reno) ;; With accumulator -- not of the answer, ;; but of the cities previosly seen. ;; If we encounter a seen city, stop -- we alraedy know ;; no path from there (ish). ;; (define (path-v2 src dest seen routes) (cond [(symbol=? src dest) (list src)] [(member src seen) false] [else (let* {[other-ways (map (lambda (c1) (path-v2 c1 dest (cons src seen) routes)) (filter (lambda (n) (not (member n seen))) (neighbors-of src routes)))] [other-successes (filter list? other-ways)]} (if (empty? other-successes) false (cons src (first other-successes))))])) ;(path-v2 'slc 'nyc empty (list (make-city 'slc '(slc nyc reno nyc)) ; (make-city 'reno '(nyc slc nyc)) ; (make-city 'nyc '(reno slc nyc)))) ;= '(slc nyc) #| Note: there is a bug in this code; can you find it? Hint: make a city with a flight to itself (say, a sight-seeing tour). See if your test cases come up with a buggy answer! |# ;; path-v3: src:name dest:name seen:list-of-name routes:list-of-city ;; --> (list false-or-list-of-name list-of-name) ;; Like path, except that we return a list of cities in the solution (or false), ;; AND a list of cities seen so far. ;; We do this so we never visit the same city twice, even in ;; separate branches of a recursive call: ;; We keep an accumulator of seen-cities, as path-v2, ;; but we do more: When visiting the 2nd neighbor of a city, ;; we will pass it all the cities seen during the processing of ;; the first neighbor. How? Well, we need to return ;; two things: the solution-or-false, AND the list of cities ;; seen in toto. THat way, we use this part of the return value ;; from the first city, to pass it on to the second city. ;; (define (path-v3 src dest seen routes) (cond [(symbol=? src dest) (list (list src) (cons src seen))] [(member src seen) (list false (cons src seen))] [else (local [(define try (path-from-list (neighbors-of src routes) dest (cons src seen) routes)) (define soln-so-far (first try)) (define seen-more (second try))] (cond [(list? soln-so-far) ; Yay, we found it! (list (cons src (first try)) seen-more)] [(boolean? soln-so-far) ; Nope, no path; return false. (list false seen-more)] [else (error 'path-v3 "uh-oh: i goofed. Given ~s.~n" try)]))])) ;; path-from-list: nearby:list-of-name dest:name seen:list-of-name routes:list-of-city ;; --> (list false-or-list-of-name list-of-name) ;; A helper for path-v3: In the returned list, the first item is either ;; a list of names (from some city in nearby to dest), or false if no such city. ;; The second item returned is a list of cities seen altogether, ;; even in different branches of a recursive call. ;; ;; (define (path-from-list nearby dest seen routes) (cond [(empty? nearby) (list false seen)] [(cons? nearby) (local [(define try-first (path-v3 (first nearby) dest seen routes)) (define path-from-first (first try-first)) (define seen-from-first (second try-first))] (cond [(list? path-from-first) ; Yay, found path to dest from the first city! try-first] [(boolean? path-from-first) ; No luck from first city; try the next, but acknowledge seen-from-first. (path-from-list (rest nearby) dest seen-from-first routes)] [else (error 'path-from-list "uh-oh: i goofed. Given ~s.~n" try)]))])) (path-v3 'slc 'nyc empty (list (make-city 'slc '(slc nyc reno nyc)) (make-city 'reno '(nyc slc nyc)) (make-city 'nyc '(reno slc nyc)))) = '(slc nyc) (path-v3 'slc 'nyc empty routes)