Exercises#

Introduction#

Let’s see now some exercises. First ones will be given in two versions: first ones usually adopt for cycles and are thus slow, second ones are denoted ‘pro’ and avoid loops using all the power offered by Numpy. In particular in many cases you can obtain very efficient and compact programs by using slices in smart ways.

import numpy as np

All exercises are functions to implement.

Exercise - frameslices#

✪✪✪ RETURN a NEW Numpy matrix of n rows and n columns, in which all the values are zero except those on borders, which must be equal to a given k.

DO NOT use for nor while loops, use slices!

Hide code cell source
def frameslices(n, k):
    mat = np.zeros((n, n))

    mat[0, :] = k
    mat[:, 0] = k
    mat[:, -1] = k
    mat[-1, :] = k

    return mat
r1 = np.array(
    [
        [7.0, 7.0, 7.0, 7.0],
        [7.0, 0.0, 0.0, 7.0],
        [7.0, 0.0, 0.0, 7.0],
        [7.0, 7.0, 7.0, 7.0],
    ]
)

# all_close return True if all the values in the first matrix are close enough
# (that is, within a given tolerance) to corresponding values in the second
assert np.allclose(frameslices(4, 7.0), r1)

r2 = np.array([[7.0]])
assert np.allclose(frameslices(1, 7.0), r2)

r3 = np.array([[7.0, 7.0], [7.0, 7.0]])
assert np.allclose(frameslices(2, 7.0), r3)

Exercise - framefill#

✪✪✪ Solve the precious exercise, this using np.full function and with only one slice

  • DO NOT use for nor while loops

Hide code cell source
def framefill(n, k):
    mat = np.full((n, n), k)
    mat[1 : -1, 1 : -1] = 0.0
    return mat
r1 = np.array(
    [
        [7.0, 7.0, 7.0, 7.0],
        [7.0, 0.0, 0.0, 7.0],
        [7.0, 0.0, 0.0, 7.0],
        [7.0, 7.0, 7.0, 7.0],
    ]
)

# all_close return True if all the values in the first matrix are close enough
# (that is, within a given tolerance) to corresponding values in the second
assert np.allclose(framefill(4, 7.0), r1)

r2 = np.array([[7.0]])
assert np.allclose(framefill(1, 7.0), r2)

r3 = np.array([[7.0, 7.0], [7.0, 7.0]])
assert np.allclose(framefill(2, 7.0), r3)

Exercise - avg_rows#

✪✪✪ Takes a numpy matrix n x m and RETURN a NEW numpy matrix consisting in a single column in which the values are the average of the values in corresponding rows of input matrix

Example:

Input: 5x4 matrix

3 2 1 4
6 2 3 5
4 3 6 2
4 6 5 4
7 2 9 3

Output: 5x1 matrix

(3+2+1+4)/4
(6+2+3+5)/4
(4+3+6+2)/4
(4+6+5+4)/4
(7+2+9+3)/4

Basic version ingredients (slow)

  • create a matrix n x 1 to return, filling it with zeros

  • visit all cells of original matrix with two nested fors

  • during visit, accumulate in the matrix to return the sum of elements takes from each row of original matrix

  • once completed the sum of a row, you can divide it by the dimension of columns of original matrix

  • return the matrix

Pro version (fast):

  • try using axis parameter and reshape

Hide code cell source
def avg_rows(mat):
    nrows, ncols = mat.shape
    ret = np.zeros((nrows, 1))
    for i in range(nrows):
        for j in range(ncols):
            ret[i] += mat[i, j]
        ret[i] = ret[i] / ncols
        # for brevity we could also write
        # ret[i] /= ncols
    return ret


def avg_rows_pro(mat):
    rows, cols = mat.shape  # obtain number of rows and columns
    media = np.mean(mat, axis=1)  # average for rows
    media = media.reshape((rows, 1))
    return media
m1 = np.array([[5.0]])
r1 = np.array([[5.0]])
assert np.allclose(avg_rows(m1), r1)

m2 = np.array([[5.0, 3.0]])
r2 = np.array([[4.0]])
assert np.allclose(avg_rows(m2), r2)


m3 = np.array([[3, 2, 1, 4], [6, 2, 3, 5], [4, 3, 6, 2], [4, 6, 5, 4], [7, 2, 9, 3]])

r3 = np.array(
    [
        [(3 + 2 + 1 + 4) / 4],
        [(6 + 2 + 3 + 5) / 4],
        [(4 + 3 + 6 + 2) / 4],
        [(4 + 6 + 5 + 4) / 4],
        [(7 + 2 + 9 + 3) / 4],
    ]
)

assert np.allclose(avg_rows(m3), r3)

Exercise - matrot#

✪✪✪ RETURN a NEW Numpy matrix which has the numbers of input matrix rotated by a column.

With rotation we mean that:

  • if a number of input matrix is found in column j, in the output matrix it will be in the column j+1 in the same row.

  • if a number is found in the last column, in the output matrix it will be in the zertoth column

