Review of Map and Filter

Map and filter are two built-in looping constructs in Scheme; both
consume a function and a list, but each performs a different operation
on the list. Filter, as its name suggests, creates a list of
all elements on which the function returns true, while map
calls the given function on every element of the list, returning a new
list of the results.

Okay, but what's interesting about map and filter? Why are
they any better/worse/different from loops in other languages?

They accept functions as arguments, which is different from the
loops you've written before. Remember: functions are just a way of
giving names to computations. Map and filter simply take the
computation to do in the loop as an argument. Using map and filter
makes your code cleaner, because you don't interleave the code for
running the loop with the code that you perform at each step of the
loop.

Think about a usual while for processing a loop in C++: you have to
write down both the code to walk along the list and check whether
you're at the end of the list, as well as the code that you want to
perform on each element. Map and filter capture the common parts of
walking down the list into one function, leaving you to write only the
part that is different for your particular loop (as a function).

The names of the looping operators tell you something about the
computation performed. If you see filter in your code, you know you
are trying to eliminate/keep certain elements. If you see map, you
know you are trying to modify all of the elements. They go a long way
to making programs more readable.

Map and filter demonstrate an important attribute of Scheme:
you can define your own loops!. If I give you a problem that
requires a different kind of loop than map or filter, you can write a
function (your new looping construct) that does the kind of traversal
you need and takes a function for the work to do within the
traversal. You can define loops over trees, arrays, or any other data
structure that you create. This is very useful!

In software engineering terminology, these loops over particular
data structures are called iterators -- defining iterators goes
a long way to improving program design, and most modern languages give
you some facilities for defining iterators. Some are more cumbersome
than others, though. Scheme's is pretty lightweight, once you get the
hang of it.

The text has a lot more information on building custom iterators
and the process by which you define an iterator for particular pairs
of functions. See Chapter 22 of
HtDP for this material.

Practice Exercises with Map and Filter

Imagine a travel agency that maintains two databases: one of
information about cities and one about flights between cities.

;; A city is a (make-city symbol list[symbol])
(define-struct city (name features))
(make-city 'new-york (list 'music 'museums 'baseball 'strong-accents))
;; A flight is a (make-flight symbol symbol number list[number])
(define-struct flight (from to number dates))
(make-flight 'boston 'new-york 675 (list 10 13))
;; this example says that flight 675 from boston to new-york
;; operates on the 10th and 13th of each month

Assume you have lists of cities and flights. Using these lists, you
can write several functions to help implement a travel agency. As you
work through these exercises, ask yourself which ones can be written
with map/filter, which ones cannot, and which ones require nested
lambdas instead of externally-defined functions. If you can write
these and know why you wrote them as you did, you're in good shape
with map and filter.

Assume that flights is a list of flight structs and cities is a
list of city structs. Give an English description of what each of the
following expressions computes: