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 |