Skip to content

Tuple and List

Learning Objectives

At the end of this sub-unit, students should

  • understand when to use tuple and when to use list.
  • know how to use both.

Tuple vs List

So when do we use tuple and when do we use list? Based on technical specification, we want to use list when the sequence is mutable or when we need the update operation to be fast. We have exemplified this with the operations above and compare the timing with tuple.

We use tuple when we want the sequence to be immutable so that we can easily understand the behavior of functions. For instance, if we use tuple and the tuple is passed as an argument to a function, we know for sure that the tuple is not modified in any way. Take the code below. We are going to be sure that tpl will still be (1, 2, 3) after the function call.

1
2
3
tpl = (1, 2, 3)
foo(tpl)   # does not matter what this is
print(tpl)
(1, 2, 3)

Cultural Reason

There is also a cultural reason when selecting list or tuple. We summarize the comparison below.

  • Tuple

    Usually stores a small collection of items with various data types (i.e, heterogeneous data type).

    Example

    A single student record with name (str), matric number (str), and their GPA name (float).

  • List

    Usually stores a large collection of data with the same data type (i.e., homogeneous data type).

    Example

    A classroom of 500 student records.

Note that violating this "culture" will not cause any error.

Restaurant

We can illustrate the cultural usage of list and tuple with the following problem. Typically, our question will not be posed this way. Often, we will specify the data representation instead.

Nearest Restaurant

Problem Description

You are working at a food delivery company as a developer. There are over 100 nice restaurants in Singapore. Given a coordinate of a customer (x, y), we want to find the restaurant nearest to the customer. Assume that there is always a unique solution to this.

Task

Write the function to find the nearest restaurant to the customer. Define your own data representation.

Let us dissect the problem closely. What data do we actually need? First, we will need to represent a restaurant. How can we represent a restaurant? Given the problem above, the only information we need are the following two. We can call a data type containing these two values as restaurant.

  • Restaurant name.
  • Restaurant coordinate.

However, upon further thinking, we will notice that the coordinate is not a single value but two values: x-coordinate and y-coordinate. Do we need to split this into three information? Since we are going to need the coordinate of the customer as well, we can also represent the customer coordinate in the same representation as the restaurant coordinate. So we need a coordinate data type containing the following information.

  • X-coordinate.
  • Y-coordinate.

What should be the data type of x- and y-coordinate? These can easily be a floating point numbers. So there is no other representation of data that we need. We can now construct a simple ADT for coordinate. We construct this first before restaurant because coordinate is used by `#!py3 restaurant.

What data type should we use to represent a coordinate? Since we always have a fixed number of information in a coordinate and it is a "small" number of information, we will use a tuple.

Coordinate

def make_coord(x, y):
  """
  (x::float, y::float) -> coordinate
  Return a coordinate (x, y).
  """
  return (x, y)

def coord_dist(coord1, coord2):
  """
  (coord1::coordinate, coord2::coordinate) -> float
  Return the distance between coord1 and coord2.
  """
  dist_x = coord1[0] - coord2[0]
  dist_y = coord1[1] - coord2[1]
  return ((dist_x * dist_x) + (dist_y * dist_y)) ** 0.5

We only need the computation of the distance because we only need to find the nearest one. Now we can use this to construct a restaurant.

Restaurant

def make_rest(name, coord):
  """
  (name::str, coord::coordinate) -> restaurant
  Return a restaurant.
  """
  return (name, coord)

def rest_dist(rest, coord):
  """
  (rest::restaurant, coord::coordinate) -> float
  Return the distance between rest and coord.
  """
  return coord_distance(rest[1], coord)

def get_name(rest):
  """
  (rest::restaurant) -> str
  """
  return rest[0]

At this point, we will assume that restaurants and coordinates are always going to be constructed correctly using make_rest and make_coord respectively. Since we have a "large" number of restaurants, we will choose a list to store the sequence of restaurants. Finally, we can solve the problem as follows.

Nearest Restaurant

def nearest_restaurant(restaurants, coord):
  # Assume the first is the nearest
  #   res is the nearest
  #   dist is the nearest distance
  res = restaurants[0]
  dist = rest_dist(res, coord)

  # Update our assumption by comparing all restaurants
  #   we will recompute the distance to index 0 twice
  #   but this will not affect our result
  for rest in restaurants:
    # Compute next restaurant distance
    next_dist = rest_dist(rest, coord)

    # Update if this restaurant is nearer
    if next_dist < dist:
      res = rest
      dist = next_dist

  # At the end, we will have the nearest
  #   so we obtain the name
  return get_name(res)

The actual code is rather short, the comments are longer. We added the comment to explain the behavior of the code.

Nearest Restaurant Without Comment
1
2
3
4
5
6
7
8
9
def nearest_restaurant(restaurants, coord):
  res = restaurants[0]
  dist = rest_dist(res, coord)
  for rest in restaurants:
    next_dist = rest_dist(rest, coord)
    if next_dist < dist:
      res = rest
      dist = next_dist
  return get_name(res)