2.6. Lecture 5: Python 3

Before this class you should:

  • Read Think Python:

    • Chapter 8: Strings;

    • Chapter 10: Lists;

    • Chapter 11: Dictionaries; and

    • Chapter 12: Tuples

Before next class you should:

  • Read Think Python:

    • Chapter 15: Classes and objects;

    • Chapter 16: Classes and functions;

    • Chapter 17: Classes and methods; and

    • Chapter 18: Inheritance

Note taker: Evan Rutten

2.6.1. Strings

2.6.1.1. A String is a Sequence

A string is a sequence of characters that can be accessed one at a time with the bracket operator:

fruit = 'banana'
letter = fruit[1]  # Gets 'a'

The index indicates which character in the sequence you want:

  • Indexing starts at 0 (first character)

  • The index must be an integer

  • You can use expressions containing variables and operators as indices

  • Negative indices count backward from the end of the string

2.6.1.2. Length

The len() function returns the number of characters in a string:

fruit = 'banana'
length = len(fruit)  # Returns 6

2.6.1.3. String Traversal

You can process a string one character at a time using either a while loop or a for loop.

Using while:

index = 0
while index < len(fruit):
    letter = fruit[index]
    print(letter)
    index = index + 1

Using for (preferred):

for letter in fruit:
    print(letter)

2.6.1.4. String Slices

A segment of a string is called a slice. Selecting a slice is similar to selecting a character:

s = 'Monty Python'
first = s[0:5]    # Gets 'Monty'
second = s[6:12]  # Gets 'Python'

Slice features:

  • [n:m] returns part of string from “n-eth” to “m-eth” character

  • Includes first position, excludes last position

  • Omitting first index starts from beginning

  • Omitting second index goes to end

  • fruit[:] creates a copy of the entire string

Additional examples with omitted indices:

s = "Hello, World!"
print(s[:5])   # "Hello" (start omitted, defaults to 0)
print(s[7:])   # "World!" (end omitted, goes to end)
print(s[:])    # "Hello, World!" (copy of entire string)

2.6.1.5. Strings are Immutable

You cannot change an existing string. The best you can do is create a new string that is a variation on the original:

greeting = 'Hello, world!'
# This will raise an error:
# greeting[0] = 'J'

# Instead, create a new string:
new_greeting = 'J' + greeting[1:]

2.6.1.6. String Methods

Strings provide various built-in methods:

word = 'banana'
new_word = word.upper()  # Convert to uppercase

# find method
index = word.find('na')      # Find substring
index = word.find('na', 3)   # Find substring starting at index 3
index = word.find('na', 3, 5)  # Find substring between indices 3 and 5

String replacement (useful since strings are immutable):

greeting = "Hello, world!"
new_greeting = greeting.replace("Hello", "Hi")  # Creates new string "Hi, world!"

2.6.1.7. The in Operator

The in operator is a boolean operator that returns True if first string appears as substring in second:

'a' in 'banana'     # Returns True
'seed' in 'banana'  # Returns False

2.6.1.8. String Comparison

Relational operators work on strings:

word = 'Pineapple'
if word < 'banana':
    print('Your word comes before banana.')
elif word > 'banana':
    print('Your word comes after banana.')

Note: Python handles uppercase and lowercase letters differently: all uppercase letters come before lowercase letters.

2.6.2. Lists

2.6.2.1. Lists as Sequences

A list is a sequence of values of any type:

numbers = [10, 20, 30, 40]
strings = ['crunchy frog', 'ram bladder', 'lark vomit']
mixed = ['spam', 2.0, 5, [10, 20]]  # Nested list
empty = []

Using list comprehensions for more concise list traversal:

# Instead of:
squares = []
for x in range(10):
    squares.append(x**2)

# Use list comprehension:
squares = [x**2 for x in range(10)]

2.6.2.2. Lists are Mutable

Unlike strings, lists are mutable. You can change elements using the bracket operator:

numbers = [42, 123]
numbers[1] = 5  # List is now [42, 5]

List indices work the same way as string indices:

  • Any integer expression can be an index

  • Out of range indices raise IndexError

  • Negative indices count from the end

2.6.2.3. List Traversal

Common ways to traverse lists:

# Method 1: Process elements
for cheese in cheeses:
    print(cheese)

# Method 2: Process indices
for i in range(len(numbers)):
    numbers[i] = numbers[i] * 2

2.6.2.4. List Operations

Lists support concatenation and repetition:

a = [1, 2, 3]
b = [4, 5, 6]
c = a + b          # Concatenation
d = [0] * 4       # Repetition
e = [1, 2, 3] * 3  # More repetition

