ICTPRG435 - Session 3: Tuples, Dictionaries, Loops and Operators

Overview: This session introduces Python tuples and dictionaries, loop structures (while and for), logical and membership operators, bitwise operations, number base conversions, and concludes with a practical encryption/decryption activity.

This session focuses on:

Tuples

A tuple is similar to a list but immutable (cannot be changed after creation). Tuples use parentheses () instead of square brackets.

Each item within the tuple is separated with a comma and is assigned a number known as its index (or position) in the sequence, the first index is 0. To access an item in the tuple, the index value of the item is required as demonstrated in the examples below

# A tuple containing integers
tup1 = (1, 0, 1)                 # Tuple of integers
tup2 = ("ant", "apple", "pear")  # Tuple of strings
tup3 = (True, False, True, True) # Tuple of Boolean values
tup4 = ((1, 0, 5, -5), ("a", "apple", "xyz"), (True, False)) # Tuple of tuples

print(tup1)                      # print all items in the tuple -> (1, 0, 1)
print(tup1[0])                   # print the first item in the tuple -> 1
print(tup2[1])                   # print the second item in the tuple -> 'apple'
print(tup2[-1])                  # print the last item in the tuple using negative indexing -> 'pear'
print(tup2[2][0])                # print the first character of third item in the tuple -> 'p'
print(tup4[1][1][:3])            # Print the first three characters of the second item in the tuple -> 'app'
                

Tuples are useful for fixed collections of items (e.g., coordinates, settings). Because they are immutable, they are hashable and can be used as dictionary keys.

Dictionaries

A dictionary is a Python data structure used to store a collection of key–value pairs enclosed in curly braces {}. Dictionaries like lists are mutable, but keys must be unique and immutable.

dict1 = {1: "ant", 2: "bug", 3: "fly"}           # Creating a dictionary with integer keys
print(dict1)                                     # print dictionary -> {1: "ant", 2: "bug", 3: "fly"}

dict1 = {1: "ant", 2: "bug", 3: "fly", 3: "bee"} # Duplicated keys override previous ones
print(dict1)                                     # key 3 is overwritten -> {1: 'ant', 2: 'bug', 3: 'bee'}

print(dict1[3])                                  # dictionary uses key to access value -> 'bee'

dict1[4] = "Nit"                                 # Add an item to the dictionary 
print(dict1)                                     # -> {1: 'ant', 2: 'bug', 3: 'bee', 4:'Nit'}

dict1[1] = "bot"                                 # update value under key 1
print(dict1)                                     # -> {1: 'bot', 2: 'bug', 3: 'bee', 4:'Nit'}

dict1.pop(1)                                     # remove key 1 from the dictionary
print(dict1)                                     # {2: 'bug', 3: 'bee', 4: 'Nit'}

dict2 = {"pet": "Dog", "name": "Flip", "age": 5} # create dictionary with string keys
print(dict2["name"])                             # access value associated with the 'name' key -> 'Flip'
                

KeyError occurs if you try to access a key that doesn't exist. Use get() method to avoid it:

dict1 = {1: "ant", 2: "bug", 3: "fly", 4: "bee"}
print(dict1.get(5))                       # -> None
print(dict1.get(5, "No Value Available")) # -> No Value Available
print(dict1.get(4, "No Value Available")) # -> 'bee'
                

Tip: Use .keys(), .values(), and .items() to loop through dictionaries (see below).

Loops

Python supports two main looping constructs: while and for.

While loops

while loops execute a block of code repeatedly as long as the condition remains True. The loop iteration stops when the condition evaluates to False.

counter = 0
while counter < 3: 
    print(counter) 
    counter = counter + 1 

print("Out of the loop!") # Output: 0, 1, 2, Out of the loop! 
            

In the example above, as long as the value associated with the counter variable remains less than 3, the condition will always evaluate to True, therefore the body of code will be executed.

Use break to exit early:

counter = 0
while True:
    print(counter)
    counter += 1
    if counter == 5:
        break

print("Out of the loop!")
                

Warning: If the loop condition never becomes False and no break is used, the program enters an infinite loop.

For loops

for loops iterate directly over items of a sequence (e.g., string, list, dictionary, range).

# Looping through a string
pet = "Cat"
for x in pet:
    print(x)      -> will print 'C' on the first iteration, then 'a' and 't'

# Looping through a list
pets = ["Cat", "Dog", "Rabbit"]
for x in pets:
    print(x)      -> will print 'Cat' on the first iteration, then 'Dog' and 'Rabbit'

for x in pets:
    if x == "Dog":
        break
    print(x)      -> will print only first item 'Cat'

for x in pets:
    if x == "Dog":
        continue
    print(x)      -> will skip 'Dog' and prints 'Cat' and 'Rabbit'

                

