Class summary:   Introduction to Lists
1 Looking up values by keys
2 Lists:   Two Motivating Problems
3 Lists:   a new kind of data for sets
4 What are Lists?
5 Extracting Lists from Tables

Class summary: Introduction to Lists

Copyright (c) 2017 Kathi Fisler

1 Looking up values by keys

We want a function that takes the name of a person and returns the number of tickets they have ordered:

  fun tickets-for(t :: Table, who :: String) -> Number:

    doc: "Extract tickcount value for order with given name"

    ...

  where:

    tickets-for(event-data-clean, "Alvina") is 3

    tickets-for(event-data-clean, "Ernie") is 0

  end

We filled in the body as follows:

  fun tickets-for(t :: Table, who :: String) -> Number:

    doc: "Extract tickcount value for order with given name"

    matches = filter-by(t, lam(r): r["name"] == who end)

    matches.row-n(0)["tickcount"]

  where:

    tickets-for(event-data-clean, "Alvina") is 3

    tickets-for(event-data-clean, "Ernie") is 0

  end

What happens if we try the following?

  tickets-for(event-data-clean,"kathi")

Our current code assumes that filter-by will return a non-empty table. We should instead check that we got a non-empty table, and raise an error if we did not:

  fun tickets-for(t :: Table, who :: String) -> Number:

    doc: "Extract tickcount value for order with given name"

    matches = filter-by(t, lam(r): r["name"] == who end)

    if matches.length() > 0:

      matches.row-n(0)["tickcount"]

    else:

      raise("Tickets-for: table has no row with name " + who)

    end

  where:

    tickets-for(event-data-clean, "Alvina") is 3

    tickets-for(event-data-clean, "Ernie") is 0

    tickets-for(event-data-clean, "Kathi") raises "no row"

  end

The where clause shows how to check whether a call to the function results in an error being raised – rather than write is in the example, we write raises. The string after raises needs to be a substring of the raised error for the test to pass.

2 Lists: Two Motivating Problems

Consider the following two questions:

We have an idea of how to write the first one – a filter-by with a helper function that uses or to check the code against a collection of options:

  fun check-discounts1(t :: Table) -> Table:

    doc: "filter out rows whose discount code is not valid"

    fun invalid-code(r :: Row) -> Boolean:

      not(

        (r["discount"] == "STUDENT") or

        (r["discount"] == "BIRTHDAY") or

        (r["discount"] == "") or

        (r["discount"] == "EARLYBIRD"))

    end

    filter-by(t, invalid-code)

  end

There’s something unsatisfying about this solution, though: every time the set of codes changes, we have to change the function. It would be much nicer if the codes could be written independently of the function. Then, the sales department could change the codes without having to bother the programmers every time.

So the real question is how can we rewrite this function so that the set of valid codes is written down outside the function?

3 Lists: a new kind of data for sets

  valid-discounts = [list: "STUDENT", "BIRTHDAY", "", "EARLYBIRD"]

  

  fun check-discounts(t :: Table) -> Table:

    doc: "filter out rows whose discount code is not valid"

    fun invalid-code(r :: Row) -> Boolean:

      not(L.member(valid-discounts, r["discount"]))

    end

    filter-by(t, invalid-code)

  where:

    check-discounts(event-data)

      is

    add-row(

      add-row(

        add-row(event-data.empty(), event-data.row-n(3)),

        event-data.row-n(4)),

      event-data.row-n(6))

  end

Here is a version written with anonymous functions/lambda.

  fun check-discounts2(t :: Table) -> Table:

    doc: "filter out rows whose discount code is not valid"

    filter-by(t, lam(r): not(L.member(valid-discounts, r["discount"])) end)

  where:

    check-discounts2(event-data)

      is

    add-row(

      add-row(

        add-row(event-data.empty(), event-data.row-n(3)),

        event-data.row-n(4)),

      event-data.row-n(6))

  end

4 What are Lists?

Lists are one of the key data structures in programming. They feature:

As we will see, there are many built-in operations on lists.

5 Extracting Lists from Tables

Turning to the second question, how could we get a list of names of people with the "STUDENT" discount? (Perhaps we want to validate those names against data from a school).

We know how to filter the table down to only those rows that have "STUDENT" in the discount column. How do we get the names from those rows? We use a table operator called get-column that pulls out the values from a column as a list:

  filter-by(

     event-data-clean,

     lam(r): r["discount"] == "STUDENT" end).get-column("name")

Alternatively, using an intermediate name for the filtered table:

  rows =

    filter-by(

       event-data-clean,

       lam(r): r["discount"] == "STUDENT" end)

  rows.get-column("name")

We’ll do a lot more with lists over the next several classes.