set-[struct]-[field]!
(define-struct [struct] (... [field] ...))Then there is a corresponding function
(set-[struct]-[field]! [a-struct-expr] exp)
Semantics of make-[struct]
:
Instead of going off and creating a chest-of-drawers with the
indicated contents and returning that chest-of-drawers (the structure),
we refine this:
make-[struct]
creates the structure but returns a reference to that
structure.
References are a new type of value;
in particular we don't pass structures to other functions,
but only references to structures.
(We have no way of ever getting hold of the structure itself;
most of the time we'll just say "the structure ..." rather
than "a reference to the structure...", for convenience.)
When hand-evaluating
make-[struct]
,
we will
draw a box (the created structure), and draw a signpost with
an arrow referring to the box (the reference).
(Sometimes, instead of the box, we'll just write the
make-[struct]
, and draw the reference to it.)
(If in full-text mode, we'll write
(define somereference^ (make-struct ...))and understand that
somereference^
is the
equivalent of the arrow referring to that make-struct.
Example:
(define-struct cat (name age)) (make-cat 'morris 93)) (define cat2 (make-cat 'garfield 87)) (define cat3 cat2)will hand-evaluate to:
(make-cat cat1 (make-cat 'morris 93)) = (define randomcat^ (make-cat 'morris 93)) (define cat2 (make-cat 'garfield 87)) = { (define somecat^ (make-cat 'garfield 87)) ; make-cat created the struct, { ; and made the reference to it. { (define cat2 somecat^) ; Make-cat returned a reference. (define cat3 cat2) = (define cat3 somecat^) ; Evaluate cat2, as usual: it's a placeholder; ; just look up the placeholder's define'd value.Note that the
(define somecat^ ...)
is a bit of
abuse of notation: it's not a real define (since we can only define
placeholders, but the somecat^
we said is a reference
value (a signpost).
Evaluate [exp]
to get val,
evaluate [a-struct-expr]
to get strct
and then follow the reference strct
(it had better be (a reference to) a struct of type
[type]
, of course),
and modify the [field]
to be val.
Note that no placeholders were modified in this process.
set-[struct]-[field]!
Call this a constructor (make-[struct]
)
once for each corresponding thing you're modeling.
That is, if you're working at a vet clinic and you have two separate cats
each named 'morris
and age 93
,
then have two calls to make-cat
.
(If you are repeatedly seeing the same cat,
only create it once and use that same value in the future:
either name it with a placeholder, or keep a list of cat structures
(retrieving the right cat from the list as needed, etc.)
Call the mutator
set-[struct]-[field]!
set-cat-age!
to modify the age
field.)
So, what does the hand-evaluation of
(cons 3 (cons 4 (cons 5 empty)))look like? How about for
(cons (make-cat 'morris 93) (cons (make-cat 'garfield 87) empty))What if , as in previous lectures, cats had an additional field
bestbuddy
refering to a cat?
Exercise: make the smallest example of you can,
of a structure which somehow contains a reference to itself.
How does it print out?
Remember shared
from last lecture.
Now, we can make our map of cities properly, no?
(define-struct city (name nhbrs seen?)) ; Initially no neighbors: (define nyc (make-city 'nyc empty false)) (define la (make-city 'la empty false)) (define slc (make-city 'slc empty false)) (define hou (make-city 'houston empty false)) (define nome (make-city 'nome empty false)) (define reno (make-city 'reno empty false)) (define cities (list nyc la slc hou nome reno)) (set-city-nhbrs! nyc (list la slc hou nyc)) (set-city-nhbrs! la (list reno slc nyc)) (set-city-nhbrs! hou (list nyc la)) (set-city-nhbrs! slc (list reno)) (set-city-nhbrs! reno (list slc))
What is our algorithm for searching?
;; path: city, city --> list-of-city or false ;; Return false if no path, otherwise return ;; a path which includes both endpoints. ;; (define (path src dest) (cond [(eq? src dest) (list src)] [else (local [(define otherway (path-from-list (city-nhbrs src) dest))] (if (list? otherway) (cons src otherway) false))])) ;; path-from-list: list-of-city, city --> list-of-city or false ;; Return either a path from one of srcs to dest, ;; or false if no such path exist from anybody in srcs. ;; (define (path-from-list srcs dest) (cond [(empty? srcs) false] [else (local [(define try-first (path (first srcs) dest))] (if (list? try-first) try-first (path-from-list (rest srcs) dest)))])) (path slc nyc) = ;; Uh-oh, runs forever! (as discussed in lects 24-27) ;; Hand-evaluation reveals ;; It's checking slc, reno, slc, reno, slc, ...N.B. In the class example from lects 24-27, Instead of a separate function
path-from-list
,
we just map
ped "find a path to dst" on each
of the cities neighbors,
and then grabbed the first element from all these solutions
(or, checked if map returned a list containing only false
from each neighbor).
Same diff.
What do we need to do, to avoid loops?
;; path: city, city --> list-of-city or false ;; Return false if no path, otherwise return ;; a path which includes both endpoints. ;; (define (path src dest) (cond [(eq? src dest) (list src)] [(city-seen? src) false] ; Already visited, so don't go here again. [else (begin (set-city-seen?! src false) (local [(define otherway (path-from-list (city-nhbrs src) dest))] (if (list? otherway) (cons src otherway) false))])) (find-path slc nyc) = false (find-path nyc slc) = ; nyc -> la -> slc, printed in "shared" form. ; Note, it's not the shortest path.This approach is called "depth-first search": it searches the first neighbor, and the first of that neighbor, and ... and backtracks when it reaches a dead end (or something seen before).
Missionaries and Cannibals, on the other hand, is a fundamentally *different* way of finding a path from one place to another. "breadth-first search" (look at all places one step away from src, then two steps away, then three steps, ...) *does* always return a shortest path (can you seen why?).
(define-struct city (name nhbrs seen?)) ; Initially no neighbors: (define nyc (make-city 'nyc empty false)) (define la (make-city 'la empty false)) (define slc (make-city 'slc empty false)) (define hou (make-city 'houston empty false)) (define nome (make-city 'nome empty false)) (define reno (make-city 'reno empty false)) (define cities (list nyc la slc hou nome reno)) (set-city-nhbrs! nyc (list la slc hou nyc)) (set-city-nhbrs! la (list reno slc nyc)) (set-city-nhbrs! hou (list nyc la)) (set-city-nhbrs! slc (list reno)) (set-city-nhbrs! reno (list slc))
What is our algorithm for searching?
;; path: city, city --> list-of-city or false ;; (define (path src dest) (cond [(eq? src dest) (list src)] [else (local [(define otherway (path-from-list (city-nhbrs src) dest))] (if (list? otherway) (cons src otherway) false))])) (define (path-from-list srcs dest) (cond [(empty? srcs) false] [else (local [(define try-first (path (first srcs) dest))] (if (list? try-first) try-first (path-from-list (rest srcs) dest)))])) ;; Okay, how to stop loops? ;; path: city, city --> list-of-city or false ;; ;; Implementation note: ;; As previous version, but ;; (a) mark a city when seen, and ;; (b) coming across a marked city means we're trivially done: ;; don't proceed further from this city. ;; (define (path src dest) (cond [(eq? src dest) (list src)] [(city-seen? src) false] [else (begin (set-city-seen?! src true) (local [(define otherway (path-from-list (city-nhbrs src) dest))] (if (list? otherway) (cons src otherway) false))])) #| 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! |#