@extends('template')
@section('title')
Lab 9: Part 4. Map and Filter
@stop
@section('head')
@stop
@section('content')
# Lab 9: Part 4. Map and Filter
A __higher order function (HOF)__ is a function that either:
+ A) take a function as a parameter, or
+ B) returns a function
In Python, the built-in functions `map` and `filter` are higher order functions because they each take a function as a parameter.
__Function signatures for [`map`](https://docs.python.org/3/library/functions.html#map) and [`filter`](https://docs.python.org/3/library/functions.html#filter) taken from [docs.python.org](https://docs.python.org/3/library/functions.html):__
```py
map(function, iterable, ...)
filter(function, iterable)
```
## Task 1. map
This semester we've seen how to perform map operations (doing the same thing to each item in the list) by using loops or list comprehensions.
Today, we'll explore this same kind of technique but we'll use Python's `map` function.
__Complete the following tasks in `lab08/mapFilter.py`__, where you'll find some predefined lists you can use for testing purposes.
### Task 1a. yell
Define a function `yell` that accepts a string and returns the string with the last letter repeated 5 times.
```py
yell('cat') # => 'catttttt'
```
When `yell` is used with `map`, it should produce results like this:
```py
map(yell, animals) # => ['catttttt', 'dogggggg', 'zebraaaaaa', 'pigggggg', 'bunnyyyyyy']
map(yell, superheroes) # => ['elektraaaaaa', 'firestarrrrrr', 'elastigirllllll', 'wonder womannnnnn', 'catwomannnnnn', 'batgirllllll']
```
### Task 1b. flip
Define a function `flip` that accepts a string and returns that string reversed.
```py
flip('cat') # => 'tac'
```
Hint: Recall the slicing syntax used to reverse a String: `'hello'[::-1] => 'olleh'`
When `flip` is used with `map`, it should produce results like this:
```py
map(flip, animals) # => ['tac', 'god', 'arbez', 'gip', 'ynnub']
map(flip, superheroes) # => ['artkele', 'ratserif', 'lrigitsale', 'namow rednow', 'namowtac', 'lrigtab']
```
### Task 1c. double
Most of our examples today will focus on Strings, but `map` also works with numbers.
Define a function named `double` that multiplies a given number x 2.
```py
double(2) # => 4
```
When `double` is used with `map`, it should produce results like this:
```py
map(double, [1, 2, 3, 4, 5]) # => [2, 4, 6, 8, 10]
```
## Task 2: Anonymous functions
The following is a typical Python function, defined using a style we've seen all semester:
```py
def pluralize(x):
return str(x) + 's'
```
In this example, `def` is used to define a function named `pluralize`.
Python provides a more succinct way of defining functions using something called __lambda notation__. The above function written in lambda notation would look like this:
```py
lambda x: str(x) + 's'
```
Observations:
+ The function is not named— this is why functions defined using lambda notation are referred to as __anonymous functions__.
+ The entire function is written on one line.
+ The body of the function, `str(x) + 's'`, is implicitly returned
(Refer to [Sorting and Lambda lecture notes, Slide #10](http://cs111.wellesley.edu/content/lectures/lec_sorting_lambda/files/lec_sorting_lambda.pdf) for more lambda notation information)
Anonymous functions are useful when working with higher order functions, where you often need a small helper function that you don't intend on using elsewhere in your code.
For example, instead of writing a definition of the function `flip` as you did above...
```py
def flip(s):
return s[::-1] # reverses a string
```
...you could use an anonymous function like this:
```py
map(lambda s: s[::-1], superheroes)
```
### Task 2a. Anonymous “yell”
Using lambda notation, complete the following code to produce the expected output:
```py
map(???, animals) # => ['catttttt', 'dogggggg', 'zebraaaaaa', 'pigggggg', 'bunnyyyyyy']
```
### Task 2b. Anonymous “flip”
Using lambda notation, complete the following code to produce the expected output:
```py
map(???, animals) # => ['tac', 'god', 'arbez', 'gip', 'ynnub']
```
## Task 3: Filter
Python's built-in `filter` function can be used to filter a sequence based on some provided criteria.
For example, we could filter the `animals` list to only include animals that contain the letter `a`.
### Task 3a. containsA
To do this, start by defining a helper function called `containsA` that returns a Boolean value as to whether a given String contains the letter `a`.
```py
containsA('cat') # => True
containsA('dog') # => False
```
When `containsA` is used with `filter`, it should produce results like this:
```py
filter(containsA, animals) # => ['cat', 'zebra']
filter(containsA, fruit) # => ['apple', 'banana', 'strawberry', 'pomegranate']
```
### Task 3b. mediumLength
Next, define a helper function called `mediumLength` that returns a Boolean value as to whether a given String is at least 4 characters long, but not longer than 7 characters long. In other words, the length of the string must be between 4 and 6, inclusive.
```py
mediumLength('cat') # => False
mediumLength('zebra') # => True
mediumLength('pillow') # => True
mediumLength('encyclopedia') # => False
```
When `mediumLength` is used with `filter`, it should produce results like this:
```py
filter(mediumLength, animals) # => ['zebra', 'bunny']
filter(mediumLength, fruit) # => ['apple', 'banana', 'cherry']
```
## Task 4: Filter with anonymous functions
Using lambda notation, complete the tasks to produce the expected output.
### Task 4a. Anonymous “containsA”
```py
# Find words that contain the letter a
filter(???, fruit) # => ['apple', 'banana', 'strawberry', 'pomegranate']
```
### Task 4b. Anonymous “mediumLength”
```py
# Find words that are between 4 and 6 characters long
filter(???, animals) # => ['zebra', 'bunny']
```
### Task 4c. Anonymous “mediumLengthWithA”
```py
# Find words that contain 'a' and are between 4 and 6 characters long
filter(???, animals) # => ['zebra']
```
## Task 5. Consecutive letters in a string
### Task 5a. consecLetters
To start, we want to define a function called `consecLetters` which accepts a String and returns `True` if it contains two consecutive letters that are the same; `False` otherwise.
```py
consecLetters('bunny')
# => True, b/c there are two consecutive letters: 'nn'
consecLetters('cat')
# => False, no consecutive letters
consecLetters('Jessica')
# => True, b/c there are two consecutive letters: 'ss'
```
We'll break this function down into three steps, so copy the following scaffold into your `lab08/mapFilter.py` file:
```py
def consecLetters(theString):
#### Step 1 ####
# Make pairs of all the letters
# [...Your code here...]
#### Step 2 ####
# Using `map`, create a list of True/False values for each pair, regarding whether it's a match (True) or not (False)
# [...Your code here...]
#### Step 3 ####
# Using `any`, return True if at least one of the pairs was a match (i.e. True)
# [...Your code here...]
```
Read through the comments to see the “big picture” of how `consecLetters` will work.
The following hints walk you through completing each step.
### Step 1 Hint
Use [`zip`](https://docs.python.org/3/library/functions.html#zip) to create pairs of neighboring letters in a word (which will be useful when searching for consecutive leters).
Example usage of zip:
```py
letterPairs = zip('bunny', 'bunny'[1:]) # => [('b', 'u'), ('u', 'n'), ('n', 'n'), ('n', 'y')]
```
### Step 2 Hint
If we think about what `consecLetters` is trying to accomplish, only one of the tuples in the list created by `zip` needs to contain a match in order for `consecLetters` to return True.
For example, `bunny` would qualify because at least one of the pairs has consecutive letters (`('n', 'n')`):
```py
# [('b', 'u'), ('u', 'n'), ('n', 'n'), ('n', 'y')]
```
To programmatically identify a matching pair, we could use for loop to look at each tuple, but since our focus is on higher-order-functions today, we'll use `map` to create a list of True/False values as to whether each pair is a match or not.
The following `map` function can be completed to accomplish this step. (It's up to you if you want to define a helper function or use `lambda` notation to create an anonymous function.)
```py
map(???, letterPairs) # => [False, False, True, False]
```
### Step 3 Hint
At this point, the results of the previous step should give you a list of Boolean values, indicating which letter pairs are matching (True) or not (False).
We only need **one** of those pairs to be matching in order to meet the requirements for “consecutive letters”, so we can call upon Python's built-in function [`any`](https://docs.python.org/3/library/functions.html) which returns `True` if at least one item in the list is `True`; Otherwise it returns `False`.
Example uses of `any`:
```py
any([True, False, False]) # => True
any([False, False, False, False, False]) # => False
any([False, False, False, True, False, True]) # => True
```
## Test consecLetters
Once you've completed the three steps in `consecLetters`, you can test your work:
```py
consecLetters('bunny') # => True
consecLetters('cat') # => False
consecLetters('book') # => True
```
## Task 5b.
With `consecLetters` working, we can now use it to filter a list of words down to only words that contain consecutive letters.
```py
list(filter(consecLetters, animals)) # => ['bunny']
list(filter(consecLetters, fruit)) # => ['apple', 'cherry', 'strawberry']
```
You can import a list of names of all CS111 students like this:
```py
from names import names
```
And then you can test as follows:
```py
list(filter(consecLetters, names))
```
should return this list:
```py
['Leen', 'Chantelle', 'Kerry', 'Jesslyn', 'Isabella', 'Hannah', 'Isabelle',
'Jisoo', 'Vanessa','Terri', 'Ann', 'Tiffany', 'Stella', 'Tyanna', 'Annie',
'Michelle', 'Michaella', 'Colleen', 'Pallavi', 'Jessica', 'Rebecca',
'Katianna', 'Jessica', 'Michaella', 'Leanne', 'Anna', 'Jessica']
```
@include('/labs/lab09/_toc')
@stop