Hide code cell source
def matrot(mat):
    ret = np.zeros(mat.shape)

    for i in range(mat.shape[0]):
        ret[i, 0] = mat[i, -1]
        for j in range(1, mat.shape[1]):
            ret[i, j] = mat[i, j - 1]
    return ret


def matrot_pro(mat):
    m = mat.shape[0]
    n = mat.shape[1]

    ret = np.zeros((m, n))

    ret[:, 0] = mat[:, -1]
    ret[:, 1:] = mat[:, :-1]
    return ret
input = np.array(
    [
        [0, 1, 0],
        [1, 1, 0],
        [0, 0, 0],
        [0, 1, 1],
    ]
)
matrot(input)
array([[0., 0., 1.],
       [0., 1., 1.],
       [0., 0., 0.],
       [1., 0., 1.]])
m1 = np.array([[1]])
r1 = np.array([[1]])

assert np.allclose(matrot(m1), r1)

m2 = np.array([[0, 1]])
r2 = np.array([[1, 0]])
assert np.allclose(matrot(m2), r2)

m3 = np.array([[0, 1, 0]])
r3 = np.array([[0, 0, 1]])

assert np.allclose(matrot(m3), r3)

m4 = np.array(
    [
        [0, 1, 0],
        [1, 1, 0],
    ]
)
r4 = np.array(
    [
        [0, 0, 1],
        [0, 1, 1],
    ]
)
assert np.allclose(matrot(m4), r4)


m5 = np.array(
    [
        [0, 1, 0],
        [1, 1, 0],
        [0, 0, 0],
        [0, 1, 1],
    ]
)
r5 = np.array(
    [
        [0, 0, 1],
        [0, 1, 1],
        [0, 0, 0],
        [1, 0, 1],
    ]
)
assert np.allclose(matrot(m5), r5)

Exercise - odd#

✪✪✪ Takes a Numpy matrix mat of dimension nrows by ncols containing integer numbers and RETURN a NEW Numpy matrix of dimension nrows by ncols which is like the original, ma in the cells which contained even numbers now there will be odd numbers obtained by summing 1 to the existing even number.

Basic versions hints (slow):

  • Since you need to return a matrix, start with creating an empty one

  • go through the whole input matrix with indices i and j

Hide code cell source
def odd(mat):
    nrows, ncols = mat.shape
    ret = np.zeros((nrows, ncols))
    for i in range(nrows):
        for j in range(ncols):
            if mat[i, j] % 2 == 0:
                ret[i, j] = mat[i, j] + 1
            else:
                ret[i, j] = mat[i, j]
    return ret


def odd_pro1(mat):
    ret = np.array(np.where(mat % 2 == 0, mat + 1, mat))
    return ret


def odd_pro2(mat):
    ret = mat.copy()
    ret[ret % 2 == 0] += 1
    return ret
odd(
    np.array(
        [
            [2, 5, 6, 3],
            [8, 4, 3, 5],
            [6, 1, 7, 9],
        ]
    )
)
array([[3., 5., 7., 3.],
       [9., 5., 3., 5.],
       [7., 1., 7., 9.]])
m1 = np.array([[2]])
m2 = np.array([[3]])
assert np.allclose(odd(m1), m2)
assert m1[0][0] == 2  # checks we are not modifying original matrix


m3 = np.array(
    [
        [2, 5, 6, 3],
        [8, 4, 3, 5],
        [6, 1, 7, 9],
    ]
)
m4 = np.array(
    [
        [3, 5, 7, 3],
        [9, 5, 3, 5],
        [7, 1, 7, 9],
    ]
)
assert np.allclose(odd(m3), m4)

Exercise - doublealt#

✪✪✪ Takes a Numpy matrix mat of dimensions nrows x ncols containing integer numbers and RETURN a NEW Numpy matrix of dimension nrows x ncols having at rows of even index the numbers of original matrix multiplied by two, and at rows of odd index the same numbers as the original matrix.

Hide code cell source
def doublealt(mat):
    nrows, ncols = mat.shape
    ret = np.zeros((nrows, ncols))

    for i in range(nrows):
        for j in range(ncols):
            if i % 2 == 0:
                ret[i, j] = mat[i, j] * 2
            else:
                ret[i, j] = mat[i, j]
    return ret


def doublealt_pro(mat):
    ret = mat.copy()
    ret[::2, :] *= 2
    return ret
m = np.array(
    [
        [2, 5, 6, 3],  #    0     even
        [8, 4, 3, 5],  #    1     odd
        [7, 1, 6, 9],  #    2     even
        [5, 2, 4, 1],  #    3     odd
        [6, 3, 4, 3],  #    4     even
    ]
)
doublealt(m)
array([[ 4., 10., 12.,  6.],
       [ 8.,  4.,  3.,  5.],
       [14.,  2., 12., 18.],
       [ 5.,  2.,  4.,  1.],
       [12.,  6.,  8.,  6.]])
m1 = np.array([[2]])
m2 = np.array([[4]])
assert np.allclose(doublealt(m1), m2)
assert m1[0][0] == 2  # checks we are not modifying original matrix


