Sequences, Tuples, and Lists

EE1021 Programming for Engineers · Lecture 4 · 2026–2027

Basri Erdogan

Lecture Goals

  1. Compare strings, tuples, and lists by order, indexing, and mutability
  2. Create, index, slice, iterate over, and unpack sequences
  3. Choose the correct list operation and predict whether it changes the original list
  4. Build a list with a loop or a list comprehension
  5. Explain aliasing, cloning, mutation, and rebinding
  6. Process a small set of engineering measurements with tuples, lists, and functions

Retrieval Warm-Up: Repeated Measurements

def calibrate_voltage(raw_voltage, offset=0.05):
    return raw_voltage + offset

v1 = calibrate_voltage(4.92)
v2 = calibrate_voltage(5.08)
v3 = calibrate_voltage(4.97)
print(v1, v2, v3)

What repeats? What would become difficult if the experiment produced 100 readings?

The same operation is repeated while only the data changes. A sequence lets one variable hold many related values.

Part 1: Sequences and Tuples

Three Ordered Sequence Types

Type Example Ordered Indexed Mutable
string “EE1021” yes yes no
tuple (“S1”, 4.92) yes yes no
list [4.92, 5.08, 4.97] yes yes yes
  • Ordered means position matters.
  • Mutable means the existing object can be changed after creation.

Tuple Syntax: The Comma Matters

empty = ()
measurement = ("S1", 4.92)
single = (4.92,)

type(measurement)  # tuple
type(single)       # tuple
type((4.92))       # float
measurement[0]     # "S1"
measurement[-1]    # 4.92
measurement[0:1]   # ("S1",)

Parentheses improve readability, but the comma creates the tuple. Indexing and slicing follow the same rules used with strings.

measurement[1] = 5.00  # TypeError: tuples are immutable

Functions Can Return Multiple Results

def reading_limits(readings):
    """Return the minimum and maximum reading."""
    return min(readings), max(readings)

low, high = reading_limits([4.92, 5.08, 4.97])
print("Range:", low, "to", high)

The function returns one tuple; unpacking gives meaningful names to its two elements.

Tuple Checkpoint

def describe_sample():
    return "T2", 23.5

sensor, value = describe_sample()
value = value + 0.5
print(sensor, value)

Predict the output. Does changing value modify the tuple returned by the function?

Check: T2 24.0. Unpacking binds two new names; the original tuple is immutable.

Checkpoint 1 — Tuples

Part 2: Lists

Create and Index a List

readings = [4.92, 5.08, 4.97, 5.11]

readings[0]       # 4.92
readings[-1]      # 5.11
readings[1:3]     # [5.08, 4.97]
len(readings)     # 4
readings[1] = 5.00
print(readings)   # [4.92, 5.0, 4.97, 5.11]

mixed = ["S1", 4.92, True, [1, 2]]   # any types, even another list
  • A list can hold elements of any type, even other lists; in practice we usually keep one type per list.
  • An index selects one element.
  • A slice produces a new list containing a range of elements.
  • Assignment to an index mutates the existing list.

Mutation Checkpoint

values = [10, 20, 30, 40]
values[1:3] = [21, 31]
print(values)

Predict the result before running the code.

Check: [10, 21, 31, 40]. Slice assignment mutates the existing list.

Iterate Directly or with a Position

Value only

total = 0
for reading in readings:
    total += reading

Position and value

for i, reading in enumerate(readings):
    print(i, reading)

Prefer direct iteration unless the index itself has a job.

Add to a List: append and extend

readings = [4.92, 5.08]

readings.append(4.97)           # adds ONE element at the end
print(readings)                 # [4.92, 5.08, 4.97]

readings.extend([5.11, 5.02])   # adds EACH element of another list
print(readings)                 # [4.92, 5.08, 4.97, 5.11, 5.02]
  • readings.append(...) is a method: a function that belongs to the list, called with a dot.
  • Both methods change the list itself.
  • append adds 1 element; extend adds as many elements as the other list has.

Predict: append a List or extend with a List

a = [1, 2]
a.append([3, 4])
print(a)
b = [1, 2]
b.extend([3, 4])
print(b)

Predict both outputs.

Check: [1, 2, [3, 4]]: append adds the whole list as ONE element. [1, 2, 3, 4]: extend adds each element.

Insert or Concatenate

queue = ["group-01", "group-03"]
queue.insert(1, "group-02")
# queue is ["group-01", "group-02", "group-03"]

