Class summary:   Processing Lists Practice
1 all-below-10
2 multi-stripe-flag

Class summary: Processing Lists Practice

Copyright (c) 2017 Kathi Fisler

We worked through two problems today to practice writing programs that take lists and produce simple values. Details of how we developed these are in the lecture capture.

1 all-below-10

Write a function that takes a list of numbers and returns a Boolean indicating whether every number in the list is smaller than 10.

  fun all-below-10(lst :: List<Number>) -> Boolean:

    doc: "Determine whether all numbers are below 10"

    cases (List) lst:

      | empty => true

      | link(fst, rst) => (fst < 10) and all-below-10(rst)

    end

  where:

    # an example on a specific list

    all-below-10(link(3, link(8, link(2, empty))))

      is (3 < 10) and (8 < 10) and (2 < 10)

    # an example on the rest of that list

    all-below-10(link(8, link(2, empty)))

      is (8 < 10) and (2 < 10)

    # rewriting the first example, using the second to show

    #   the call on the rest

    all-below-10(link(3, link(8, link(2, empty))))

      is (3 < 10) and all-below-10(link(8, link(2, empty)))

  end

One interesting discussion here concerned the result of true in the empty case. Let’s use an example to see why we have to return true rather than false. We’ll take a function and expand out the code as we evaluate a call to all-below-10:

  all-below-10([list: 7, 8])

  (7 < 10) and all-below-10([list: 8])

  (7 < 10) and (8 < 10) and all-below-10(empty)

The result of all-below-10(empty) cannot interfere with the result on the items in the list. Since both 7 and 8 are less than 10, the function should return true on this list. If all-below-10(empty) returns false, the entire and expression will return false. To avoid interfering with the computation of and, we must return true in the empty case.

By analogous reasoning, if the computation involves or, we must return false in the empty case.

2 multi-stripe-flag

Remember that we started out with producing images of flags? At the start of the course, we wrote a function to produce a flag with three stripes:

  fun three-stripe-flag(top :: String, middle :: String, bot :: String) -> Image:

    doc: "produce image of three equal-height horizontal stripes"

    above(rectangle(120, 30, "solid", top),

      above(rectangle(120, 30, "solid", middle),

        rectangle(120, 30, "solid", bot)))

  end

What if wanted to allow an arbitrary number of stripes? We would take the sequence of stripes in as a list. (Don’t worry about scaling the stripe heights for now). Here’s the list-based version:

  fun multi-stripe-flag(colors :: List<String>) -> Image:

    doc: ```produce flag with horizontal stripes in given colors

            from top to bottom```

    cases (List) colors:

      | empty => rectangle(1, 1, "solid", "white")

      | link(fst, rst) =>

        above(rectangle(120, 30, "solid", fst),

          multi-stripe-flag(rst))

    end

  where:

    # Normally, we don't write wheres for functions that return images,

    # but I'm doing it here to illustrate the pattern of building

    # the image

    multi-stripe-flag([list: "red", "blue", "orange"])

      is

    above(rectangle(30, 100, "solid", "red"),

      multi-stripe-flag([list: "blue", "orange"]))

  end

Here, we returned a tiny, nearly invisible image in the empty case. There actually is a way to have an empty image in Pyret, but we didn’t have that code on hand, so we wrote it this way instead. If that little rectangle bothers you, trust that we could indeed have eliminated it.

The key thing to note here is that the nested sequence of above expressions that we wrote in the three-stripe version is precisely what we get from the multi-stripe version – if you unroll the calls to multi-stripe-flag, you’ll see that we get the same pattern of nested above expressions as we wrote manually. This is one of the beauties of writing functions that call themselves in this way – they expand repeated patterns for us without us having to manually replicate code.