The problem: The Shuffled Shopping List
A robot keeps its shopping list in a language called LISP. Its owner types three lines into the robot:
(SETQ BAG '(APPLE MILK BREAD JAM)) (DEF SWAP (L) (CONS (CADR L) (CONS (CAR L) (CDDR L)))) (CONS (CAR BAG) (SWAP (CDR BAG)))What is the value of the last line?
On an ACSL contest, the Intermediate and Senior papers ask exactly this kind of question: a few lines of LISP, and "what is the value of the final statement?" Right now these lines probably look like a jumble of brackets. By the end of this unit you will read them easily: Lesson 1 teaches how LISP writes a calculation, Lesson 2 how it takes lists apart and builds new ones, and Lesson 3 how it stores values in variables and defines new functions. At the end you will answer the robot's question by hand, and you will also write a Python program that does the same shuffle to any list.
-
Read atoms, lists and
NIL, and evaluate LISP calls from the innermost brackets outward. -
Use ACSL's arithmetic functions and the tests
EQ,POS,NEGandATOM. -
Take lists apart and build them with
CAR,CDR,CONS,REVERSEand shortcuts such asCADR. -
Trace programs that use
SETQ,SET,EVAL,DEFandDEFUN, and write list functions in Python.
-
Lesson 1 — Atoms, lists and arithmetic
Division: Intermediate and above.
LISP was invented in the 1950s by John McCarthy. Its name means LISt Processing, and everything in it is built from just two things.
-
An atom is a single item: a number such as
23or-4, or a word such asHELLO. -
A list is some items inside round brackets, separated by spaces:
(23 HELLO 821). An item of a list can itself be a list:(1 (2 3) 4)has three items, and the middle one is the list(2 3).
-
The list with nothing inside,
(), has a special name:NIL.NILis the only thing in LISP that counts as both an atom and a list.Every statement is a function call
A LISP statement is always a list whose first item names a function and whose other items are its arguments:
(function arg1 arg2 ... argn)The function comes first, before its arguments. So where you would write
2 + 3in maths, LISP writes(ADD 2 3).To find the value of a statement, LISP first finds the value of every argument, and then calls the function on those values. When an argument is itself a statement, you work it out first, so you always work from the innermost brackets outwards.
Example 1.
(MULT (ADD 1 2) (SUB 9 4))Rewrite it one step at a time, replacing each innermost statement by its value:
(MULT (ADD 1 2) (SUB 9 4)) (MULT 3 5) 15The value is
15. In Python you would write the same calculation with ordinary brackets:print((1 + 2) * (9 - 4))Scroll the code for more ↓
Output
15The arithmetic functions
Statement Value (ADD x1 x2 ...)the sum of all its arguments (MULT x1 x2 ...)the product of all its arguments (SUB a b)a − b (DIV a b)a ÷ b (SQUARE a)a × a (EXP a n)a to the power n ADDandMULTtake any number of arguments:(ADD 1 4 2 3)is10.SUBandDIVtake exactly two, and the order matters:(SUB 3 10)is-7, not7.ACSL also lets you write the first four with their usual symbols:
+forADD,-forSUB,*forMULTand/forDIV. So(* 2 (+ 1 1 1))is the same as(MULT 2 (ADD 1 1 1)), and its value is6.Example 2.
(ADD (SQUARE 3) (EXP 2 4) (- 1 5))(ADD (SQUARE 3) (EXP 2 4) (- 1 5)) (ADD 9 16 -4) 21Watch out:
(- 1 5)is1 − 5 = -4. The first argument is always the one you subtract from.print(3 * 3 + 2 ** 4 + (1 - 5))Scroll the code for more ↓
Output
21Division and exact answers
DIVgives the true quotient, so it can have a decimal part:(DIV 30 4)is7.5. When the quotient is a whole number, it is a whole number:(DIV 12 -4)is-3.This book writes every number answer the same way: a whole number with no decimal point (
-3, never-3.0), and anything else as a decimal (7.5,0.25). Contest questions are chosen so that the decimals always stop, like halves, quarters and eighths.Python's
/always gives a decimal, even when the answer is whole, so a program has to decide how to print it:print(30 / 4) print(12 / -4) a = 12 b = -4 if a % b == 0: print(a // b) else: print(a / b)Scroll the code for more ↓
Output
7.5 -3.0 -3a % b == 0means "bgoes intoaexactly". Thena // bgives the whole-number answer,-3, with no.0on the end.True or NIL: EQ, POS and NEG
Some functions answer a yes-or-no question. ACSL writes "yes" as
trueand "no" asNIL.Statement Value (EQ a b)trueif a and b are equal, otherwiseNIL(POS a)trueif a is positive (greater than 0), otherwiseNIL(NEG a)trueif a is negative (less than 0), otherwiseNILSo
(EQ (ADD 2 3) 5)istrue,(POS (SUB 4 4))isNIL(0 is not positive), and(NEG -7)istrue.A real LISP program prints
Tinstead oftrue. In this book, and on ACSL answer sheets, writetrue.Put it together: this program reads one LISP statement that uses
ADD,MULTorSUB, with only numbers as arguments, and prints its value. The input(ADD 4 7 1)prints12.line = "(MULT 2 -3 5)" inside = line[1:len(line) - 1] parts = inside.split() print(inside) print(parts)Scroll the code for more ↓
Output
MULT 2 -3 5 ['MULT', '2', '-3', '5']The slice
line[1:len(line) - 1]keeps everything except the first and last characters, which removes the two brackets. Thensplitgives the function name first and the numbers after it, still as text.The whole program is saved as
assets/l1.py.Try it line = input() inside = line[1:len(line) - 1] parts = inside.split() name = parts[0] numbers = [] for k in range(1, len(parts)): numbers.append(int(parts[k])) result = 0 if name == "ADD": for value in numbers: result = result + value elif name == "MULT": result = 1 for value in numbers: result = result * value elif name == "SUB": result = numbers[0] - numbers[1] print(result)Scroll the code for more ↓
Run it from this unit's folder:
python assets/l1.py < assets/l1/1.inA product starts at
1and a sum starts at0, so thatADDandMULTwork for any number of arguments. Tryassets/l1/2.inandassets/l1/3.inas well.Lesson 2 — Taking lists apart and building them
Division: Intermediate and above.
Quote: "take this as it is"
When LISP sees a list, it tries to call it as a function.
(RED WHITE BLUE)would mean "call the functionREDwith argumentsWHITEandBLUE", which is not what you want when you mean a list of three colours.A quote mark
'in front of something tells LISP: do not work this out, just take it as it is.-
'(RED WHITE BLUE)is the list of three atoms. -
'REDis the atomREDitself.
-
In the questions, every list of data has a quote in front of it. The quote is not part of the value: the value of
'(RED WHITE BLUE)is(RED WHITE BLUE).CAR and CDR
These two functions take a list apart.
-
(CAR x)is the first item of the listx. -
(CDR x)is the listxwithout its first item (say it "could-er").
Statement Value (CAR '(RED WHITE BLUE))RED(CDR '(RED WHITE BLUE))(WHITE BLUE)(CAR '((1 2) 3 4))(1 2)(CDR '((1 2) 3 4))(3 4)(CDR '(SOLO))NILWatch out:
-
CARgives an item, which can be an atom or a list.CDRalways gives a list. -
The
CDRof a list with one item is the empty list,NIL. -
In
((1 2) 3 4), the first item is the whole list(1 2), not the1.
-
CONS and REVERSE
These two build new lists.
-
(CONS x y)putsxat the front of the listy. The second argument is always a list. -
(REVERSE x)gives the items of the listxin the opposite order.
Statement Value (CONS 'A '(B C))(A B C)(CONS '(1 2) '(3 4))((1 2) 3 4)(CONS 'A NIL)(A)(REVERSE '(1 2 3))(3 2 1)(REVERSE '((1 2) 3 4))(4 3 (1 2))Watch out:
CONSadds one item, even when that item is a list:(CONS '(1 2) '(3 4))has three items, not four.REVERSEturns round only the top-level items; the list(1 2)inside stays(1 2).Example 3.
(CONS (CAR '(X Y Z)) (REVERSE (CDR '(1 2 3))))-
(CONS (CAR '(X Y Z)) (REVERSE (CDR '(1 2 3)))) (CONS X (REVERSE (2 3))) (CONS X (3 2)) (X 3 2)The value is
(X 3 2).Shortcuts: CADR and friends
Taking a
CARof aCDRis so common that LISP has a shortcut.(CADR x)means(CAR (CDR x)): the letters between theCand theRare the steps, and you do them from right to left, the letter next to theRfirst.Shortcut Means What it gives CADR(CAR (CDR x))the second item CDDR(CDR (CDR x))the list without its first two items CADDR(CAR (CDR (CDR x)))the third item CAAR(CAR (CAR x))the first item of the first item CDAR(CDR (CAR x))the first item, without its first item CDDAR(CDR (CDR (CAR x)))the first item, without its first two items Example 4.
(CDDAR '((5 6 7 8) 9))Read
CDDARfrom right to left: firstA(aCAR), thenD, thenD.CAR of ((5 6 7 8) 9) is (5 6 7 8) CDR of (5 6 7 8) is (6 7 8) CDR of (6 7 8) is (7 8)The value is
(7 8).ATOM
(ATOM x)istruewhenxis an atom andNILwhenxis a list.Statement Value (ATOM 'RED)true(ATOM 25)true(ATOM '(RED WHITE))NIL(ATOM (CAR '((1 2) 3)))NIL, because theCARis the list(1 2)(ATOM (CADR '((1 2) 3)))true, because theCADRis the atom3Lists in Python
A LISP list of atoms fits naturally into a Python list of strings.
CARis just the item at position 0.CDRis a new list holding every item after the first; build it with a loop andappend.items = ["RED", "WHITE", "BLUE"] first = items[0] rest = [] for k in range(1, len(items)): rest.append(items[k]) print(first) print(rest) print(items)Scroll the code for more ↓
Output
RED ['WHITE', 'BLUE'] ['RED', 'WHITE', 'BLUE']The loop starts at position 1, so the first item is skipped. The original list
itemsis not changed; LISP never changes a list when it takes itsCARorCDR, and neither should your program.CONSalso builds a new list: start a list holding the new first item, then append every item of the old list after it.items = ["WHITE", "BLUE"] result = ["RED"] for item in items: result.append(item) print(result)Scroll the code for more ↓
Output
['RED', 'WHITE', 'BLUE']To print a list the LISP way, as
(RED WHITE BLUE)with single spaces, glue the text together one item at a time. An empty list prints asNIL.def show(items): if len(items) == 0: return "NIL" text = "(" for k in range(len(items)): if k > 0: text = text + " " text = text + items[k] return text + ")" print(show(["RED", "WHITE", "BLUE"])) print(show(["SOLO"])) print(show([]))Scroll the code for more ↓
Output
(RED WHITE BLUE) (SOLO) NILThe space goes before every item except the first, so there is no extra space after the last item.
Put it together: this program reads a list of atoms, such as
(TOM ANN SAM), and prints itsCARon the first line and itsCDRon the second. It is saved asassets/l2.py.Try it def show(items): if len(items) == 0: return "NIL" text = "(" for k in range(len(items)): if k > 0: text = text + " " text = text + items[k] return text + ")" line = input() inside = line[1:len(line) - 1] items = inside.split() rest = [] for k in range(1, len(items)): rest.append(items[k]) print(items[0]) print(show(rest))Scroll the code for more ↓
Run it from this unit's folder:
python assets/l2.py < assets/l2/1.inIt prints
TOMand then(ANN SAM). Tryassets/l2/2.in, a list with one atom: itsCDRprints asNIL.Lesson 3 — Variables and new functions
Division: Intermediate and above.
SETQ and SET
SETQgives a variable a value. The value of theSETQstatement is the value it stores.Statement Value What happens (SETQ N (ADD 3 4))7Nnow holds7(SETQ COLORS '(RED WHITE BLUE))(RED WHITE BLUE)COLORSnow holds a list of 3 atoms(SETQ S '(ADD 3 4))(ADD 3 4)Sholds a list; the quote stops it being worked outAfter these lines, a variable name without a quote stands for its value:
(CAR COLORS)isRED, and(MULT N 2)is14.SETdoes the same job, but you must quote the variable's name yourself:(SET 'N 7)is the same as(SETQ N 7). (TheQinSETQstands for "quote": it quotes the name for you.)Watch out: quote or no quote makes all the difference.
(SETQ COLORS '(RED WHITE BLUE)) (CAR COLORS) (CAR 'COLORS)(CAR COLORS)isRED, becauseCOLORSis replaced by its value.(CAR 'COLORS)is an error:'COLORSis just the atomCOLORS, and an atom has noCAR. In the same way,(ATOM COLORS)isNIL(its value is a list), but(ATOM 'COLORS)istrue(the name itself is an atom).EVAL: work it out one more time
(EVAL x)takes the value of its argument and works it out again.(SETQ S '(ADD 3 4)) (EVAL S)The argument
Sis worked out first, as always, giving the list(ADD 3 4). ThenEVALworks out that list as a statement, giving7.Sitself still holds the list(ADD 3 4);EVALdoes not change it.The same idea works with names. If
Pholds5andWholds the atomP, thenWis worked out toP, and(EVAL W)works outPto get5.DEF and DEFUN: new functions
DEFdefines a new function. ACSL also writes it asDEFUN; the two mean the same thing.(DEF FINAL (L) (CAR (REVERSE L)))This defines a function called
FINALwith one parameter,L. Its body is(CAR (REVERSE L)). To work out(FINAL '(4 5 6)), put the argument in place ofLeverywhere in the body and work that out:(FINAL '(4 5 6)) (CAR (REVERSE (4 5 6))) (CAR (6 5 4)) 6A function can have several parameters, and one function can call another:
(DEFUN HALF (X) (DIV X 2)) (DEFUN AVG (A B) (HALF (ADD A B))) (AVG 4 7)(AVG 4 7)is(HALF (ADD 4 7)), which is(HALF 11), which is(DIV 11 2), which is5.5.Python has the same idea:
DEFin LISP isdefin Python.def half(x): return x / 2 def avg(a, b): return half(a + b) print(avg(4, 7))Scroll the code for more ↓
Output
5.5Tracing a short program
A contest question gives several statements in order and asks for the value of the last one. Work through them from top to bottom, and keep a small table of what each variable holds, exactly as when you trace a Python program.
Example 5.
(SETQ X '(2 (3 4) 5)) (SETQ Y (CADR X)) (DEF F (A B) (MULT A (SUB B 1))) (F (CAR Y) (CADDR X))After line XY1 (2 (3 4) 5)2 (2 (3 4) 5)(3 4)Line 3 only defines
F. In line 4,(CAR Y)is3and(CADDR X)is5, so the value is(F 3 5)=(MULT 3 (SUB 5 1))=(MULT 3 4)=12.Back to the robot
Now the robot's question is easy.
(SETQ BAG '(APPLE MILK BREAD JAM)) (DEF SWAP (L) (CONS (CADR L) (CONS (CAR L) (CDDR L)))) (CONS (CAR BAG) (SWAP (CDR BAG)))SWAPputs the second item first, then the first item, then everything after the second: it swaps the first two items.(CDR BAG) is (MILK BREAD JAM) (SWAP '(MILK BREAD JAM)) is (CONS BREAD (CONS MILK (JAM))) is (BREAD MILK JAM) (CONS APPLE (BREAD MILK JAM)) is (APPLE BREAD MILK JAM)The value is
(APPLE BREAD MILK JAM): the robot swapped the second and third items and left the rest alone.Put it together: the same shuffle in Python. Each LISP function becomes a small Python function that returns a new list, and
swapis built from them exactly asSWAPis built in LISP. The program reads a list of at least three atoms and prints the value of(CONS (CAR BAG) (SWAP (CDR BAG)))for it. It is saved asassets/l3.py.Try it def show(items): if len(items) == 0: return "NIL" text = "(" for k in range(len(items)): if k > 0: text = text + " " text = text + items[k] return text + ")" def car(items): return items[0] def cdr(items): rest = [] for k in range(1, len(items)): rest.append(items[k]) return rest def cons(first, items): result = [first] for item in items: result.append(item) return result def swap(items): return cons(car(cdr(items)), cons(car(items), cdr(cdr(items)))) line = input() bag = line[1:len(line) - 1].split() print(show(cons(car(bag), swap(cdr(bag)))))Scroll the code for more ↓
Run it from this unit's folder:
python assets/l3.py < assets/l3/1.inIt prints
(APPLE BREAD MILK JAM), the answer you worked out by hand. Tryassets/l3/2.inandassets/l3/3.intoo.cdr(cdr(items))is the Python version ofCDDR: the inner call runs first, just as the letter next to theRcomes first.-
Every LISP statement is a function call: the first item names the function, and the rest are its arguments.
-
A quote means "take this as it is", so a quoted list is data, not a call.
-
CARis the first item;CDRis a new list of everything after it, and theCDRof a one-item list isNIL. -
SETQstores a value,DEFandDEFUNmake new functions, and a variable table keeps your trace straight.
-
You can now find the value of a short LISP program, such as the shopping list's
(APPLE BREAD MILK JAM).