m3 = np.array([[2, 5, 6], [8, 4, 3]])
m4 = np.array([[4, 10, 12], [8, 4, 3]])
assert np.allclose(doublealt(m3), m4)

m5 = np.array([[2, 5, 6, 3], [8, 4, 3, 5], [7, 1, 6, 9], [5, 2, 4, 1], [6, 3, 4, 3]])
m6 = np.array(
    [[4, 10, 12, 6], [8, 4, 3, 5], [14, 2, 12, 18], [5, 2, 4, 1], [12, 6, 8, 6]]
)
assert np.allclose(doublealt(m5), m6)

Exercise - chessboard#

✪✪✪ RETURN a NEW Numpy matrix of n rows and n columns, in which all cells alternate zeros and ones.

Basic version ingredients (slow):

  • to alternate, you can use range in the form in which takes 3 parameters, for example range(0,n,2) starts from 0, arrives to n excluded by jumping one item at a time, generating 0,2,4,6,8, ….

  • range(1,n,2) would instead generate 1,3,5,7, …

Hide code cell source
def chessboard(n):
    mat = np.zeros((n, n))

    for i in range(0, n, 2):
        for j in range(0, n, 2):
            mat[i, j] = 1

    for i in range(1, n, 2):
        for j in range(1, n, 2):
            mat[i, j] = 1

    return mat


def chessboard_pro(n):
    ret = np.zeros((n, n))
    ret[::2, ::2] = 1
    ret[1::2, 1::2] = 1
    return ret
chessboard(4)
array([[1., 0., 1., 0.],
       [0., 1., 0., 1.],
       [1., 0., 1., 0.],
       [0., 1., 0., 1.]])
r1 = np.array(
    [
        [1.0, 0.0, 1.0, 0.0],
        [0.0, 1.0, 0.0, 1.0],
        [1.0, 0.0, 1.0, 0.0],
        [0.0, 1.0, 0.0, 1.0],
    ]
)
assert np.allclose(chessboard(4), r1)

r2 = np.array([[1.0]])
assert np.allclose(chessboard(1), r2)

r3 = np.array([[1.0, 0.0], [0.0, 1.0]])
assert np.allclose(chessboard(2), r3)

Exercise - altsum#

✪✪✪ MODIFY the input Numpy matrix (n x n), by summing to all the odd rows the even rows. For example

Basic version ingredients (slow):

  • to alternate, you can use range in the form in which takes 3 parameters, for example range(0,n,2) starts from 0, arrives to n excluded by jumping one item at a time, generating 0,2,4,6,8, ….

  • instead range(1,n,2) would generate 1,3,5,7, ..

Hide code cell source
def altsum(mat):
    nrows, ncols = mat.shape
    for i in range(1, nrows, 2):
        for j in range(0, ncols):
            mat[i, j] = mat[i, j] + mat[i - 1, j]


def altsum_pro(mat):
    mat[1::2] += mat[::2]
    return mat
m = np.array(
    [
        [1.0, 3.0, 2.0, 5.0],
        [2.0, 8.0, 5.0, 9.0],
        [6.0, 9.0, 7.0, 2.0],
        [4.0, 7.0, 2.0, 4.0],
    ]
)
altsum(m)
m
array([[ 1.,  3.,  2.,  5.],
       [ 3., 11.,  7., 14.],
       [ 6.,  9.,  7.,  2.],
       [10., 16.,  9.,  6.]])
m1 = np.array(
    [
        [1.0, 3.0, 2.0, 5.0],
        [2.0, 8.0, 5.0, 9.0],
        [6.0, 9.0, 7.0, 2.0],
        [4.0, 7.0, 2.0, 4.0],
    ]
)

r1 = np.array(
    [
        [1.0, 3.0, 2.0, 5.0],
        [3.0, 11.0, 7.0, 14.0],
        [6.0, 9.0, 7.0, 2.0],
        [10.0, 16.0, 9.0, 6.0],
    ]
)

altsum(m1)
assert np.allclose(m1, r1)  # checks we MODIFIED the original matrix

m2 = np.array([[5.0]])
r2 = np.array([[5.0]])
altsum(m1)
assert np.allclose(m2, r2)

m3 = np.array([[6.0, 1.0], [3.0, 2.0]])
r3 = np.array([[6.0, 1.0], [9.0, 3.0]])
altsum(m3)
assert np.allclose(m3, r3)

Exercise - avg_half#

✪✪✪ Takes as input a Numpy matrix with an even number of columns, and RETURN as output a Numpy matrix 1x2, in which the first element will be the average of the left half of the matrix, and the second element will be the average of the right half.

Ingredients:

  • to obtain the number of columns divided by two as integer number, use // operator

Hide code cell source
def avg_half(mat):
    nrows, ncols = mat.shape
    half_cols = ncols // 2

    avg_sx = 0.0
    avg_dx = 0.0

    for i in range(nrows):
        for j in range(half_cols):
            avg_sx += mat[i, j]
        for j in range(half_cols, ncols):
            avg_dx += mat[i, j]

    half_elements = nrows * half_cols
    avg_sx /= half_elements
    avg_dx /= half_elements
    return np.array([avg_sx, avg_dx])