2.6.2.5. List Slices

The slice operator works on lists similar to strings:

t = ['a', 'b', 'c', 'd', 'e', 'f']
t[1:3]  # Gets ['b', 'c']
t[:4]   # Gets ['a', 'b', 'c', 'd']
t[:]    # Creates a copy of the whole list

You can also update multiple elements using slices:

t[1:3] = ['x', 'y']

2.6.2.6. List Methods

Common list methods:

t = ['a', 'b', 'c']
t.append('d')        # Add single element
t.extend(['e', 'f']) # Add multiple elements
t.sort()            # Sort in place

# Removing elements
x = t.pop(1)        # Remove and return element at index
t.remove('b')       # Remove specific value
del t[1]            # Delete element at index
del t[1:5]          # Delete slice

2.6.2.7. Map, Filter, and Reduce

Common list operations patterns:

# Reduce - combine elements
def add_all(t):
    total = 0
    for x in t:
        total += x
    return total

# Map - transform elements
def capitalize_all(t):
    res = []
    for s in t:
        res.append(s.capitalize())
    return res

# Filter - select elements
def only_upper(t):
    res = []
    for s in t:
        if s.isupper():
            res.append(s)
    return res

2.6.2.8. Lists and Strings

Converting between strings and lists:

# String to list of characters
s = 'spam'
t = list(s)

# String to list of words
s = 'pining for the fjords'
t = s.split()

# List to string
t = ['pining', 'for', 'the', 'fjords']
s = ' '.join(t)

2.6.2.9. Objects and Values

Two variables can refer to the same object (aliasing) or different objects with the same value:

a = [1, 2, 3]
b = [1, 2, 3]  # Different object, same value
c = a          # Same object (alias)

# Test identity
a is b  # False
a is c  # True

2.6.2.10. List Arguments

When you pass a list to a function, the function gets a reference to the list:

def delete_head(t):
    del t[0]    # Modifies original list

def tail(t):
    return t[1:]  # Returns new list

2.6.3. Dictionaries

2.6.3.1. Dictionaries as Mappings

A dictionary maps keys to values:

eng2sp = dict()              # Empty dictionary
eng2sp['one'] = 'uno'        # Add key-value pair
eng2sp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}  # Create with items

Dictionary features:

  • Keys must be hashable (immutable)

  • Values can be any type

  • Each key maps to exactly one value

  • Order is preserved (Python 3.7+)

Performance considerations:

  • List searches: \(O(n)\) (linear search required)

  • Dictionary lookups: \(O(1)\) (hash-based retrieval)

Nested dictionaries are useful for complex data structures:

students = {
    "Alice": {"age": 21, "major": "CS"},
    "Bob": {"age": 22, "major": "Math"}
}
print(students["Alice"]["major"])  # Output: "CS"

2.6.3.2. Dictionary Operations

Common operations:

# Length
len(eng2sp)

# Membership testing (keys only)
'one' in eng2sp

# Access values
eng2sp['two']           # Direct access
eng2sp.get('four', 0)   # Access with default

2.6.3.3. Counters

Dictionaries are useful for counting:

def histogram(s):
    d = dict()
    for c in s:
        if c not in d:
            d[c] = 1
        else:
            d[c] += 1
    return d

2.6.3.4. Dictionary Traversal

Ways to process dictionaries:

# Traverse keys
for c in h:
    print(c, h[c])

# Traverse in sorted order
for key in sorted(h):
    print(key, h[key])

2.6.3.5. Reverse Lookup

Finding a key based on a value:

def reverse_lookup(d, v):
    for k in d:
        if d[k] == v:
            return k
    raise LookupError('value not found')

2.6.3.6. Dictionaries and Lists

Lists can be dictionary values but not keys:

def invert_dict(d):
    inverse = dict()
    for key in d:
        val = d[key]
        if val not in inverse:
            inverse[val] = [key]
        else:
            inverse[val].append(key)
    return inverse

2.6.4. Best Practices

  1. Choose appropriate data structures:

  • Strings for text data

  • Lists for ordered sequences

  • Dictionaries for key-value mappings

  1. Remember mutability:

  • Strings are immutable

  • Lists are mutable

  • Dictionary values are mutable, keys must be immutable

  1. Consider performance:

  • Dictionary lookups are fast

  • List searches are linear time

  • String operations create new strings

  1. Use built-in methods when available:

  • String methods for text processing

  • List methods for sequence operations

  • Dictionary methods for mapping operations