Check your understanding of the previous reading: how values flow between loop functions, why we compose loops, and which compositions make sense.

Question 1

Given the program below,

messages = ["go", "fly", "a", "kite"]

def join_strings(messages: list[str]) -> str:
    result = ""
    for message in messages:
        result = result + ", " + message
    return result

def map_title_case(messages: list[str]) -> list[str]:
    result = []
    for message in messages:
        result.append(message.title())
    return result

# ____ <-- What goes here?

print(messages)

Which of the following lines of code should be put in the blank to make the strings Title Cased and joined together with commas?

Select every option that applies, then check.

  • Not quite. Both calls return new values, but nothing stores them: messages still holds the original lowercase list, so print(messages) shows it unchanged.
  • Correct. The title-cased list that map_title_case returns goes straight into join_strings, and the joined string is stored back in messages before it is printed.
  • Not quite. Both functions have a messages parameter, so calling them without an argument raises a TypeError.
  • Not quite. The composition is right, but its result is thrown away: messages is never updated, so the original list is printed.
  • Not quite. This prints the title-cased list, then the original lowercase strings joined together, and the final print(messages) still shows the original list.
  • Correct. The same composition in two steps: messages first holds the title-cased list, then the joined string.
  • Not quite. The messages parameters are local to each function; the global messages only changes when you assign a function's result to it.

Question 2

What are advantages and reasons to compose loops?

Select every option that applies, then check.

  • Correct. Each function only does one thing, so if you make a mistake, you can track it down very quickly.
  • Correct. A helper that applies one pattern is relatively easy to define and test, compared to one loop that combines several patterns.
  • Not quite. Composition makes code easier to write, test and debug, not faster: each helper usually walks through the list again.
  • Not quite. Each function has its own local variables; values move between the functions as arguments and return values.
  • Not quite. The loops are still there, inside the helper functions; composing just keeps each loop small.

Question 3

Given the following code to calculate the average of a list of numbers, identify the correct statement.

def average(numbers: list[int]) -> float:
    if not numbers:
        return None
    for number in numbers:
        return sum(numbers) / count(numbers)

Select one answer, then check.

  • Correct. sum and count already loop over the list. The for loop returns during its first pass, so it adds nothing: return sum(numbers) / count(numbers) on its own is enough.
  • Not quite. It returns the right value, but only because the loop returns straight away on its first pass: the loop serves no purpose.
  • Not quite. sum and count consume a whole list; number is a single element of it.
  • Not quite. The average is the sum of the elements divided by the number of elements, so you need both.
  • Not quite. An empty list is falsy, so if not numbers: is exactly how to guard against an empty list.
  • Not quite. The two results are divided, sum(numbers) / count(numbers); neither function consumes the other's result.

Question 4

Consider the following functions' names, parameters, and return values.

def convert_grade(grade: int) -> str:
    pass

def map_convert_grades(grades: list[int]) -> list[str]:
    pass

def remove_failed_grades(text_grades: list[str]) -> list[str]:
    pass

def get_passing_text_grades(grades: list[int]) -> list[str]:
    pass

Which of the following statements would be good compositions?

Select every option that applies, then check.

  • Correct. map_convert_grades applies the Map pattern: it calls convert_grade on each grade in the list.
  • Correct. After converting, the main function filters the text grades with remove_failed_grades, which consumes and produces a list of strings.
  • Correct. The main function first turns its list of integer grades into text grades with map_convert_grades: remove_failed_grades(map_convert_grades(grades)).
  • Not quite. convert_grade handles a single grade, while the main function works on whole lists, so it reaches convert_grade through map_convert_grades.
  • Not quite. remove_failed_grades consumes text grades (strings), but convert_grade consumes an integer.
  • Not quite. map_convert_grades should only convert every grade; removing failed grades is a separate step.
  • Not quite. get_passing_text_grades is the main function that calls map_convert_grades; calling it back would go round in circles.
  • Not quite. convert_grade works on one integer grade; it has no use for the main function, which consumes a whole list.