123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- (import
- (except (rnrs base) let-values map)
- (only (guile)
- lambda* λ))
- ;;; ============
- ;;; TWO IN A ROW
- ;;; ============
- ;; A function shall be defined, which determines, whether there are 2
- ;; subsequent elements inside a given list, for which ~eq?~ is ~#t~.
- ;; CODE 1:
- ;; (define is-first?
- ;; (λ (a lat)
- ;; (cond
- ;; [(null? lat) #f] ; check for empty list
- ;; [else (eq? a (car lat))])))
- ;; (define two-in-a-row?
- ;; (λ (lat)
- ;; (cond
- ;; [(null? lat) #f] ; check for empty list
- ;; [else
- ;; (or (is-first? (car lat) (cdr lat))
- ;; (two-in-a-row? (cdr lat)))])))
- ;; However, this version obscures, that in one case it performs a
- ;; check twice. When it checks, whether the list is ~null?~ in
- ;; ~is-first?~ and returns ~#f~, ~two-in-a-row?~ will recur and call
- ;; itself. Then it will check for a second time, whether the list it
- ;; receives is ~null?~.
- ;; To avoid this duplicate check, the book suggests to leave the
- ;; decision of whether to recur or not to the function, which checks
- ;; for an empty list first. That is ~is-first?~.
- ;; Let us try to leave that responsibility to ~is-first?~:
- ;; CODE 2:
- ;; (define is-first?
- ;; (λ (a lat)
- ;; (cond
- ;; [(null? lat) #f]
- ;; [else
- ;; (or (eq? a (car lat))
- ;; ;; Not (cdr lat) here! is-first? is already given the cdr of
- ;; ;; the list and the argument ~a~ is the actual car of the
- ;; ;; list! The predicate ~is-first?~ does not reduce the
- ;; ;; input. That is a responsibility of ~two-in-a-row?~.
- ;; (two-in-a-row? lat))])))
- ;; (define two-in-a-row?
- ;; (λ (lat)
- ;; (cond
- ;; [(null? lat) #f]
- ;; [else
- ;; (is-first? (car lat) (cdr lat))])))
- ;; Now we have two functions which are mutually recursive.
- ;; NOTE: There is also a problem of (two-in-a-row? lat) in ~is-first?~
- ;; not being in the tail position and thus growing the stack each
- ;; call. (two-in-a-row? lat) is inside the (or ...) expression, which
- ;; still needs to be kept on the stack, so that is-first? can return
- ;; the result of (or ...).
- ;; NOTE: There is still a duplicate check of (null? lat), because when
- ;; ~two-in-a-row?~ is called from ~is-first?~, it checks again,
- ;; whether the list is empty.
- ;; NOTE: is-first? decides, whether to recur or not.
- ;; OBSERVATION: When we look at what ~two-in-a-row?~ actually does,
- ;; when called from ~is-first?~, we can see, that it does not actually
- ;; do much. The ~null?~ check for the empty list will always be ~#f~,
- ;; because the same thing was already checked in ~is-first?~.
- ;; OBSERVATION: ~lat~ is given to ~two-in-a-row?~. So ~two-in-a-row?~
- ;; will always enter the ~else~ branch, when called from ~is-first?~
- ;; and the ~null?~ check is only useful, when ~two-in-a-row?~ is
- ;; initially called from outside of ~is-first?~.
- ;; CONCLUSION: This observation leads to the conclusion, that one
- ;; could write a single function, by modifying ~is-first?~ a little
- ;; more, so that the call to ~two-in-a-row?~ is no longer needed.
- ;; The modified version of ~is-first?~ will be renamed to
- ;; ~two-in-a-row?~ to express what it does.
- ;; CODE 3:
- ;; (define two-in-a-row?
- ;; (λ (preceding lat)
- ;; (cond
- ;; [(null? lat) #f]
- ;; [else
- ;; (or (eq? preceding (car lat))
- ;; (two-in-a-row? (car lat) (cdr lat)))])))
- ;; NOTE: There is still a problem here: ~two-in-a-row?~ receives
- ;; always 2 arguments. This is inconvenient to work with and
- ;; unexpected at the caller side. One can use ~two-in-a-row?-helper~
- ;; to avoid this.
- ;; NOTE: The problem of non-tail position of the call to
- ;; ~(two-in-a-row? (car lat) (cdr lat))~ still persists.
- ;; CODE 4:
- ;; (define two-in-a-row?-helper
- ;; (λ (preceding lat)
- ;; (cond
- ;; [(null? lat) #f]
- ;; [else
- ;; (or (eq? preceding (car lat))
- ;; (two-in-a-row?-helper (car lat) (cdr lat)))])))
- ;; (define two-in-a-row?
- ;; (λ (lat)
- ;; (cond
- ;; [(null? lat) #f]
- ;; [else
- ;; (two-in-a-row?-helper (car lat) (cdr lat))])))
- ;; This clears up the interface for the user.
- ;; A tail call optimized version, getting rid of the non-tail position
- ;; of the recursive call, could be written as follows:
- ;; CODE 5:
- (define two-in-a-row?-helper-tail-recursive
- (λ (preceding lat)
- (cond
- [(null? lat) #f]
- ;; It is a bit meh, that we cannot simply return the result of the
- ;; expression here and need to write #t instead. Perhaps there is a better
- ;; way?
- [(eq? preceding (car lat)) #t]
- [else
- ;; Tail-recursive.
- (two-in-a-row?-helper-tail-recursive (car lat) (cdr lat))])))
- (define two-in-a-row?
- (λ (lat)
- (cond
- [(null? lat) #f]
- [else
- (two-in-a-row?-helper-tail-recursive (car lat) (cdr lat))])))
- ;; USAGE:
- (display
- (simple-format
- #f "(two-in-a-row? '(1 2 2 3 4)) -> ~a\n"
- (two-in-a-row? '(1 2 2 3 4))))
- (display
- (simple-format
- #f "(two-in-a-row? ''(1 2 3 4 5)) -> ~a\n"
- (two-in-a-row? '(1 2 3 4 5))))
|