Chapter 2 Collections

  • list: Ordered collection of items, e.g., my_list = [1, 2, 3].

  • tuple: Immutable ordered collection of items, e.g., my_tuple = (1, 2, 3).

  • set: Unordered collection of unique items, e.g., my_set = {1, 2, 3}.

  • dict: Key-value pairs, e.g., my_dict = {'a': 1, 'b': 2}.

2.0.1 Properties

In Python, collections are built-in data types that can be used to group multiple elements together. Here are some common properties of Python collections:

2.0.1.1 Mutable vs. Immutable:

  • Mutable Collections: Lists (list), Sets (set), and Dictionaries (dict) are mutable. You can modify their contents after creation.

  • Immutable Collections: Tuples (tuple) and Strings (str) are immutable. Once created, their contents cannot be changed.

2.0.1.2 Ordering:

  • Ordered Collections: Lists and Tuples maintain the order of elements. Elements are stored in the order they were added.

  • Unordered Collections: Sets and Dictionaries do not guarantee any specific order. The order of elements may not be the same as the order of insertion.

2.0.1.3 Indexing and Slicing:

  • Indexing: Lists, Tuples, and Strings support indexing. Elements can be accessed using indices (0-based).
my_list = [1, 2, 3, 4]
print(my_list[0])  # Output: 1
  • Slicing: Lists, Tuples, and Strings support slicing to create sub-collections.
my_string = "Hello, World!"
print(my_string[0:5])  # Output: Hello

2.0.1.4 Uniqueness:

  • Unique Elements: Sets only contain unique elements. If you try to add an element that already exists, it won’t be added again.
my_set = {1, 2, 3, 1}
print(my_set)  # Output: {1, 2, 3}

2.0.1.5 Key-Value Pairs (Dictionaries):

  • Associative Data: Dictionaries consist of key-value pairs, allowing you to associate values with unique keys.
my_dict = {'name': 'John', 'age': 30, 'city': 'New York'}
print(my_dict['age'])  # Output: 30

2.0.1.6 Heterogeneity:

  • Mixed Types: Lists, Tuples, and Sets can contain elements of different data types.
my_list = [1, 'apple', 3.14]

2.0.1.7 Using append() for a Single Element:

list_1 = [1, 2, 3]

list_1.append(10)   # do not assign to a variable

print(list_1)
## [1, 2, 3, 10]

2.0.1.8 Using extend() for Multiple Elements:

list_2 = [1, 2, 3]

list_2.extend( [10, 100, 1000] )   # requires a list

print(list_2)
## [1, 2, 3, 10, 100, 1000]

2.0.1.9 Using add() for set objects:

set_3 = {1, 2, 3}

set_3.add( 100 )   # requires a list

print(set_3)
## {1, 2, 3, 100}
  • Removing Elements: remove(), pop(), discard(), clear()

2.0.1.10 remove() Method

Purpose: Removes the first occurrence of a specified value from a list or set.

Usage:

For lists, if the specified value does not exist, it raises a ValueError.

For sets, it also raises a KeyError if the specified value is not present.

my_list = [1, 2, 3, 2, 4]

# Removes the first occurrence of 2
my_list.remove(2)

print(my_list) 
## [1, 3, 2, 4]

2.0.1.11 pop() Method

Purpose: Removes and returns an element from a list or set.

Usage:

For lists, it removes and returns the element at a specified index. If no index is provided, it removes and returns the last element. Raises an IndexError if the list is empty.

For sets, it removes and returns an arbitrary element because sets are unordered. Raises a KeyError if the set is empty.

my_list = [1, 2, 3, 4]

# Removes and returns the last element by default
last_element = my_list.pop()

print(last_element)  # Output: 4
## 4
print(my_list)  # Output: [1, 2, 3]
## [1, 2, 3]
# Removes and returns the element at index 1
second_element = my_list.pop(1)

print(second_element)  # Output: 2
## 2
print(my_list)  # Output: [1, 3]
## [1, 3]

2.0.1.12 discard() Method

Purpose: Removes the specified element from a set.

Usage: Sets only: Does not raise an error if the specified element does not exist, unlike remove().

my_set = {1, 2, 3}

# Discards the element 2
my_set.discard(2)
print(my_set)  # Output: {1, 3}
## {1, 3}

2.0.1.13 clear() Method

Purpose: Removes all elements from a list or set, making it empty.

Usage: Can be used with both lists and sets to empty them.

my_list = [1, 2, 3]

# Clears all elements from the list
my_list.clear()

print(my_list)
## []

2.0.1.14 Extra: Imuutable

