Exercise 1
Problem description
It is hard to find the pattern of the input and output. But through this example:
1 | statements = 'from exercise_6_1 import f1; '\ |
We need to sort the elements into a one-dimensional list and then split them according to the lengths of the sublists in the input.
My Solution
So, the function f1
should be like this:
1 | def f1(L): |
Standard Solution
1 | def f1(L): |
Explanation
The difference between the two solutions is that the standard solution uses the walrus operator :=
to simplify the code. The walrus operator is a new feature in Python 3.8. It is used to assign a value to a variable as part of an expression. It is useful when you want to assign a value to a variable and use that value in the same expression.
Exercise 2
Problem description
This exercise ask us to output the sum of diagonal elements of a matrix. Based on the line where i
, j
are located.
We can use Numpy
to solve this problem. It has a function numpy.diagonal()
that can return the diagonal elements of a matrix.
My Solution
1 | import numpy as np |
Explanation
In a normal matrix, the np.diag()
function allows extracting diagonals at different offsets, where the offset k=0
represents the main diagonal.
When we flip the matrix, the coordinates for each cell change in such a way that we need to adjust our calculation of the offset accordingly.
Specifically, for a matrix of size n
, flipping it means that the column index j
of the original matrix transforms to (n - 1 - j)
in the flipped version. Hence, when we want to calculate the offset of the diagonal that passes through (i, j)
in the original matrix, we need to determine the offset using (n - 1 - j) - i
to correctly represent the position in the flipped version.
Standard Solution
1 | def f2(L, i, j, major=True): |
Explanation
The standard solution uses a while loop to iterate through the elements of the matrix based on the given indices i
and j
. It calculates the sum of the diagonal elements by moving in the specified direction (major or minor diagonal) and updating the sum accordingly.
Exercise 3
Problem description
This exercise involves swapping elements in a square matrix with an even side length such that each element in a specified half (‘top’, ‘bottom’, ‘left’, or ‘right’) is at least equal to its diagonally opposite element.
If the half is ‘top’ or ‘left’, the element in that half should be at least equal to the diagonally opposite one, otherwise they should be swapped. Conversely, if the half is ‘bottom’ or ‘right’, the element in the other half should be swapped if it is greater.
Standard Solution
The solution is implemented by creating a copy of the matrix and then iterating over the relevant half to determine if swapping is necessary based on the given criteria.
1 | # Assume that the argument L is a list of lists of integers |
Explanation
The approach in my solution uses a dictionary to define the ranges for each half of the matrix. By iterating over these ranges, it ensures that only the elements in the specified half are considered. If the element in the specified half is smaller than its diagonally opposite counterpart, they are swapped.
The ranges dictionary defines which rows and columns are to be iterated based on the specified half:
'top'
considers the top half of the matrix (rows from0
ton//2
)'bottom'
considers the bottom half of the matrix (rows fromn//2
ton
)'left'
considers the left half of the matrix (columns from0
ton//2
)'right'
considers the right half of the matrix (columns fromn//2
ton
)
The values are swapped only if they do not meet the condition defined for the given half.
Exercise 4
Problem description
This exercise involves creating a visual pattern on an n x n
grid using two different characters to represent black and white cells. The argument n
is an integer at least equal to 1, and the argument black_center
is either True
or False
, which influences the color of the center of the grid. The function should use np.full()
to create the grid and modify it using simple loops without nested loops.
The function prints the grid instead of returning it.
My Solution
The solution starts by creating an n x n
grid initialized entirely to either black or white, depending on whether the center should be black or not. The function then iteratively adjusts concentric squares within the grid, switching the colors as needed.
1 | import numpy as np |
Explanation
- The function
black(i, black_centre)
determines the color of the concentric square based on the current sizei
and whether the center should be black (black_centre
argument). - The
np.full()
function is used to create the grid, either filled with black (‘⬛’) or white (‘⬜’), depending on the value ofblack(n, black_centre)
, which determines the color of the center. - The
for
loop iterates through half of the matrix (n // 2
), adjusting each concentric square layer by layer.- The slicing operation
grid[i : n - i, i : n - i]
selects the relevant inner square and fills it with either black or white, alternating colors as determined byblack(n - 2 * i, black_centre)
.
- The slicing operation
- Finally, the grid is printed row by row, with each character printed consecutively for easy visualization.
Standard Solution
1 | import numpy as np |
Explanation
The standard solution follows a similar approach to my solution but uses slightly different syntax to achieve the same result:
- The grid is created using
np.full()
, just like in my solution. - The loop iterates through each concentric layer, updating the color accordingly. The variable
color
is used to determine what color each inner square should be, making the assignment more readable. - The grid is printed in a slightly different way by joining the row’s elements into a single string (
print(''.join(row))
). This makes the output cleaner by removing the spaces between characters, which is purely aesthetic.
The primary differences between my solution and the standard solution are the use of inline expressions for color selection and minor differences in how the final output is formatted when printed.
Exercise 5
Try it!
1 | import numpy as np |
Problem description
The exercise is to compute the sum of elements in a given row and column and display a colored square at their intersection based on the sign of the sum:
- A blue square (‘🟦’) if the sum is
0
. - A green square (‘🟩’) if the sum is strictly positive.
- A red square (‘🟥’) if the sum is strictly negative.
For example, given a list of lists representing the matrix:
1 | . . 2 . . |
The function should compute the sum for each row and column, and update the intersection elements accordingly.
The function should use np.sum()
and np.sign()
to simplify calculations, and should use at most loops within loops, avoiding deeper nesting.
The output is printed, not returned.
My Solution
The solution uses NumPy to calculate the sum of each row and column. The function then iterates through the given list and determines the sign of the sum at each intersection, displaying the appropriate color.
1 | import numpy as np |
Explanation
- NumPy Conversion: The list
L
is converted to a NumPy array to take advantage of the efficientnp.sum()
function for computing sums. - Row and Column Sums: The sums for rows and columns are computed separately using
np.sum()
with the appropriateaxis
argument. - Iteration for Colors: The function then iterates over each element in the matrix to determine the color of the intersection based on the calculated row and column sums. The color is chosen as follows:
'🟦'
(blue) for a sum of0
.'🟩'
(green) for a strictly positive sum.'🟥'
(red) for a strictly negative sum.
- Output: Finally, the grid is printed row by row.
Standard Solution
1 | import numpy as np |
Explanation
The standard solution follows a similar approach to my solution but uses a dictionary to map the sign of the sum to the corresponding color. The np.sign()
function is used to determine the sign of the sum, and the dictionary is used to select the appropriate color based on the sign.
Exercise 6
Problem description
This exercise is similar to Exercise 5, where we compute the sum of elements in a given row and column and display a colored square at their intersection based on the sign of the sum:
- A blue square (‘🟦’) if the sum is
0
. - A green square (‘🟩’) if the sum is strictly positive.
- A red square (‘🟥’) if the sum is strictly negative.
However, the main differences are:
- Input Structure: The list
L
must be a square matrix (i.e., the number of rows equals the number of columns). - Optimization Requirement: The solution for
f6
must avoid manual loops for computation (except for output). Instead, the solution must use vectorized operations in NumPy to enhance computational efficiency. - Advanced NumPy Usage: The exercise explicitly suggests using advanced NumPy functions such as
np.vectorize()
,np.newaxis
, and broadcasting.
The output is printed, not returned.
My Solution
The solution for f6
uses advanced NumPy techniques to fully vectorize the computations without using explicit loops (except for printing). It creates the required colored grid by computing the sum for each row and column and then using a vectorized mapping to determine the color.
1 | import numpy as np |
Explanation
- Vectorization: The solution uses
np.vectorize()
to apply a function element-wise over an array, effectively mapping each sum to a specific color. - Avoiding Loops: Instead of iterating through each element manually, the solution leverages NumPy’s vectorization to calculate row and column sums simultaneously.
- Broadcasting and
np.newaxis
: The use of[:, np.newaxis]
helps in broadcasting the row sums across the columns to facilitate efficient addition with the column sums. This allows the entire computation to be performed without explicit looping. - Output: Finally, each row is printed with the appropriate color, similar to Exercise 5.
Key Differences from Exercise 5
- Input Structure: Exercise 5 allows any list of lists, whereas Exercise 6 requires a square matrix.
- Loop Usage: Exercise 5 uses explicit loops to calculate intersection sums, whereas Exercise 6 relies entirely on vectorized operations and avoids manual loops (except for printing).
- Efficiency: Exercise 6 is more computationally efficient, as it is designed to handle larger datasets using optimized NumPy operations, whereas Exercise 5 uses loops that can be slower for large matrices.