Practice Exam 1

The four problems from Wednesday, September 16, with worked answers. Not marked.

The first half of Class 7, Wednesday, September 16. It was not marked and nothing about it reached your grade.

Progress Exam 1 is Monday, September 21 — same shape, same length of work, different problems.

Write the four functions again from a blank file, and only then scroll down to the answers. Recognizing an answer and producing one are different skills, and only the second one is on Monday’s exam.

How it was run

Forty-five minutes, on your own. Open book: this site, the textbook, Pyret’s documentation, and Pyret’s error messages. No AI and no talking.

Monday gives you fifty. If you are timing yourself on these four, take the fifty.

Every function needs a contract, a doc: line, and a where: block with examples you worked out yourself. A where: block with correct answers and a body you could not finish is worth most of a problem. Monday is marked the same way.

You do not need import lists as L. map, filter, fold, length, member, reverse, sort and range are all there as they are.

Problem 1 — a picture

Write banner. It takes one number, u, and gives back a picture: a green rectangle that is u tall and twice as wide, with a white square of side u / 2 centered on it.

overlay(front, back)                      # the first picture on top of the second
rectangle(width, height, mode, color)
square(side, mode, color)

Every measurement in the body comes from u, so that banner(10) and banner(40) are the same picture at two sizes.

Two examples is enough on this one.

Problem 2 — one question, or

Write ticket. It takes an age and whether the person is a member, and gives back the price in dollars:

  • a member pays 5, whatever their age
  • otherwise, someone under 12, or 65 and over, pays 8
  • otherwise, 15

Call the second parameter is-member. Not member — that is already the name of a list function and Pyret will not let you reuse it.

Write it as one if / else if / else. Write the middle case as one question using or, not as an if inside an if.

Four examples at least. Two of them are at exactly 12 and exactly 65, which is where a wrong < or >= shows up. One of them is a member who is also under 12 — that is the only example that catches the three cases being asked in the wrong order.

Problem 3 — filter, then map

Write over-budget. It takes a list of prices and gives back, for each price that is over 20, how far over 20 it is. A price of exactly 20 is not over budget.

over-budget([list: 5, 20, 25, 40])  is  [list: 5, 20]

Use filter and map together. Two passes, not one.

Three examples. One is a list where nothing survives the filter, and one contains exactly 20.

Problem 4 — by hand, with cases

Write total-width. It takes a list of units and gives back the total width of those stripes laid end to end. A stripe of unit u is u * 6 wide.

No library function that walks the list for you — not filter, not map, not fold, not length. Use cases.

Three examples, and the first one is the empty list. Write that one, and its answer, before you write any of the body.

Answers

Everything below runs as it stands on https://code.pyret.org/, and all fifteen examples pass.

2. ticket

fun ticket(age :: Number, is-member :: Boolean) -> Number:
  doc: "the price in dollars: members 5, under 12 or 65 and over 8, everyone else 15"
  if is-member:
    5
  else if (age < 12) or (age >= 65):
    8
  else:
    15
  end
where:
  ticket(30, true) is 5
  ticket(8, true) is 5
  ticket(8, false) is 8
  ticket(12, false) is 15
  ticket(65, false) is 8
  ticket(64, false) is 15
end

Check yours: is-member has to be asked first. Ask the age bands first and an eight-year-old member pays 8 — and every other example still passes, which is why the problem asked for a member who is also under 12 by name. Then check the boundaries: <= 12 in place of < 12 makes a twelve-year-old pay 8.

3. over-budget

fun over-budget(prices :: List<Number>) -> List<Number>:
  doc: "for each price over 20, how far over 20 it is"
  map(lam(p): p - 20 end, filter(lam(p): p > 20 end, prices))
where:
  over-budget([list: ]) is [list: ]
  over-budget([list: 5, 20, 25, 40]) is [list: 5, 20]
  over-budget([list: 3, 7]) is [list: ]
end

Check yours: exactly 20 must not reach the answer. If you subtracted before filtering, read your predicate again — it is being handed differences rather than prices, so it is asking a different question than the one written above. A named helper in place of lam is fine.

4. total-width

fun total-width(units :: List<Number>) -> Number:
  doc: "the total width of these stripes end to end, where a stripe of unit u is u * 6 wide"
  cases (List) units:
    | empty => 0
    | link(f, r) => (f * 6) + total-width(r)
  end
where:
  total-width([list: ]) is 0
  total-width([list: 4]) is 24
  total-width([list: 4, 10]) is 84
  total-width([list: 1, 2, 3]) is 36
end

Check yours: | empty => 0. A wrong empty branch spoils every example and not only the empty one — with empty => 1, total-width([list: 4]) comes out at 25. That is why the problem asked you to write the empty case first.

What to do with this before Monday

Write down the one you could not start. Not the one you got wrong — the one you did not know how to begin. Those are different problems and only the first one needs the weekend.

The postmortems — A1 and A2 — say what the room actually got wrong. How Pyret runs your program and how to design functions are the two pages worth having open on Monday.