Bonus

These questions are all-or-nothing.

Part A

Write a function, enhanced_average, which computes the average of a list of numbers, with some tweaks.

  • Takes 2 arguments. 1 is required: the list of numbers. The second is optional: it's a list of weights (named weights) for the required list of numbers. If provided, it should be the same length as the required list. In that case, it should be used to compute a weighted average. If it's not provided, the average should be computed normally.
  • returns 1 number: the average (or weighted average, if the optional argument weights is provided).

When you're computing a weighted average, you need to multiply each number in the required list by its corresponding weight in the weights list. You can think of the "normal" way of computing the mean (without weights) as being a weighted version, just where all the weights are 1.

For example, if you have a list of numbers [1, 2, 3], the unweighted mean is 2. However, with the weights [1, 1, 10], the weighted average is 2.75. Since the corresponding weight for 3 is 10, that means 3 counts as essentially 10 3s, whereas 1 and 2 are still weighted normally. Except, however, now you're dividing by the sum of the weights (12), except simply by how many numbers you have (3).

If weights is not the same length as the required list your function should throw a ValueError.


In [ ]:


In [ ]:
import numpy as np
np.random.seed(235764)

i1 = np.random.randint(0, 100, 10).tolist()
a1 = np.mean(i1)
np.testing.assert_allclose(a1, enhanced_average(i1))

In [ ]:
i2 = np.random.randint(0, 100, 10).tolist()
w2 = [0.0, 1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1]
a2 = 60.5
np.testing.assert_allclose(a2, enhanced_average(i2, weights = w2))

In [ ]:
i3 = np.random.randint(0, 100, 10).tolist()
w3 = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.2]
try:
    a = enhanced_average(i3, weights = w3)
except ValueError:
    assert True
else:
    assert False

Part B

If you've ever taken a computer science course, you know that sorting is an integral part of the field. If lists are ordered, there are lots of operations that become significantly easier--for instance, searching for a specific item. There are many different ways of sorting, each with their advantages and drawbacks (though some are objectively better than others).

Write your own sorting function, my_sort, which takes a list of numbers and sorts them in ascending order (least to greatest). You cannot use any built-in functions (no list.reverse() calls), or indexing tricks (no [::-1] usage), nor any sorting calls to the NumPy library (no calls to np.sort()). You have to do this on your own!

Your function should also have an optional argument, desc, which if set to True, instead returns the sorted list in descending order (greatest to least).


In [ ]:


In [ ]:
import numpy as np

np.random.seed(85973)
i1 = np.random.random(10).tolist()
np.testing.assert_allclose(np.sort(i1), my_sort(i1))

In [ ]:
i2 = np.random.random(100).tolist()
a2 = np.sort(i2).tolist()
a2.reverse()
np.testing.assert_allclose(a2, my_sort(i2, desc = True))