my_list = [1, 2, 3, [1, 2, 3, [1, 2, 3]]]McKinney Chapter 3 - Practice for Section 03
FINA 6333 for Spring 2024
1 Announcements
- Due Friday, 1/19, at 11:59 PM:
- Complete Introduction to Python course on DataCamp (and upload certificate to Canvas)
- Acknowledge academic integrity statement on Canvas
- I will record and post the next lecture video on Thursday, 1/18, and the associate pre-class quiz is due before class on Tuesday, 1/23
- Start joining groups on Canvas (Canvas > People > Team Projects); I removed joining groups as a scored assignment, but please prioritize joining groups
2 10-minute Recap
2.1 List
A list is a collection of items that are ordered and changeable. You can add, remove, or modify elements in a list. In Python, you can create an empty list using either [] or list().
Python is zero-indexed!
my_list[1]2
my_list[:1][1]
my_list[-1][-1][-1]3
my_list[-1][-1][-1] = 'McKinney'
my_list[1, 2, 3, [1, 2, 3, [1, 2, 'McKinney']]]
2.2 Tuple
A tuple is similar to a list, but it is immutable, meaning that you cannot change its contents once it has been created. You can create a tuple using parentheses () or the tuple() function.
my_tuple = (1, 2, 3, (1, 2, 3, (1, 2, 3)))
my_tuple(1, 2, 3, (1, 2, 3, (1, 2, 3)))
Unlike lists, tuples are immutable and cannot be changed!
# my_tuple[-1][-1][-1] = 'McKinney'
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# Cell In[15], line 1
# ----> 1 my_tuple[-1][-1][-1] = 'McKinney'
# TypeError: 'tuple' object does not support item assignmentThe following result is surprising! Tuples are immutable, but a list nested inside a tuple is mutable!
my_tuple_2 = (1, 2, 3, (1, 2, 3, [1, 2, 3]))my_tuple_2[-1][-1][-1] = 'McKinney'
my_tuple_2(1, 2, 3, (1, 2, 3, [1, 2, 'McKinney']))
2.3 Dictionary
A dictionary is a collection of key-value pairs that are unordered and changeable. You can add, remove, or modify elements in a dictionary. In Python, you can create an empty dictionary using either {} or the dict() function.
stock_prices = [100, 200, 300] # AAPL, MSFT, TSLADictionaries let us index a value by its key!
stock_prices = {'AAPL': 100, 'MSFT': 200, 'TSLA': 300}
stock_prices['AAPL']100
2.4 List Comprehension
A list comprehension is a concise way of creating a new list by iterating over an existing list or other iterable object. It is more time and space-efficient than traditional for loops and offers a cleaner syntax. The basic syntax of a list comprehension is new_list = [expression for item in iterable if condition] where:
expressionis the operation to be performed on each element of the iterableitemis the current element being processediterableis the list or other iterable object being iterated overconditionis an optional filter that only accepts items that evaluate to True.
For example, we can use the following list comprehension to create a new list of even numbers from 0 to 8: even_numbers = [x for x in range(9) if x % 2 == 0]
List comprehensions are a powerful tool in Python that can help you write more efficient and readable code (i.e., more Pythonic code).
even_numbers = []
for i in range(9):
if i%2 == 0:
even_numbers.append(i)
even_numbers[0, 2, 4, 6, 8]
The list comprehension version of the code above is:
[i for i in range(9) if i%2 == 0][0, 2, 4, 6, 8]
List comprehensions are “very Pythonic” (the code style that other Python programmers expect).
3 Practice
3.1 Swap the values assigned to a and b using a third variable c.
a = 1b = 2c = aa = bb = cdel cprint(f'a is {a}, b is {b}')a is 2, b is 1
3.2 Swap the values assigned to a and b without using a third variable c.
a = 1b = 2b, a = a, bprint(f'a is {a}, b is {b}')a is 2, b is 1
The code above uses “f strings”! Here is the non-f-string equivalent:
print('a is ' + str(a) + ', b is ' + str(b))a is 2, b is 1
3.3 What is the output of the following code and why?
The parentheses () around tuples are optional to create tuples, but avoid ambiguity!
_ = 1, 1, 1
type(_)tuple
1, 1, 1 == (1, 1, 1)(1, 1, False)
(1, 1, 1) == 1, 1, 1(False, 1, 1)
If we want to remove ambiguity, we should add parentheses () to both sides.
(1, 1, 1) == (1, 1, 1)True
3.4 Create a list l1 of integers from 1 to 100.
Jupyter prints the last line of code in a code cell automatically. In the next cell, I wrap l1 with print() so the list contents wrap around.
l1 = list(range(1, 101))
print(l1)[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
3.5 Slice l1 to create a list of integers from 60 to 50 (inclusive).
Name this list l2.
l1.index(50)49
With zero-indexing, left edges are included and right edges are excluded. So, right - left is the number of elements in a sequence.
In the code below, the first [] slices 50 to 60, and the second [::-1] reverses.
l2 = l1[49:60][::-1]
l2[60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50]
l1[59:48:-1][60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50]
3.6 Create a list l3 of odd integers from 1 to 21.
l3 = l1[0:21][::2]
l3[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
l1[0:21:2][1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
list(range(1, 22, 2))[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
[i for i in range(22) if i%2 != 0][1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
What is the advantage of the list comprehension? There is no advantage in this example, but the list comprehension lets us specify several conditions and variable step sizes.
3.7 Create a list l4 of the squares of integers from 1 to 100.
l4 = [i**2 for i in range(1, 101)]
print(l4)[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801, 10000]
3.8 Create a list l5 that contains the squares of odd integers from 1 to 100.
l5 = [i**2 for i in range(1, 101) if i%2 != 0]
print(l5)[1, 9, 25, 49, 81, 121, 169, 225, 289, 361, 441, 529, 625, 729, 841, 961, 1089, 1225, 1369, 1521, 1681, 1849, 2025, 2209, 2401, 2601, 2809, 3025, 3249, 3481, 3721, 3969, 4225, 4489, 4761, 5041, 5329, 5625, 5929, 6241, 6561, 6889, 7225, 7569, 7921, 8281, 8649, 9025, 9409, 9801]
3.9 Use a lambda function to sort strings by the last letter.
strings = ['card', 'aaaa', 'foo', 'bar', 'abab']Almost all core Python methods modify the object in place. Here we use the sorted() function so we can see the output/results more easily.
sorted(strings, key=len)['foo', 'bar', 'card', 'aaaa', 'abab']
How do we get the last letter in a string?
'Friday'[-1]'y'
def get_last_letter(x):
return x[-1]get_last_letter('Friday')'y'
sorted(strings, key=get_last_letter)['aaaa', 'abab', 'card', 'foo', 'bar']
We can get the same result without the hassle of writing the get_last_letter() function! Enter, anonymous (or lambda) functions!
sorted(strings, key=lambda x: x[-1])['aaaa', 'abab', 'card', 'foo', 'bar']
We can translate lambda x: x[-1] into the following:
def function_that_I_do_not_want_to_use_again(x):
return x[-1]
3.10 Given an integer array nums and an integer k, return the \(k^{th}\) largest element in the array.
Note that it is the \(k^{th}\) largest element in the sorted order, not the \(k^{th}\) distinct element.
Example 1:
Input: nums = [3,2,1,5,6,4], k = 2
Output: 5
Example 2:
Input: nums = [3,2,3,1,2,4,5,5,6], k = 4
Output: 4
I saw this question on LeetCode.
def get_largest(x, k):
return sorted(x)[-k]get_largest(x=[3,2,1,5,6,4], k=2)5
get_largest(x=[3,2,3,1,2,4,5,5,6], k=4)4
3.11 Given an integer array nums and an integer k, return the k most frequent elements.
You may return the answer in any order.
Example 1:
Input: nums = [1,1,1,2,2,3], k = 2
Output: [1,2]
Example 2:
Input: nums = [1], k = 1
Output: [1]
I saw this question on LeetCode.
def get_frequent(nums, k):
counts = {}
for n in nums:
if n in counts:
counts[n] += 1
else:
counts[n] = 1
return [x[0] for x in sorted(counts.items(), key=lambda x: x[1], reverse=True)[:k]]get_frequent(nums=[1,1,1,2,2,3], k=2)[1, 2]
3.12 Test whether the given strings are palindromes.
Input: ["aba", "no"]
Output: [True, False]
def is_palindrome(xs):
return [x == x[::-1] for x in xs]is_palindrome(["aba", "no"])[True, False]
3.13 Write a function returns() that accepts lists of prices and dividends and returns a list of returns.
prices = [100, 150, 100, 50, 100, 150, 100, 150]
dividends = [1, 1, 1, 1, 2, 2, 2, 2]This example is one the rare where I use a loop counter.
def returns(p, d):
rs = []
for i in range(1, len(p)):
r = (p[i] + d[i] - p[i-1]) / p[i-1]
rs.append(r)
return rsWe can use a magic called %precision to print fewer decimal places!
%precision 4'%.4f'
returns(p=prices, d=dividends)[0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200]
Here is a solution that avoids a loop counter and combines a list comprehension zip() to loop over prices, dividends, and lagged prices. This solution may be a little more Pythonic, but I find it harder to follow that our loop-counter solution above.
[(p + d - p_lag) / p_lag for p, d, p_lag in zip(prices[1:], dividends[1:], prices[:-1])][0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200]
3.14 Rewrite the function returns() so it returns lists of returns, capital gains yields, and dividend yields.
def returns_2(p, d):
rs, dps, cgs = [], [], []
for i in range(1, len(p)):
r = (p[i] + d[i] - p[i-1]) / p[i-1]
dp = d[i] / p[i-1]
cg = r - dp
rs.append(r)
dps.append(dp)
cgs.append(cg)
return {'r':rs, 'dp':dps, 'cg':cgs}returns_2(p=prices, d=dividends){'r': [0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200],
'dp': [0.0100, 0.0067, 0.0100, 0.0400, 0.0200, 0.0133, 0.0200],
'cg': [0.5000, -0.3333, -0.5000, 1.0000, 0.5000, -0.3333, 0.5000]}
returns_2(p=prices, d=dividends)['r'][0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200]
3.15 Rescale and shift numbers so that they cover the range [0, 1].
Input: [18.5, 17.0, 18.0, 19.0, 18.0]
Output: [0.75, 0.0, 0.5, 1.0, 0.5]
x = [18.5, 17.0, 18.0, 19.0, 18.0]def rescale(x):
x_min = min(x)
x_max = max(x)
return [(i - x_min) / (x_max - x_min) for i in x]rescale(x)[0.7500, 0.0000, 0.5000, 1.0000, 0.5000]
Now we can rescale any list!