Class summary:   Processing Lists
1 Summing a list of numbers
2 Checking all Numbers Less than 10
2.1 The Case-based Definition of Lists
3 Leveraging Cases to Write Our Functions

Class summary: Processing Lists

Copyright (c) 2017 Kathi Fisler

Throughout this course, we’ve written programs that leverage the structure of data. We started with images, showing that the structure of code that builds a piece of data reflects the structure of the datum itself. We moved onto tables, writing programs to process the rows and cells that make up tables.

Now we have lists. Last lecture, we learned several built-in functions for processing lists. But the operations we learned only cover some of the computations we might want to do with lists. For example, what if we wanted the sum of a list of numbers? Or if we wanted to make sure all numbers in a list are less than 10. Neither L.map nor L.filter, which return lists, can be used alone to write functions that return numbers or booleans. Sometimes, we will need to write our own functions to process or aggregate lists. Today, we start learning how to do that.

This material is covered very nicely in a chapter of a textbook by Brown CS Professor Shriram Krishnamurthi. These notes will highlight unique features of how we covered this material in class, but details will be in chapters 6.1 through 6.4.2 of PAPL chapter 6 (which we followed fairly closely in class), as well as in the lecture capture.

1 Summing a list of numbers

Imagine that we had written a function called my-sum (sum is built-in, so we need to use a different name). What might the where: block look like?

  fun my-sum(lst :: List<Number>) -> Number:

    ...

  where:

     my-sum([list: 4, 7, 5]) is 4 + 7 + 5

     my-sum([list: 7, 5]) is 7 + 5

     my-sum([list: 5]) is 5

     my-sum([list:]) is 0

  end

Stare at these examples – do you notice a pattern?

Yes, each where answer could be written using the line below it:

  where:

     my-sum([list: 4, 7, 5]) is 4 + my-sum([list: 7, 5])

     my-sum([list: 7, 5]) is 7 + my-sum([list: 5])

     my-sum([list: 5]) is 5 + my-sum([list:])

     my-sum([list:]) is 0

  end

Do you see the pattern here? When we want to sum a list, we can add the first item on the list to the sum of the list with everything except the first item (that list is called the rest of the list). If we had an easy way to get at the first and rest parts of a list, our examples guide us to writing the function.

2 Checking all Numbers Less than 10

Let’s write the where clause for another problem:

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

    ...

  where:

     all-below-10([list: 8, 2, 6]) is

       (8 < 10) and (2 < 10) and (6 < 10)

     all-below-10([list: 2, 6]) is (2 < 10) and (6 < 10)

     all-below-10([list: 6]) is (6 < 10)

     all-below-10([list:]) is true

  end

Which, substituting subexpressions, yields:

  where:

     all-below-10([list: 8, 2, 6]) is

       (8 < 10) and all-below-10([list: 2, 6])

     all-below-10([list: 2, 6]) is

       (2 < 10) and all-below-10([list: 6])

     all-below-10([list: 6]) is

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

     all-below-10([list:]) is true

  end

2.1 The Case-based Definition of Lists

See section 6.1 for details.

3 Leveraging Cases to Write Our Functions

Lecture capture shows how we built-up these final solutions piece-by-piece.

  fun my-sum(lst :: List<Number>) -> Number:

    cases (List) lst:

      | empty => 0

      | link(fst, rst) => fst + my-sum(rst)

    end

  where:

    my-sum([list: 4, 7, 5]) is 4 + 7 + 5

    my-sum([list: 7, 5]) is 7 + 5

    my-sum([list: 5]) is 5

    my-sum([list:]) is 0

  end

  

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

    cases (List) lst:

      | empty => true

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

    end

  where:

     all-below-10([list: 8, 2, 6]) is

       (8 < 10) and (2 < 10) and (6 < 10)

     all-below-10([list: 2, 6]) is (2 < 10) and (6 < 10)

     all-below-10([list: 6]) is (6 < 10)

     all-below-10([list:]) is true

  end