g = [[1,2,3], [4,5], [6,7], [8]]
for i in range(len(g)):
print("i: ", i, " outer list element", g[i])
for j in range(len(g[i])):
print(" j: ", j, " inner list element", g[i][j])
Solution:
i: 0 outer list element [1, 2, 3]
j: 0 inner list element 1
j: 1 inner list element 2
j: 2 inner list element 3
i: 1 outer list element [4, 5]
j: 0 inner list element 4
j: 1 inner list element 5
i: 2 outer list element [6, 7]
j: 0 inner list element 6
j: 1 inner list element 7
i: 3 outer list element [8]
j: 0 inner list element 8
[11.10] What is the output of the following code?
lst = [[1,1], [2,3,4]]
x = 0
i = 1
for j in range(len(lst[0])):
x = x + lst[i][j]
print(x)
Solution:
5
The variable i has a value of 1. The variable j goes through the values of 0 and 1, as lst[0] has a length of 2. The values that get added are lst[1][0], lst[1][1], which are 2 + 3 = 5.
[11.11] What does the following Python function do with the 2D list grid?
def process_grid(grid):
result = 0
for i in range(len(grid)):
for j in range(len(grid[0])):
if (i + j) % 2 == 0:
result += grid[i][j]
return result
Options:
A. Calculates the sum of elements in even rows and columns.
B. Calculates the sum of elements at positions where the sum of row and column indices is even.
C. Counts the number of elements that are even.
D. Finds the maximum element where the sum of row and column indices is even.
Answer: B. Calculates the sum of elements at positions where the sum of row and column indices is even.
[11.12] Which of the following statements accurately describes a destructive operation on a 1D list?
A. A new list is created, and the original list remains unchanged.
B. The operation modifies the original list by adding or removing elements.
C. The list's elements are rearranged and saved in a new list.
D. A copy of the list is made for backup while the original list is used for processing.
The correct answer is B. The operation modifies the original list by adding or removing elements.
[11.13] True or False: A non-destructive function always returns a new list and does not change the original list.
Answer: True.
[11.14] Describe what it means for a function to be non-destructive in the context of 1D list manipulation.
A non-destructive function in the context of 1D list manipulation is one that performs operations on a list (like filtering, mapping, or sorting) and returns a new list with the results, leaving the original list unchanged. The function does not alter the structure or content of the original list.
[11.15] Match the following operations with either "Destructive" or "Non-Destructive" and a short description why:
list.append(4)
sorted(list)
list.pop()
list + [4, 5]
Explanation:
Destructive: The list.append(4) operation modifies the original list by adding an element.
Non-Destructive: The sorted(list) function returns a new sorted list, leaving the original list unchanged.
Destructive: The list.pop() operation removes an element from the original list.
Non-Destructive: The list + [4, 5] operation creates a new list, leaving the original list unchanged.
[11.16] What will each of the following code snippets evaluate to?
[ 5 ] * 3
["a", "b", "c"][1]
min([5, 1, 8, 2])
max([5, 1, 8, 2])
sum([5, 1, 8, 2])
Explanation:
[ 5, 5, 5]
"b"
1
8
16
sum([5, 1, 8, 2])
[11.17] What will the value of lst be at the end of the following operations? Why? How is it different from the exercise below?
Explanation: lst and lst2 become alieased in the second line. All the operations above are destructive operations. Namely, they are changing lst without changing the variable reference. Any changes to lst, will also be reflected in lst2. In the next exercise, we are changing the list in a non-destructive way.
Explanation: Explanation: lst and lst2 become alieased in the second line. However, all the operations above are npn-destructive operations. Namely, they are changing lst by changing the variable reference. Any changes to lst, will NOT be reflected in lst2. The alias gets broken in line 4, and after that point, any changes to one list, are not visible in the other. In the previous exercise, we are changing the list in a destructive way.
[11.19] At the end of this set of operations, which lists will be aliased? What values will each variable hold?
1. a = [ 1, 2, "x", "y" ]
2. b = a
3. c = [ 1, 2, "x", "y" ]
4. d = c
5. a.pop(2)
6. b = b + [ "woah" ]
7. c[0] = 42
8. d.insert(3, "yowza")
Explanation:
Variables b and a become aliased in line 2. Variables d and c become aliased in line 4. Even though a and b have the same elements as c and d, only a and b are aliased to each other, and c and d are aliased to each other.
In line 5, we modify list a destructively, which also causes changes to list b. At this point, both a and b have: [1, 2, 'y'].
In line 6, we non-destructively change list b. That breaks the alias between b and a. At this point, a has [1, 2, 'y'] vs b has [1, 2, 'y', 'woah'].
In line 7, list c gets changed destructively, which also changes list c. Lists c and d now have both: [42, 2, 'x', 'y'].
Finally, in line 8we destructively change list d which also affects list c. Both c and d are still aliased and have [42, 2, 'x', 'yowza', 'y'].
In the end, lists c and d are aliased, with values: [42, 2, 'x', 'yowza', 'y']. On the other hand, a and b are not aliased anymore, and a has [1, 2, 'y'] vs b has [1, 2, 'y', 'woah'].
[11.20] What will the value of x be at the end of the code below? Why?
def destructiveDouble(lst):
for i in range(len(lst)):
lst[i] = lst[i] * 2
x = [1, 2, 3]
destructiveDouble(x)
print(x)
Explanation: The value of x will be [2, 4, 6] when printed in the end. The reason why that is happening is because when we pass x as an argument to the function destructiveDouble(x), x and lst become aliased at that point in time. This means that any destructive changes to lst in the function, will be reflected in x as well, as they are aliased. Even though we do not change x directly, by changing lst destructively, we can see the changes reflected in x.
[11.21] What will the value of x and y be at the end of the code below? Why?
def nonDestructiveDouble(lst):
result = [ ]
for i in range(len(lst)):
result.append(lst[i] * 2)
return result
x = [1, 2, 3]
y = nonDestructiveDouble(x)
print(x, y)
Explanation: The value of x will be [1, 2, 3], while the value of y will be [2, 4, 6] when printed in the end. The reason why that is happening is because when we pass x as an argument to the function nonDestructiveDouble(x), x and lst become aliased at that point in time. This means that any destructive changes to lst in the function, will be reflected in x as well, as they are aliased.
That being said, lst is not changing within the function. Instead, a new variable result is created and is being changed. Thus, no changes happen to x or lst. The variable result is returned in the end where it becomes aliased with y when the returned value from nonDestructiveDouble(x) is assigned to y. Even though we do not change y directly, by changing result and returning it in the end, we can see the changes reflected in y.
[11.22] What will the code below print? Why?
def fun(lst1, lst2):
r = [ ]
for i in range(len(lst1)):
lst1[i] = lst1[i] + 10
r.append(lst2[i] * 2)
print("lst1: ", lst1)
print("lst2: ", lst2)
return r
x = [1, 2, 3]
y = [3, 4, 5]
z = fun(x, y)
print("x: ", x)
print("y: ", y)
print("z: ", z)
Let's analyze the code step by step:
fun is a function that takes two lists, lst1 and lst2, as arguments.
Inside the function:
It initializes an empty list r.
It iterates over the indices of lst1 using range(len(lst1)).
For each index i, it modifies the corresponding element of lst1 by adding 10 to it (lst1[i] = lst1[i] + 10). As lst1 and x are aliased, any changes to lst1 will be reflected in x too.
It appends the result of multiplying the corresponding element of lst2 by 2 to the list r. As changes to r are non-destructive, no changes to lst2 happen, and as a result, no changes to y happen, as lst2 and y are aliased.
Finally, it prints the destructively modified lst1 and the original lst2.
It returns the list r.
Outside the function:
It initializes two lists x and y.
It calls the function fun with x and y as arguments and assigns the result to z. As a result, x and lst1 are aliased, y and lst2 are aliased, and then r and z are aliased after the return.
It prints x, y, and then z.
Now, let's predict the output:
After calling fun(x, y):
lst1 will be modified to [11, 12, 13] because each element of lst1 is incremented by 10. These changes will be reflected in x as well as they are aliased
lst2 remains unchanged at [3, 4, 5]. So does y, as they are aliased.
The returned list r will be [6, 8, 10] because each element of lst2 is doubled and saved in r.
After the function call, the modified x, y, and the returned list z will be printed:
s and s1, and lst and lst1 are aliased when we call mystery with s1 and lst1. However, when within the function we change s = s + “world” we are creating a brand new string “helloworld” but it does not change the original string “hello”. Similarly, when within mystery we do lst = lst + [99] we are non-destructively changing the list, by changing the reference.
As a result, s1 and lst1 remain unchanged when we print them.
Code Writing
[11.27] Write a line of code to do each of the following:
a. Create a list with the numbers 20, 3, 50, 19 in it
b. Create a list with the strings “one”, “two”, “three”
c. Create a list with values of different types
Solution:
a. lst1 = [20, 3, 50, 19]
b. lst2 = ["one", "two", "three"]
c. lst3 = [0.1, "haha", True, 50]
[11.28] Write a function called findSum that takes as a parameter a list of numbers (integers and floats) and returns their sum.
Solution:
# Function to find the sum of a list of numbers using a loop
def findSum(numbers):
total = 0
for num in numbers: # you can also use in range here
total += num
return total
# Example usage:
numbers_list = [1, 5.5, 3, 8.2, 2.7]
sum_result = findSum(numbers_list)
print(sum_result)
[11.29] Write a function called findMax that takes as a parameter a list of integers and returns the maximum number.
Solution:
# Function to find the maximum of a list of numbers using a loop
def findMax(numbers):
max_num = numbers[0]
for i in range(1, len(numbers)):
if numbers[i] > max_num:
max_num = numbers[i]
return max_num
# Example usage:
numbers_list = [1, 5, 3, 8, 2]
max_result = findMax(numbers_list)
print(max_result)
[11.30] Write a function called removeAll that takes a list as a parameter and a value, and removes all occurrences of the given value from the given list.
Solution:
def removeAll(lst, value):
i = 0
while i < len(lst):
if lst[i] == value:
lst.remove(lst[i])
else:
i = i + 1
return lst
print(removeAll([1,2,1,1,1,1], 1)) # prints [2]
[11.31] Write a function that takes a list of numbers and returns True if the list is sorted in ascending order or False otherwise.
Solution:
# Function to check if a list is sorted in ascending order
def isSorted(numbers):
# Check for ascending order
for i in range(len(numbers) - 1):
if numbers[i] > numbers[i + 1]:
return False
# If not ascending return False
return True
# Example usage:
ascending_list = [1, 2, 3, 4, 5]
unsorted_list = [3, 1, 4, 2, 5]
print(isSorted(ascending_list)) #True
print(isSorted(unsorted_list)) #False
[11.32] Write a function that takes a 2D list and returns a list of the sums of each row.
Solution:
# Function to calculate the sum of each row in a 2D list
def sum_of_rows(matrix):
row_sums = []
for row in matrix:
total = 0
for element in row:
total += element
row_sums.append(total)
return row_sums
# Example usage:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(sum_of_rows(matrix))
[11.33] Write a function that finds and prints the minimum and maximum values in a 2D list.
Solution:
# Function to find the minimum and maximum values in a 2D list
def find_min_max(matrix):
min_val = matrix[0][0]
max_val = matrix[0][0]
for row in matrix:
for element in row:
if element < min_val:
min_val = element
if element > max_val:
max_val = element
print("Min value is: ", min_val)
print("Max value is: ", max_val)
# Example usage:
matrix = [[1, 2, 3], [4, 5, 63], [7, -8, 9]]
find_min_max(matrix)
[11.34] Write a function to check if a 2D list is a square matrix (the number of rows and columns are equal).
Solution:
# Function to check if a 2D list is a square matrix
def is_square_matrix(matrix):
num_rows = len(matrix)
for row in matrix:
if len(row) != num_rows:
return False
return True
print(is_square_matrix([[1], [2,3], [4]]))
print(is_square_matrix([[1,7,3], [2,3,2], [4,5,6]]))
[11.35] Write a function called countVal that takes a list and a value as a paramter and returns how many times the value is present in the list or -1 if the value is not in the list.
Solution:
def countVal(lst, value):
count = 0
for i in range(len(lst)):
if lst[i] == value:
count = count + 1
if count == 0:
return -1
else:
return count
print(countVal([1,22,3,4,5,22,4,22,5,22],22))
print(countVal([1,22,3,4,5,22,4,22,5,22],100))
[11.36] Write a function called addAll that takes a 2 dimensional list of numbers and finds the sum of all the numbers in it.
Solution:
# Function to find the sum of all numbers in a 2D list
def addAll(matrix):
total = 0
for i in range(len(matrix)):
for j in range(len(matrix[i])):
total += matrix[i][j]
return total
[11.37] Write a function that shifts a list by k elements, where k < n. For instance, given an array [1,2,3,4,5,6,7] and k = 3, the array should be modified to [5,6,7,1,2,3,4].
Example:
Input: nums = [1,2,3,4,5,6,7], k = 3
Output: [5,6,7,1,2,3,4]
def shift_list(lst, k):
# Initialize a counter
count = 0
# Continue shifting until count reaches k
while count < k:
# Remove the last element of the list and store it
popped_element = lst.pop()
# Insert the popped element at the beginning of the list
lst.insert(0, popped_element)
# Increment the counter
count += 1
# Example usage:
arr = [1, 2, 3, 4, 5, 6, 7]
k = 3
shift_list(arr, k)
print(arr) # Output: [5, 6, 7, 1, 2, 3, 4]
[11.38] Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements. You must do this destructively.
Example:
Input: [0,1,0,3,12]
Output: [1,3,12,0,0]
def move_zeroes(nums):
zeroCounter = 0
i = 0
while i < len(nums):
if nums[i] == 0:
nums.pop(i)
zeroCounter = zeroCounter + 1
else:
i = i + 1
for j in range(zeroCounter):
nums.append(0)
# Test the function
nums = [0, 1, 0, 3, 12]
move_zeroes(nums)
print(nums) # Output should be [1, 3, 12, 0, 0]
[11.39] Given a list of positive integers, find the second largest number in the list.
Example:
Input: [10, 5, 20, 8]
Output: 10
def second_largest(nums):
first = -1
second = -1
for num in nums:
if num > first:
second = first
first = num
elif num > second and num != first:
second = num
return second
# Test the function
nums = [10, 5, 20, 8]
print(second_largest(nums)) # Output should be 10
[11.40] Given a list of integers, rearrange the list so that all odd numbers come before all even numbers, but the relative order of odd and even numbers should be preserved. you may use non-destructive operations.
Example:
Input: [1, 2, 3, 4, 5, 6, 7]
Output: [1, 3, 5, 7, 2, 4, 6]
def odd_even_partition(nums):
odd = []
even = []
for num in nums:
if num % 2 == 0:
even.append(num)
else:
odd.append(num)
return odd + even
# Test the function
nums = [1, 2, 3, 4, 5, 6, 7]
print(odd_even_partition(nums)) # Output should be [1, 3, 5, 7, 2, 4, 6]
[11.41] Write a function that takes a 1D list and returns a new list that is the reverse of the original list. Do this both non-destructively and destructively.
Example:
Input: [1, 2, 3, 4]
Output: [4, 3, 2, 1]
Non-destructive:
ddef reverse_list_non_destructive(nums):
lst = []
for el in nums:
lst.insert(0, el)
return lst
# Test the function
original_list = [1, 2, 3, 4]
reversed_list = reverse_list_non_destructive(original_list)
print("Original:", original_list) # Should be [1, 2, 3, 4]
print("Reversed:", reversed_list) # Should be [4, 3, 2, 1]
Destructive:
def reverse_list_destructive(nums):
count = 0 # index where we move to
n = len(nums) # length of list
i = len(nums) - 1 # index where we pop from
while count < n:
v = nums.pop(i) # remove last value
nums.insert(count, v) #insert the removed value at the front at count
count = count + 1
# Test the function
nums = [1, 2, 3, 4]
reverse_list_destructive(nums)
print(nums) # Should be [4, 3, 2, 1]
[11.42] Write a function that takes a sorted 1D list of integers and returns a new list with all duplicates removed, preserving the original order of elements. The original list should remain unchanged (non-destructive).
Example:
Input: [1, 2, 2, 3, 4, 4, 5]
Output: [1, 2, 3, 4, 5]
def remove_duplicates_non_destructive(nums):
unique = []
for num in nums:
if num not in unique:
unique.append(num)
return unique
# Test the function
original_list = [1, 2, 2, 3, 4, 4, 5]
unique_list = remove_duplicates_non_destructive(original_list)
print("Original:", original_list) # Should be [1, 2, 2, 3, 4, 4, 5]
print("Unique:", unique_list) # Should be [1, 2, 3, 4, 5]
[11.43] Write a function that replaces all elements in a 1D list greater than a given value n with n itself, modifying the list in place (destructive).
Example:
Input: nums = [2, 3, 5, 8, 1], n = 4
Output: nums should become [2, 3, 4, 4, 1]
def replace_elements_greater_than_n(nums, n):
for i in range(len(nums)):
if nums[i] > n:
nums[i] = n
# Test the function
nums = [2, 3, 5, 8, 1]
replace_elements_greater_than_n(nums, 4)
print(nums) # Output should be [2, 3, 4, 4, 1]