def avg_half_pro(mat):
    n, m = mat.shape

    m2 = m // 2
    half_els = n * m2

    avg = np.zeros((1, 2))

    avg[0, 0] = np.sum(mat[:, :m2]) / half_els
    avg[0, 1] = np.sum(mat[:, m2:]) / half_els

    return avg
m1 = np.array([[7, 9]])

r1 = np.array([(7) / 1, (9) / 1])
assert np.allclose(avg_half(m1), r1)


m2 = np.array([[3, 4], [6, 3], [5, 2]])

r2 = np.array([(3 + 6 + 5) / 3, (4 + 3 + 2) / 3])
assert np.allclose(avg_half(m2), r2)

m3 = np.array(
    [
        [3, 2, 1, 4],
        [6, 2, 3, 5],
        [4, 3, 6, 2],
        [4, 6, 5, 4],
        [7, 2, 9, 3],
    ]
)

r3 = np.array(
    [
        (3 + 2 + 6 + 2 + 4 + 3 + 4 + 6 + 7 + 2) / 10,
        (1 + 4 + 3 + 5 + 6 + 2 + 5 + 4 + 9 + 3) / 10,
    ]
)

assert np.allclose(avg_half(m3), r3)

Exercise - matxarr#

✪✪ Takes a Numpy matrix n x m and an ndarray of m elements, and RETURN a NEW Numpy matrix in which the values of each column of input matrix are multiplied by the corresponding value in the n elements array.

Hide code cell source
def matxarr(mat, arr):
    ret = np.zeros(mat.shape)

    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            ret[i, j] = mat[i, j] * arr[j]

    return ret


def matxarr_pro(mat, arr):
    return np.array(arr) * mat
m1 = np.array(
    [
        [3, 2, 1],
        [6, 2, 3],
        [4, 3, 6],
        [4, 6, 5],
    ]
)
a1 = [5, 2, 6]
r1 = [
    [3 * 5, 2 * 2, 1 * 6],
    [6 * 5, 2 * 2, 3 * 6],
    [4 * 5, 3 * 2, 6 * 6],
    [4 * 5, 6 * 2, 5 * 6],
]

assert np.allclose(matxarr(m1, a1), r1)

Exercise - colgap#

✪✪ Given a numpy matrix of \(n\) rows and \(m\) columns, RETURN a numpy vector of \(m\) elements consisting in the difference between the maximum and minimum values of each column.

Hide code cell source
def colgap(mat):
    mx = mat[0].copy()
    mn = mat[0].copy()
    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            if mat[i, j] > mx[j]:
                mx[j] = mat[i, j]
            if mat[i, j] < mn[j]:
                mn[j] = mat[i, j]
    return mx - mn


def colgap_pro(mat):
    mx = np.max(mat, axis=0)
    mn = np.min(mat, axis=0)
    return mx - mn
m = np.array(
    [
        [5, 4, 2],
        [8, 5, 1],
        [6, 7, 9],
        [3, 6, 4],
        [4, 3, 7],
    ]
)
colgap(m)
array([5, 4, 8])
m1 = np.array([[6]])
assert np.allclose(colgap(m1), np.array([0]))
ret = colgap(m1)
assert isinstance(ret, np.ndarray)

m2 = np.array([[6, 8]])
assert np.allclose(colgap(m2), np.array([0, 0]))
m3 = np.array(
    [
        [2],
        [5],
    ]
)

assert np.allclose(colgap(m3), np.array([3]))
m4 = np.array(
    [
        [5, 7],
        [2, 9],
    ]
)
assert np.allclose(colgap(m4), np.array([3, 2]))
m5 = np.array(
    [
        [4, 7],
        [4, 9],
    ]
)
assert np.allclose(colgap(m5), np.array([0, 2]))
m6 = np.array(
    [
        [5, 2],
        [3, 7],
        [9, 0],
    ]
)
assert np.allclose(colgap(m6), np.array([6, 7]))
m7 = np.array(
    [
        [5, 4, 2],
        [8, 5, 1],
        [6, 7, 9],
        [3, 6, 4],
        [4, 3, 7],
    ]
)
assert np.allclose(colgap(m7), np.array([5, 4, 8]))

Exercise - substmax#

✪✪ Given an \(n\) x \(m\) numpy matrix mat, MODIFY the matrix substituting each cell with the maximum value found in the corresponding column.

Hide code cell source
def substmax(mat):
    mx = mat[0].copy()
    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            if mat[i, j] > mx[j]:
                mx[j] = mat[i, j]
    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            mat[i, j] = mx[j]


def substmax_pro(mat):
    mat[:, :] = np.max(mat, axis=0)
m = np.array(
    [
        [5, 4, 2],
        [8, 5, 1],
        [6, 7, 9],
        [3, 6, 4],
        [4, 3, 7],
    ]
)
substmax(m)  # returns nothing!
m
array([[8, 7, 9],
       [8, 7, 9],
       [8, 7, 9],
       [8, 7, 9],
       [8, 7, 9]])
