2023-02-03 List and lists I stumbled across this text adventure called "Lists and Lists"[1] for the Z-machine[2] by Andrew Plotkin where a genie gives you coding problems and you have to solve them in a very restricted Scheme environment. The genie then checks your progress by running some tests with random inputs. It was a fun challenge so I decided to document my solutions. The text adventure part is practically non-existent. When you have gained acces to the computer (after opening the door, smashing the glass box and answering "yes") the game-loop looks this: turn on computer >>'(write scheme code) :q check There is a manual included which is very helpful because it's really a bare-bones Scheme implementation which even lacks a multiplication function. Of course I wrote most of the solutions in Emacs. I had the game running in an *ansi-term* buffer and simply pasted my code into it (C-c C-j enters the `term-line-mode` where all Emacs keybindings are present). Here it goes: ;; GENIE: Your first problem is just to acquaint you with the ;; system. Start up the machine, and define twentyseven to have the ;; value 27. You can ask me to 'check' when you're ready, or ;; 'repeat' the problem if you need me to. (define twentyseven 27) ;; GENIE: Let's try creating some lists. Define values for cat and ;; dog so that cat and dog are equal? but not eqv?. Furthermore, ;; cdr(cat) and cdr(dog) must be eqv?. (define dog '(5)) (define cat '(5)) ;; GENIE: Perfect! There are actually two ways to solve this ;; problem. You used the simpler one, using one-term lists. The ;; trickier solution would be something like this: ;; (define tail '(end)) ;; (define cat (cons 'head tail)) ;; (define dog (cons 'head tail)) ;; The cdrs are eqv? because they are both the thing defined on the ;; first line. See?" ;; GENIE: Define abs to be the absolute value function for integers. ;; That is, (abs 4) should return 4; (abs -5) should return 5; and ;; (abs 0) should return 0. (define abs (lambda (x) (cond ((< x 0) (- x)) (t x)))) ;; GENIE: Define sum to be a function that adds up a list of ;; integers. So (sum '(8 2 3)) should return 13. Make sure it works ;; correctly for the empty list; (sum nil) should return 0. (define cadr (lambda (x) (car (cdr x)))) (define sum (lambda (ls) (cond ((null? ls) 0) ((= (length ls) 2) (+ (car ls) (cadr ls))) (t (+ (car ls) (sum (cdr ls))))))) ;; GENIE: This problem is like the last one, but more general. ;; Define megasum to add up an arbitrarily nested list of integers. ;; That is, (megasum '((8) 5 (2 () (9 1) 3))) should return 28. (define megasum (lambda (ls) (cond ((null? ls) 0) ((not (list? (car ls))) (+ (car ls) (megasum (cdr ls)))) (t (+ (megasum (car ls)) (megasum (cdr ls))))))) ;; GENIE: Define max to be a function that finds the maximum of a ;; list of integers. So (max '(5 14 -3)) should return 14. You can ;; assume the list will have at least one term. (define max-rec (lambda args (let ((max-elem (car args)) (ls (cadr args))) (cond ((null? ls) max-elem) ((> (car ls) max-elem) (max-rec (car ls) (cdr ls))) (t (max-rec max-elem (cdr ls))))))) (define max (lambda (ls) (max-rec (car ls) ls))) ;; GENIE: Last problem. You're going to define a function called ;; pocket. This function should take one argument. Now pay attention ;; here: pocket does two different things, depending on the ;; argument. If you give it nil as the argument, it should simply ;; return 8. But if you give pocket any integer as an argument, it ;; should return a new pocket function -- a function just like ;; pocket, but with that new integer hidden inside, replacing the 8. ;; >>(pocket nil) ;; 8 ;; >>(pocket 12) ;; [function] ;; >>(define newpocket (pocket 12)) ;; [function] ;; >>(newpocket nil) ;; 12 ;; >>(define thirdpocket (newpocket 3)) ;; [function] ;; >>(thirdpocket nil) ;; 3 ;; >>(newpocket nil) ;; 12 ;; >>(pocket nil) ;; 8 ;; Note that when you create a new pocket function, ;; previously-existing functions should keep working. (define pocket-gen (lambda (x) (letrec ((f (lambda (y) (cond ((null? y) x) (t (pocket-gen y)))))) f))) (define pocket (lambda (a) (cond ((null? a) 8) (t (pocket-gen a))))) APPENDIX My first solution to `megasum` actually looked different. I was convinced that I had to use `sum` from the *last* problem to solve *this* problem so I had to define some common scheme functions not present in the environment. All the time I was thinking that my solution was over-engineered and sure it was. After thinking about it for a few days I just modified `sum` to work for nested lists. (define append (lambda args (let ((ls (car args)) (tail (cadr args))) (cond ((null? ls) tail) (t (cons (car ls) (append (cdr ls) tail))))))) (define flatten (lambda (ls) (cond ((null? ls) '()) ((list? (car ls)) (append (flatten (car ls)) (flatten (cdr ls)))) (t (cons (car ls) (flatten (cdr ls))))))) (define megasum (lambda (ls) (sum (flatten ls)))) The final problem actually took me a while to understand. I tried many approaches but always ended up where I began. Then I actually read the included manual page about `letrec` and came quite close to the solution but the tests still failed. I was a bit frustrated so I wrote this brain-dead version of the pocket function which only works for three nesting-levels. Since the genie doesn't check any further I finally beat the "game" but of course I was not yet satisfied. So a few days later I'm laying on the couch as I get this funny idea. I pull out my laptop, make a final change to my `letrec`-version of the pocket function and, lo and behold, it works. XD (define pocket (lambda (a) (cond ((null? a) 8) (t (lambda (b) (cond ((null? b) a) (t (lambda (c) (cond ((null? c) b) (t 'fuck)))))))))) Footnotes _________ [1] https://ifdb.org/viewgame?id=zj3ie12ewi1mrj1t [2] https://en.wikipedia.org/wiki/Z-machine I used the program `frotz` from the Ubuntu/Debian repository to run the game