first = [1, 2]
second = [3, 4]
combined = first + second
# combined is [1, 2, 3, 4]
# first and second are unchanged
  • insert mutates at a chosen position.
  • The plus operator creates a new list.

Remove Elements Intentionally

queue = ["g1", "g2", "g3", "g2"]

queue.remove("g2")  # remove first matching value
processed = queue.pop(0)  # remove by index and return it
del queue[-1]       # remove by index, no returned value

After all three lines:

  • processed is “g1”
  • queue is [“g3”]

Mutating or Producing a New List?

Operation Mutates? Returns
append, extend, insert yes None
remove, sort, reverse yes None
pop yes removed element
plus, slicing, sorted no new list
raw = [5.08, 4.92, 4.97]
ordered = sorted(raw)

print(raw)
print(ordered)

Raw is unchanged; ordered is [4.92, 4.97, 5.08]. Calling raw.sort() would mutate raw and return None.

Checkpoint 2 — List Methods

Part 3: Build Lists Concisely

From a Loop to a Comprehension

Loop pattern

volts = []
for millivolts in [4920, 5080, 4970]:
    volts.append(millivolts / 1000)

Equivalent comprehension

volts = [millivolts / 1000 for millivolts in [4920, 5080, 4970]]
  • Read it as: produce one expression for each element in the input sequence.
  • A comprehension is a single expression inside brackets: no colon, no indented body.

Filter While Building

readings = [4.10, 4.92, 5.08, 5.70, 4.97]

valid = [value for value in readings if 4.75 <= value <= 5.25]

print(valid)

Output: [4.92, 5.08, 4.97]

Use a regular loop when the logic would become difficult to read in one comprehension.

Comprehension Checkpoint

Write a comprehension that converts Celsius readings to Fahrenheit.

celsius = [0, 20, 25, 30]
fahrenheit = ...  # replace with a comprehension
fahrenheit = [value * 9 / 5 + 32 for value in celsius]

Part 4: References and Mutation

Aliasing: Two Names, One List

original = [4.92, 5.08]
alias = original
alias.append(4.97)
original alias 4.92 5.08 4.97 one list object

Mutation through either name is visible through both names.

Clone: Two Names, Two Lists

original = [4.92, 5.08]
clone = original.copy()   # original[:] also makes a shallow copy
clone.append(4.97)
original clone 4.92 5.08 4.92 5.08 4.97

The outer lists are separate. A shallow copy does not recursively copy nested objects.

Lists Passed to Functions

Mutation is visible

def add_reading(values):
    values.append(5.00)

data = [4.92]
add_reading(data)
print(data)  # [4.92, 5.0]

The parameter values and the caller’s data name the same list, so append changes the caller’s list.

Rebinding stays local

def replace_readings(values):
    values = [5.00]

data = [4.92]
replace_readings(data)
print(data)  # [4.92]

values = [5.00] makes the local name point to a new list; data still names the original one.

A function can change the caller’s list only by mutating it, never by assigning a new list to its parameter. To give back a new list, return it.

Reference Checkpoint

a = [1, 2]
b = a
c = a.copy()

b.append(3)
c.append(4)

print(a, b, c)

Check: [1, 2, 3] [1, 2, 3] [1, 2, 4]. Names a and b refer to one list; c refers to a separate list.

Checkpoint 3 — References and Comprehensions

Integrated Example: Clean Readings

def clean_readings(readings, low=4.75, high=5.25):
    """Return valid readings without changing the input list."""
    return [value for value in readings if low <= value <= high]

raw = [4.10, 4.92, 5.08, 5.70, 4.97]
valid = clean_readings(raw)
minimum, maximum = min(valid), max(valid)

print("Raw:", raw)
print("Valid:", valid)
print("Range:", minimum, "to", maximum)

This combines a function, a list comprehension, and tuple unpacking without mutating the input.

Looking Ahead: Parallel Lists

sensor_ids = ["S1", "S2", "S3"]
readings = [4.92, 5.08, 4.97]
units = ["V", "V", "V"]
  • Related data is split across several lists.
  • Every list must remain the same length and order.
  • Updating one sensor requires coordinated indices.

Next lecture, dictionaries will give each value a meaningful key.

Five-Question Checkout

Summary

  • Strings and tuples are immutable ordered sequences; lists are mutable.
  • A tuple is created by commas and can be unpacked into meaningful names.
  • List methods differ in whether they mutate and what they return.
  • A comprehension builds a new list by transforming and optionally filtering.
  • Assignment can create an alias; copy or slicing creates a separate outer list.
  • Mutation and rebinding behave differently when lists are passed to functions.