m1 = np.array([[6]])
substmax(m1)
assert np.allclose(m1, np.array([6]))
ret = substmax(m1)
assert ret is None  # returns nothing!

m2 = np.array([[6, 8]])
substmax(m2)
assert np.allclose(m2, np.array([6, 8]))

m3 = np.array([[2], [5]])
substmax(m3)
assert np.allclose(m3, np.array([[5], [5]]))

m4 = np.array([[5, 7], [2, 9]])
substmax(m4)

assert np.allclose(m4, np.array([[5, 9], [5, 9]]))

m5 = np.array([[4, 7], [4, 9]])
substmax(m5)
assert np.allclose(m5, np.array([[4, 9], [4, 9]]))

m6 = np.array(
    [
        [5, 2],
        [3, 7],
        [9, 0],
    ]
)
expected = np.array(
    [
        [9, 7],
        [9, 7],
        [9, 7],
    ]
)
substmax(m6)
assert np.allclose(m6, expected)

m7 = np.array(
    [
        [5, 4, 2],
        [8, 5, 1],
        [6, 7, 9],
        [3, 6, 4],
        [4, 3, 7],
    ]
)
expected = np.array(
    [
        [8, 7, 9],
        [8, 7, 9],
        [8, 7, 9],
        [8, 7, 9],
        [8, 7, 9],
    ]
)
substmax(m7)
assert np.allclose(m7, expected)

Exercise - quadrants#

✪✪✪ Given a matrix 2n * 2n, divide the matrix in 4 equal square parts (see example) and RETURN a NEW matrix 2 * 2 containing the average of each quadrant.

We assume the matrix is always of even dimensions

HINT: to divide by two and obtain an integer number, use // operator

Example:

 1, 2 , 5 , 7
 4, 1 , 8 , 0
 2, 0 , 5 , 1
 0, 2 , 1 , 1

can be divided in

  1, 2 | 5 , 7
  4, 1 | 8 , 0
-----------------
  2, 0 | 5 , 1
  0, 2 | 1 , 1

and returns

  (1+2+4+1)/ 4  | (5+7+8+0)/4                        2.0 , 5.0
  -----------------------------            =>        1.0 , 2.0
  (2+0+0+2)/4   | (5+1+1+1)/4
Hide code cell source
def quadrants(mat):
    ret = np.zeros((2, 2))

    dim = mat.shape[0]
    n = dim // 2
    elements_per_quad = n * n

    for i in range(n):
        for j in range(n):
            ret[0, 0] += mat[i, j]
    ret[0, 0] /= elements_per_quad

    for i in range(n, dim):
        for j in range(n):
            ret[1, 0] += mat[i, j]
    ret[1, 0] /= elements_per_quad

    for i in range(n, dim):
        for j in range(n, dim):
            ret[1, 1] += mat[i, j]
    ret[1, 1] /= elements_per_quad

    for i in range(n):
        for j in range(n, dim):
            ret[0, 1] += mat[i, j]
    ret[0, 1] /= elements_per_quad

    return ret


def quadrants_pro(matrice):
    m = matrice.shape[0]
    ret = np.zeros((2, 2))
    n = m // 2
    qarea = n * n

    ret[0, 0] = np.sum(matrice[:n, :n]) / qarea
    ret[0, 1] = np.sum(matrice[:n, n:]) / qarea
    ret[1, 0] = np.sum(matrice[n:, :n]) / qarea
    ret[1, 1] = np.sum(matrice[n:, n:]) / qarea

    return ret
m1 = np.array([[3.0, 5.0], [4.0, 9.0]])
r1 = np.array(
    [
        [3.0, 5.0],
        [4.0, 9.0],
    ]
)
assert np.allclose(quadrants(m1), r1)

m2 = np.array(
    [
        [1.0, 2.0, 5.0, 7.0],
        [4.0, 1.0, 8.0, 0.0],
        [2.0, 0.0, 5.0, 1.0],
        [0.0, 2.0, 1.0, 1.0],
    ]
)
r2 = np.array([[2.0, 5.0], [1.0, 2.0]])
assert np.allclose(quadrants(m2), r2)

Exercise - downup#

✪✪✪ Write a function which given the dimensions of n rows and m columns, RETURN a NEW n x m numpy matrix with sequences which go down and up in alternating rows as in the examples.

  • if m is odd, raises ValueError