Yes, in Python, strings are immutable objects. This means that once a string is created, you cannot change its content. Any operation that appears to modify a string actually creates a new string. This immutability has several implications:

  1. No In-Place Modifications:
    • You cannot modify a string directly by changing a character at a specific index, like you can with a list.
    my_string = "Hello"
    # The following will result in an error
    my_string[0] = 'J'
  2. Creating New Strings:
    • Operations like concatenation or slicing create new strings rather than modifying the original.
    original_string = "Hello"
    new_string = original_string + ", World!"
  3. Hashing:
    • Because strings are immutable, they can be used as keys in dictionaries and elements in sets. Their hash value remains constant.
    my_set = {"apple", "banana", "cherry"}
  4. Memory Efficiency:
    • Python can optimize memory usage by reusing the same string in memory if it already exists, thanks to immutability.
    a = "Hello"
    b = "Hello"
    # Both a and b refer to the same string object in memory

Understanding the immutability of strings is important when working with them in Python to avoid unexpected behavior and to write efficient and correct code. If you need to modify a string, you typically create a new string with the desired changes.

2.0.2 1. lists

A list is a sequenced collection of different objects such as integers, strings, and even other lists as well. The address of each element within a list is called an index. An index is used to access and refer to items within a list.

Lists can contain strings, floats, and integers. We can nest other lists, and we can also nest tuples and other data structures. The same indexing conventions apply for nesting:

list are like tuples, ordered sequences.

But lists are mutable.

A list is a built-in data type used to store an ordered collection of items. Lists are mutable, which means you can modify their contents by adding, removing, or changing elements. Lists are defined using square brackets [].

Here’s an overview of lists and some example methods:

2.0.2.1 Creating Lists:


x = []    ## empty list
y = [1, 2, 3, 'apple', 'banana', 'cherry']
z = list( a_array)

2.0.2.2 Common List Methods:

  1. Accessing
my_list = [1, 2, 3, 'apple', 'banana']

# Accessing by index
my_list[0]  # Result: 1
## 1
# Slicing
my_list[1:4]  # Result: [2, 3, 'apple']
## [2, 3, 'apple']
  1. Append (append()):
    • Adds an element to the end of the list.
my_list.append("XXXX")

print(my_list)
## [1, 2, 3, 'apple', 'banana', 'XXXX']
  1. Extend (extend()):
    • Extends the list by appending elements from another iterable.
another_list = [5, 6, 7]
   
my_list.extend(another_list)
   
print(my_list)
## [1, 2, 3, 'apple', 'banana', 'XXXX', 5, 6, 7]
  1. Insert (insert()):
    • Inserts an element at a specified position.
    • this is not replacing !!!
my_list.insert(2, 'orange')

print(my_list)
## [1, 2, 'orange', 3, 'apple', 'banana', 'XXXX', 5, 6, 7]
  1. Remove (remove()):
    • Removes the first occurrence of a specified value.
my_list.remove('banana')

print(my_list)
## [1, 2, 'orange', 3, 'apple', 'XXXX', 5, 6, 7]
  1. Pop (pop()):

    • Removes and returns the element at the specified index. If no index is provided, it removes the last element.
popped_element = my_list.pop(2)

print(popped_element)
## orange
  1. Index (index()):
    • Returns the index of the first occurrence of a specified value.
index_of_apple = my_list.index('apple')

print(index_of_apple)
## 3
  1. Count (count()):
    • Returns the number of occurrences of a specified value.
count_of_cherry = my_list.count('cherry')
print(count_of_cherry)
## 0
  1. Sort (sort()):

    • Sorts the list in ascending order. Optionally, you can specify reverse=True for descending order.
# Convert all elements to strings before sorting
st_list = [str(x) for x in my_list]

st_list.sort()

print(st_list)
## ['1', '2', '3', '5', '6', '7', 'XXXX', 'apple']
# Convert all elements to strings before sorting
sorted_list = sorted(map(str, my_list))

print(sorted_list)
## ['1', '2', '3', '5', '6', '7', 'XXXX', 'apple']
  1. Reverse (reverse()):

    • Reverses the order of the elements in the list.
my_list.reverse()

print(my_list)
## [7, 6, 5, 'XXXX', 'apple', 3, 2, 1]
  1. Concatenate lists
a_list = [1, 2, 3]
b_list = ["x", "y", "z"]


print(a_list + b_list)
## [1, 2, 3, 'x', 'y', 'z']

loop thru a list

a = ["a", "b", "c"]

for item in a:
   print(item.upper())
## A
## B
## C

2.0.3 tuples

Tuples are an ordered sequences of items, just like lists. The main difference between tuples and lists is that tuples cannot be changed (immutable) unlike lists which can (mutable).

tuples are collection of different type of objects.

Empty tuples

a = ()
b = tuple()

Create tuples

## 1. way
a_tuple = (1, 2.5, "string", [3, 4])

b = (1,)

print(b)
## (1,)

Concatenate tuples

b_tuple = ("a", "ab")