Note: The latter two examples are different, one uses a break statement, and the other a continue statement. Can you work out what is happening?

Looping through a dictionary

languages = {
    "Australia": "English",
    "Brazil": "Portuguese",
    "Denmark": "Danish",
    "Jordan": "Arabic"
}

# Iterating over dictionary keys
print("KEYS")
for key in languages:
    print(key)               -> "Australia", "Brazil", "Denmark", "Jordan"

# Iterating over dictionary values                    
print("VALUES")
for value in languages.values():
    print(value)             -> "English", "Portuguese", "Danish", "Arabic"

#Iterating over dictionary keys and values
print("KEYS - VALUES")
for key, value in languages.items():
    print(f"{key}: {value}") -> "Australia - English", "Brazil - Portuguese", "Denmark - Danish", "Jordan - Arabic"

The range() function

range() generates a sequence of numbers, often used in for loops.

Syntax: range(start,stop, step)

Examples

# range(stop)
for num in range(2):
    print(num)           -> will output 0 at the first iteration and then 1

# range(start, stop)
for num in range(2, 6):
    print(num)           -> 2, 3, 4, 5

# range(start, stop, step)
for num in range(2, 8, 2):
    print(num)           -> 2, 4, 6

# Negative step
for num in range(3, 0, -1):
    print(num)           -> 3, 2, 1
                

Logical Operators

Used to perform logical operations; return True or False.

Operator Description Example Result
and True if both conditions are true. a==3 and b==5 True
or True if at least one condition is true. a==3 or b==5 True
not Negates the condition. not (a==3 and b==5) False

Membership Operators

Used to test if a value exists in a sequence (like strings, lists, or tuples).

Operator Description Example Result
in True if the value exists in the sequence. "e" in "Hello" True
not in True if the value does not exist in the sequence. "E" not in "Hello" True

Example:

word = "PROGRAMMING"
while True:
    char = input("Enter a character: ")
    if len(char) == 1:
        break
    else:
        print("Only one character, please!")

if char in word:
    print("Found")
else:
    print("Not Found")
                

Can you predict the output of the above program, if enter 'p' letter?

Bitwise Operators

Operate at the binary level - comparing bits of integers.

Bitwise operators can be used to implement algorithms such as compression, encryption, and error detection.

Operator Description Example Bitwise Level Result
& Bitwise AND (1 if both bits are 1) 156 & 52
156 1 0 0 1 1 1 0 0
52 0 0 1 1 0 1 0 0
20 0 0 0 1 0 1 0 0
20
| Bitwise OR (1 if either bit is 1) 156 | 52
156 1 0 0 1 1 1 0 0
52 0 0 1 1 0 1 0 0
188 1 0 1 1 1 1 0 0
188
^ Bitwise XOR (1 if bits differ) 156 ^ 52
156 1 0 0 1 1 1 0 0
52 0 0 1 1 0 1 0 0
20 0 0 0 1 0 1 0 0
168
~ Bitwise NOT (inverts bits - returns -(n+1))
Python uses two complement system to store negative numbers
~10
10 0 0 0 0 1 0 1 0
-11 1 1 1 1 0 1 0 1
-11
<< Left shift (append zeros to the right) 53 << 1
53 0 0 1 1 0 1 0 1
106 0 0 1 1 0 1 0 1 0
106
>> Right shift (remove right bits) 53 >> 1
53 0 0 1 1 0 1 0 1
26 0 0 0 1 1 0 1 0 1
26

Note: Bitwise operations are useful for low-level programming tasks such as encryption, compression, and flags management.

For more information on two’s complement please visit: Two's complement and negative numbers for integers

Number Conversion Functions

Python includes several built-ins for converting between number bases.

Function Description Example Result
hex() Convert decimal to hexadecimal string. hex(255) '0xff'
bin() Convert decimal to binary string. bin(79) '0b1001111'
int(x, base) Convert from given base to decimal. int('a2', 16)
int('1001111', 2)
162
79

Notes:

Student Activity - Encryptor

Objective: You are required to develop a program which encrypts, and decrypts a given password.

Note: The incomplete .py file (encryptor.py) required for the activity is presented below and is located under the Session 3 Files heading on the resources page

def encrypt():
    ?
    return encrypted_list

def decrypt():
    ?
    return pwd

if __name__ == "__main__":
    pwd = encrypt("Hello$2#")
    print("Encrypt:", pwd)           # Encrypt: [-73, -102, -109, -109, -112, -37, -51, -36]
    print("Decrypt:", decrypt(pwd))  # Decrypt: Hello$2#
    
                

Requirements

Hint: Use ord() and chr() for conversions, and remember that bitwise NOT (~) gives -(x + 1). To reverse it, use ~n again.

Extension ideas