Hide code cell source
def downup(n, m):
    if m % 2 == 1:
        raise ValueError("m must be even, found %s" % m)
    mat = np.zeros((n, m))
    for i in range(0, n, 2):
        for j in range(m // 2):
            mat[i, j + m // 2] = m // 2 - j - 1
    for i in range(1, n, 2):
        for j in range(m // 2):
            mat[i, j] = j
    return mat

def downup_pro(n, m):
    if m % 2 == 1:
        raise ValueError("m must be even, found %s" % m)

    ret = np.zeros((n, m))

    left = np.tile(np.arange(0.0, m / 2, 1.0), (n // 2, 1))
    right = np.tile(np.arange(m / 2 - 1, -0.5, -1.0), (n // 2, 1))
    ret[1::2, : m // 2] = left
    ret[0::2, m // 2 :] = right
    return ret
downup(6, 10)
array([[0., 0., 0., 0., 0., 4., 3., 2., 1., 0.],
       [0., 1., 2., 3., 4., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 4., 3., 2., 1., 0.],
       [0., 1., 2., 3., 4., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 4., 3., 2., 1., 0.],
       [0., 1., 2., 3., 4., 0., 0., 0., 0., 0.]])
assert np.allclose(downup(2, 2), np.array([[0.0, 0.0], [0.0, 0.0]]))
assert isinstance(downup(2, 2), np.ndarray)

assert np.allclose(
    downup(2, 6),
    np.array([[0.0, 0.0, 0.0, 2.0, 1.0, 0.0], [0.0, 1.0, 2.0, 0.0, 0.0, 0.0]]),
)

assert np.allclose(
    downup(6, 10),
    np.array(
        [
            [0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 3.0, 2.0, 1.0, 0.0],
            [0.0, 1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0],
            [0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 3.0, 2.0, 1.0, 0.0],
            [0.0, 1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0],
            [0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 3.0, 2.0, 1.0, 0.0],
            [0.0, 1.0, 2.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 0.0],
        ]
    ),
)
try:
    downup(2, 3)
    raise Exception("I should have failed!")
except ValueError:
    pass

Exercise - stairsteps#

✪✪✪ Given a numpy square matrix mat of dimension n, RETURN a NEW numpy array containing the values retrieved from the matrix in the following order:

   1,2,*,*,*
   *,3,4,*,*
   *,*,5,6,*
   *,*,*,7,8
   *,*,*,*,9
  • if the matrix is not square, raises ValueError

  • DO NOT use python lists!

  • HINT: how many elements must the array to return have?

def stairsteps(mat):
    n, m = mat.shape
    if n != m:
        raise ValueError("Required a square matrix, found instead: %s x %s" % (n, m))

    res = np.zeros(n + n - 1)

    for i in range(n):
        res[2 * i] = mat[i, i]

    for i in range(n - 1):
        res[2 * i + 1] = mat[i, i + 1]

    return res

def stairsteps_pro(mat):
    n, m = mat.shape
    if n != m:
        raise ValueError("Richiesta una n x n, trovata invece una %s x %s" % (n, m))
    a = np.diag(mat)
    b = np.diag(mat, 1)
    ret = np.zeros((1, a.shape[0] + b.shape[0]))
    ret[:, ::2] = a
    ret[:, 1::2] = b
    return ret
stairsteps(
    np.array(
        [
            [6, 3, 5, 2, 5],
            [3, 4, 2, 3, 4],
            [6, 5, 4, 5, 1],
            [4, 3, 2, 3, 9],
            [2, 5, 1, 6, 7],
        ]
    )
)
array([6., 3., 4., 2., 4., 5., 3., 9., 7.])
m1 = np.array([[7]])
assert np.allclose(stairsteps(m1), np.array([7]))
assert isinstance(m1, np.ndarray)

m2 = np.array([[6, 8], [9, 3]])
assert np.allclose(stairsteps(m2), np.array([6, 8, 3]))
assert isinstance(m1, np.ndarray)

m3 = np.array(
    [
        [6, 3, 5, 2, 5],
        [3, 4, 2, 3, 4],
        [6, 5, 4, 5, 1],
        [4, 3, 2, 3, 9],
        [2, 5, 1, 6, 7],
    ]
)

assert np.allclose(stairsteps(m3), np.array([6, 3, 4, 2, 4, 5, 3, 9, 7]))

try:
    stairsteps(np.array([[1, 2, 3], [4, 5, 6]]))
    raise Exception("I should have failed!")
except ValueError:
    pass

Exercise - vertstairs#

✪✪✪ Given a numbers of rows n and of columns m, RETURN a NEW n x m numpy matrix having the numbers in even columns progressively increasing from 1 to n, and numbers in odd columns progressively decreasing from n to 1.

Hide code cell source
def vertstairs(n, m):
    ret = np.zeros((n, m))
    for i in range(n):
        for j in range(m):
            if j % 2 == 0:
                ret[i, j] = i + 1
            else:
                ret[i, j] = n - i
    return ret


def vertstairs_pro(n, m):
    ret = np.zeros((n, m))
    ret[:, 0::2] = np.tile(np.transpose([np.arange(1, n + 1, 1)]), (m + 1) // 2)
    ret[:, 1::2] = np.tile(np.transpose([np.arange(n, 0, -1)]), m // 2)
    return ret
assert np.allclose(vertstairs(1, 1), np.array([[1]]))
assert np.allclose(vertstairs(1, 2), np.array([[1, 1]]))
assert np.allclose(vertstairs(2, 1), np.array([[1], [2]]))
assert np.allclose(vertstairs(2, 2), np.array([[1, 2], [2, 1]]))
assert isinstance(vertstairs(2, 2), np.ndarray)
assert np.allclose(
    vertstairs(4, 5),
    np.array([[1, 4, 1, 4, 1], [2, 3, 2, 3, 2], [3, 2, 3, 2, 3], [4, 1, 4, 1, 4]]),
)

Exercise - comprescol#

✪✪✪ Given an \(n\) x \(2m\) matrix mat with an even number of columns, RETURN a NEW \(n\) x \(m\) matrix in which the columns are given by the sum of corresponding column pairs from mat

  • if mat doesn’t have an even number of columns, raise ValueError

Example:

>>> >>> comprescol(m)
np.array([[ 9, 8, 6],
          [12, 1, 7],
          [13,11,10],
          [ 7,10, 4],
          [ 9, 7, 7]])

because

9 = 5 + 4     8 = 2 + 6     6 = 4 + 2
12= 7 + 5     1 = 1 + 0     7 = 6 + 1
. . .

Hide code cell source
def comprescol(mat):
    if mat.shape[1] % 2 != 0:
        raise ValueError(
            "Expected matrix with an even number of columns, got instead %s"
            % mat.shape[1]
        )
    ret = mat[:, ::2].copy()
    ret += mat[:, 1::2]
    return ret
m = np.array(
    [
        [5, 4, 2, 6, 4, 2],
        [7, 5, 1, 0, 6, 1],
        [6, 7, 9, 2, 3, 7],
        [5, 2, 4, 6, 1, 3],
        [7, 2, 3, 4, 2, 5],
    ]
)
comprescol(m)
array([[ 9,  8,  6],
       [12,  1,  7],
       [13, 11, 10],
       [ 7, 10,  4],
       [ 9,  7,  7]])
m1 = [[7, 9]]
res = comprescol(np.array(m1))
assert isinstance(res, np.ndarray)
assert np.allclose(res, np.array([[16]]))

m2 = np.array([[5, 8], [7, 2]])
assert np.allclose(comprescol(m2), np.array([[13], [9]]))
assert np.allclose(
    m2, np.array([[5, 8], [7, 2]])
)  # check doesn't MODIFY original matrix

m3 = np.array(
    [
        [5, 4, 2, 6, 4, 2],
        [7, 5, 1, 0, 6, 1],
        [6, 7, 9, 2, 3, 7],
        [5, 2, 4, 6, 1, 3],
        [7, 2, 3, 4, 2, 5],
    ]
)

assert np.allclose(
    comprescol(m3),
    np.array([[9, 8, 6], [12, 1, 7], [13, 11, 10], [7, 10, 4], [9, 7, 7]]),
)

try:
    comprescol(np.array([[7, 1, 6], [5, 2, 4]]))
    raise Exception("I should have failed!")
except ValueError:
    pass

Exercise - revtriang#

✪✪✪ Givena square numpy matrix, RETURN a NEW numpy matrix having the same dimensions as the original one, and the numbers in the lower triangular part (excluding the diagonal) in reverse.

  • if the matrix is not square, raise ValueError

Hide code cell source
def revtriang(mat):
    n, m = mat.shape
    if n != m:
        raise ValueError("Expected square matrix, got instead n=%s, m=%s" % (n, m))

    ret = mat.copy()

    for i in range(1, n):
        ret[i, :i] = np.flip(mat[i, :i])
    return ret
m = np.array(
    [
        [5, 4, 2, 6, 4],
        [3, 5, 1, 0, 6],
        [6, 4, 9, 2, 3],
        [5, 2, 8, 6, 1],
        [7, 9, 3, 2, 2],
    ]
)
revtriang(m)
array([[5, 4, 2, 6, 4],
       [3, 5, 1, 0, 6],
       [4, 6, 9, 2, 3],
       [8, 2, 5, 6, 1],
       [2, 3, 9, 7, 2]])
m1 = np.array([[8]])
assert np.allclose(revtriang(m1), np.array([[8]]))


m3 = np.array([[1, 5], [9, 6]])
assert np.allclose(revtriang(m3), np.array([[1, 5], [9, 6]]))

m4 = np.array([[1, 5, 8], [9, 6, 2], [3, 2, 5]])
assert np.allclose(revtriang(m4), np.array([[1, 5, 8], [9, 6, 2], [2, 3, 5]]))
assert np.allclose(
    m4, np.array([[1, 5, 8], [9, 6, 2], [3, 2, 5]])
)  # shouldn't change the original

m5 = np.array(
    [
        [5, 4, 2, 6, 4],
        [3, 5, 1, 0, 6],
        [6, 4, 9, 2, 3],
        [5, 2, 8, 6, 1],
        [7, 9, 3, 2, 2],
    ]
)
assert np.allclose(
    revtriang(m5),
    np.array(
        [
            [5, 4, 2, 6, 4],
            [3, 5, 1, 0, 6],
            [4, 6, 9, 2, 3],
            [8, 2, 5, 6, 1],
            [2, 3, 9, 7, 2],
        ]
    ),
)
try:
    revtriang(np.array([[7, 1, 6], [5, 2, 4]]))
    raise Exception("I should have failed!")
except ValueError:
    pass

Exercise - walkas#

✪✪✪ Given a numpy matrix \(n\) x \(m\) with odd \(m\), RETURN a numpy array containing all the numbers found along the path of an S, from bottom to top.

HINT: can you determine the array dimension right away?

Hide code cell source
def walkas(mat):
    n, m = mat.shape
    ret = np.zeros(n + m - 1)
    ret[: m // 2] = mat[-1, : m // 2]
    ret[m // 2 : m // 2 + n] = mat[::-1, m // 2]
    ret[-m // 2 :] = mat[0, m // 2 :]
    return ret
m = np.array(
    [
        [5, 8, 2, 4, 6, 5, 7],
        [7, 9, 5, 8, 3, 2, 2],
        [6, 1, 8, 3, 6, 6, 1],
        [1, 5, 3, 7, 9, 4, 7],
        [1, 5, 3, 2, 9, 5, 4],
        [4, 3, 8, 5, 6, 1, 5],
    ]
)
walkas(m)
array([4., 3., 8., 5., 2., 7., 3., 8., 4., 6., 5., 7.])
m1 = np.array([[7]])
assert np.allclose(walkas(m1), np.array([7]))

m2 = np.array([[7, 5, 2]])
assert np.allclose(walkas(m2), np.array([7, 5, 2]))

m3 = np.array([[9, 3, 5, 6, 0]])
assert np.allclose(walkas(m3), np.array([9, 3, 5, 6, 0]))

m4 = np.array([[7, 5, 2], [9, 3, 4]])
assert np.allclose(walkas(m4), np.array([9, 3, 5, 2]))

m5 = np.array([[7, 4, 6], [8, 2, 1], [0, 5, 3]])
assert np.allclose(walkas(m5), np.array([0, 5, 2, 4, 6]))

m6 = np.array(
    [
        [5, 8, 2, 4, 6, 5, 7],
        [7, 9, 5, 8, 3, 2, 2],
        [6, 1, 8, 3, 6, 6, 1],
        [1, 5, 3, 7, 9, 4, 7],
        [1, 5, 3, 2, 9, 5, 4],
        [4, 3, 8, 5, 6, 1, 5],
    ]
)
assert np.allclose(walkas(m6), np.array([4, 3, 8, 5, 2, 7, 3, 8, 4, 6, 5, 7]))

Exercise - walkaz#

✪✪✪ Given a numpy matrix \(n\) x \(m\) with odd \(m\), RETURN a numpy array containing all the numbers found along the path of an Z, from bottom to top.

HINT: can you determine the array dimension right away?

Hide code cell source
def walkaz(mat):
    n, m = mat.shape
    ret = np.zeros(n + m - 1)
    ret[: m // 2] = mat[-1, -1 : m // 2 : -1]
    ret[m // 2 : m // 2 + n] = mat[::-1, m // 2]
    ret[-m // 2 :] = mat[0, m // 2 :: -1]
    return ret
m = np.array(
    [
        [5, 8, 2, 4, 6, 5, 7],
        [7, 9, 5, 8, 3, 2, 2],
        [6, 1, 8, 3, 6, 6, 1],
        [1, 5, 3, 7, 9, 4, 7],
        [1, 5, 3, 2, 9, 5, 4],
        [4, 3, 8, 5, 6, 1, 5],
    ]
)
walkaz(m)
array([5., 1., 6., 5., 2., 7., 3., 8., 4., 2., 8., 5.])
m1 = np.array([[7]])
assert np.allclose(walkaz(m1), np.array([7]))

m2 = np.array([[7, 5, 2]])
assert np.allclose(walkaz(m2), np.array([2, 5, 7]))

m3 = np.array([[9, 3, 5, 6, 0]])
assert np.allclose(walkaz(m3), np.array([0, 6, 5, 3, 9]))

m4 = np.array([[7, 5, 2], [9, 3, 4]])
assert np.allclose(walkaz(m4), np.array([4, 3, 5, 7]))

m5 = np.array([[7, 4, 6], [8, 2, 1], [0, 5, 3]])
assert np.allclose(walkaz(m5), np.array([3, 5, 2, 4, 7]))

m6 = np.array(
    [
        [5, 8, 2, 4, 6, 5, 7],
        [7, 9, 5, 8, 3, 2, 2],
        [6, 1, 8, 3, 6, 6, 1],
        [1, 5, 3, 7, 9, 4, 7],
        [1, 5, 3, 2, 9, 5, 4],
        [4, 3, 8, 5, 6, 1, 5],
    ]
)
assert np.allclose(walkaz(m6), np.array([5, 1, 6, 5, 2, 7, 3, 8, 4, 2, 8, 5]))

Continue#

Try doing exercises from Nested structures using Numpy instead - try making the exercises performant by using Numpy features and functions (i.e. 2*arr multiplies all numbers in arr without the need of a slow Python for)

References#