print(a_tuple + b_tuple)
## (1, 2.5, 'string', [3, 4], 'a', 'ab')

Immutable

element of a tuple can not be changed

a_tuple = (1,2,3,4,5)

a_tuple[5] = "a"

# TypeError: 'tuple' object does not support item assignment

sorted function

sorted() function: We can sort the tuple and assign a new name

sorted() function returns list type.

a_tuple = (1, 5, 2, 7, 4)

x = sorted(a_tuple)

print(x)
## [1, 2, 4, 5, 7]

Nesting

we can create nested tuples.

nested = (1, 2, ("a", "b", "c"), ("ayan", (4, 5)))

nested[2]
## ('a', 'b', 'c')

2.0.3.1 tuple methods

index method

The index method returns the first index at which a value occurs.

a = ("a", "b", "c", "d")

a.index("c")
## 2

count method

The count method returns the number of times a value occurs in a tuple.

a = ("a", "b", "c", "d", "a", "b", "c", "b")

a.count("b")
## 3

loop thru a tuple

a = ("a", "b", "a", "b")

for item in a:
   print(item)
## a
## b
## a
## b

2.0.4 Sets

Sets in Python are unordered collections of unique elements.

Unlike lists, sets do not have a specific order, and each element in a set must be unique. Here are some common operations and methods associated with sets:

  1. Creating Sets
my_set = {1, 2, 3, 4, 5}
  1. Adding Elements
my_set.add(6)  # Adds the element 6 to the set
  1. Removing Elements
my_set.remove(3)  # Removes the element 3 from the set
  1. Set Operations
set1 = {1, 2, 3, 4, 5}
set2 = {3, 4, 5, 6, 7}

# Union
union_set = set1.union(set2)  # Result: {1, 2, 3, 4, 5, 6, 7}

# Intersection
intersection_set = set1.intersection(set2)  # Result: {3, 4, 5}

# Difference
difference_set = set1.difference(set2)  # Result: {1, 2}

# Symmetric Difference
symmetric_difference_set = set1.symmetric_difference(set2)  # Result: {1, 2, 6, 7}

2.0.4.1 Set Methods

# Discard an element (if it exists)
my_set.discard(4)

# Clear all elements
my_set.clear()

# Copying a set
copy_of_set = my_set.copy()
  1. Other Operations
# Checking Membership
is_present = 5 in my_set  # Result: True

# Length of a Set
length_of_set = len(my_set)

Sets are useful when you need to work with unique elements, perform set operations, or check for membership efficiently. They do not support indexing or slicing, as they are unordered. Additionally, sets are mutable, meaning you can add and remove elements, but individual elements must be immutable (e.g., numbers, strings).

2.0.5 Dictionary

Dictionaries in Python are collections of key-value pairs, where each key must be unique. Dictionaries are versatile and used for mapping values to unique keys. Here are common operations and methods associated with dictionaries:

  1. Creating Dictionaries
my_dict = {'name': 'John', 
           'age': 30, 
           'city': 'New York'}

print(my_dict)
## {'name': 'John', 'age': 30, 'city': 'New York'}
  1. Accessing Values
# Accessing by key
p0 = my_dict['name']  # Result: 'John'

print(p0)
## John
# Using the get() method
p1 = my_dict.get('name')  # Result: 30

print(p1)
## John
  1. Modifying Dictionaries
# Updating a value
my_dict['age'] = 31

# Adding a new key-value pair
my_dict['gender'] = 'Male'
  1. Removing Items
# Removing a key-value pair
del my_dict['city']
my_dict = {'name': 'John', 
           'age': 30, 
           'gender': 'Male'}
           
# Using the pop() method
gender = my_dict.pop('gender')  # Removes the 'gender' key and returns its value

print(gender)
## Male

2.0.5.1 Dictionary Methods:

# Getting all keys
keys = my_dict.keys()  # Result: ['name', 'age']

print(keys)
## dict_keys(['name', 'age'])
# Getting all values
values = my_dict.values()  # Result: ['John', 31]

print(values)
## dict_values(['John', 30])
# Getting all key-value pairs as tuples
items = my_dict.items()  # Result: [('name', 'John'), ('age', 31)]

print(items)
## dict_items([('name', 'John'), ('age', 30)])

2.0.6 Iterating Over a Dictionary:

for key in my_dict:
    print(key, my_dict[key])
## name John
## age 30

2.0.7 Other Operations:

# Checking Membership (in terms of keys)
is_present = 'age' in my_dict  # Result: True

# Length of a Dictionary
length_of_dict = len(my_dict)

Dictionaries are widely used in Python for tasks that involve mapping keys to values, such as representing data structures, configuration settings, and more. They are mutable, meaning you can modify their contents by adding, updating, or removing key-value pairs. The keys must be immutable (e.g., strings, numbers), but the values can be of any type.