%precision 4'%.4f'
FINA 6333 for Spring 2025
The %precision magic makes it easy to round all float on print to 4 decimal places.
%precision 4'%.4f'
A list is an ordered collection of objects that is changeable (mutable). You can create an empty list using either [] or list().
my_list = [1, 2, 3, [1, 2, 3, [1, 2, 3]]]
my_list[1, 2, 3, [1, 2, 3, [1, 2, 3]]]
Python is zero-indexed!
my_list[0]1
my_list[:3] # to get first 3 objects, :3[1, 2, 3]
my_list[1:4] # to get next 3 objects from 1, go from 1 to 1+3 or 1:3[2, 3, [1, 2, 3, [1, 2, 3]]]
A tuple is similar to a list but un-changeable (immutable). 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)))
Python is zero-indexed!
my_tuple[0]1
Tuples are immutable, so they cannot be changed!
# my_tuple[0] = 2_001
# ---------------------------------------------------------------------------
# TypeError Traceback (most recent call last)
# Cell In[10], line 1
# ----> 1 my_tuple[0] = 2_001
# TypeError: 'tuple' object does not support item assignmentA dictionary is an ordered collection of key-value pairs that are changeable (mutable). You can create an empty dictionary using either {} or the dict() function.
my_dict = {'wb': 'Warren Buffett', 'sk': 'Seth Klarman'}
my_dict{'wb': 'Warren Buffett', 'sk': 'Seth Klarman'}
my_dict['wb']'Warren Buffett'
my_dict['pl'] = 'Peter Lynch'
my_dict{'wb': 'Warren Buffett', 'sk': 'Seth Klarman', 'pl': 'Peter Lynch'}
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:
expression is the operation to be performed on each element of the iterableitem is the current element being processediterable is the list or other iterable object being iterated overcondition is 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).
What if we wanted multiples of 3 or 5 from 1 to 25?
threes_fives = [i for i in range(1, 26) if (i%3==0) | (i%5==0)]threes_fives[3, 5, 6, 9, 10, 12, 15, 18, 20, 21, 24, 25]
threes_fives_2 = [print(i) for i in range(1, 26) if (i%3==0) | (i%5==0)]3
5
6
9
10
12
15
18
20
21
24
25
threes_fives_2[None, None, None, None, None, None, None, None, None, None, None, None]
a and b using a third variable c.a = 1b = 2c = aa = bb = cprint(f'a is {a} and b is {b}')a is 2 and b is 1
More on f-strings!
F-strings offer a concise way to embed expressions inside string literals, using curly braces {}. Prefixed with f or F, these strings allow for easy formatting of variables, numbers, and expressions. For example:
name = "Alice"
print(f"Hello, {name}!")
This outputs “Hello, Alice!”. F-strings simplify complex formatting, making code more readable. For a deeper understanding and more examples: https://realpython.com/python-f-strings/
a and b without using a third variable c.a = 1
b = 2
b, a = a, b
print(f'a is {a} and b is {b}')a is 2 and b is 1
a = 1
b = 2
a, b = b, a
print(f'a is {a} and b is {b}')a is 2 and b is 1
1, 1, 1 == (1, 1, 1)(1, 1, False)
Without parentheses (), Python reads the final element in the tuple as 1 == (1, 1, 1), which is False. We can use parentheses () to force Python to do what we want!
(1, 1, 1) == (1, 1, 1)True
For this example, we must use parentheses () to be unambiguous!
l1 of integers from 1 to 100.l1 = list(range(1, 101))l1[:5][1, 2, 3, 4, 5]
l1[-5:][96, 97, 98, 99, 100]
l1 to create a list l2 of integers from 60 to 50 (inclusive).l2 = l1[59:48:-1]
l2[60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50]
l1[48:59][49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
l2_alt_1 = l1[49:60][::-1]
l2_alt_1[60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50]
l2_alt_2 = list(reversed(l1[49:60]))
l2_alt_2[60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50]
l3 of odd integers from 1 to 21.l3 = list(range(1, 22, 2))
l3[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
Python != is the same as Excel’s <>.
l3_alt = [i for i in range(22) if i%2 != 0]
l3_alt[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
l3_alt_do_not_do_this = []
for i in range(22):
if i%2 != 0:
l3_alt_do_not_do_this.append(i)
l3_alt_do_not_do_this[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
l3_alt_do_not_do_this[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
l4 of the squares of integers from 1 to 100.l4 = [i**2 for i in range(1, 101)]
l4[:5][1, 4, 9, 16, 25]
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]
l5[:5][1, 9, 25, 49, 81]
l5_alt = [i**2 for i in range(1, 101, 2)]
l5_alt[:5][1, 9, 25, 49, 81]
l5 == l5_altTrue
Which one is faster?!
%timeit [i**2 for i in range(1, 101) if i%2!=0]12.3 μs ± 2.96 μs per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
%timeit [i**2 for i in range(1, 101, 2)]4.74 μs ± 956 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
Premature optimization is the root of all evil
- Donald Knuth
strings by the last letter in each string.strings = ['Pillsbury', 'Shubrick', 'Clemson', 'Hinson']strings.sort()
strings['Clemson', 'Hinson', 'Pillsbury', 'Shubrick']
'Clemson'[-1]'n'
strings.sort(key=lambda x: x[-1])
strings['Shubrick', 'Clemson', 'Hinson', 'Pillsbury']
strings.sort(key=len)
strings['Hinson', 'Clemson', 'Shubrick', 'Pillsbury']
nums and an integer k, write a function to 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_klarge(nums=[3,2,3,1,2,4,5,5,6], k=4):
return sorted(nums)[-k]get_klarge(nums=[3,2,1,5,6,4], k=2)5
The following code follows a bad practice and uses variables in the global scope instead of passing them as arguments or parameters of the functions.
nums = [3,2,3,1,2,4,5,5,6]
k = 4def get_klarge_donotdo():
return sorted(nums)[-k]get_klarge_donotdo()4
Here is an extreme example how this practice can lead to non-deterministic and confusing results that depend on how many times a function has been run.
x = [1, 2, 3, 4]def confusing():
x.append(1)confusing()
confusing()
confusing()
confusing()
confusing()x[1, 2, 3, 4, 1, 1, 1, 1, 1]
nums and an integer k, write a function to 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_kfreq(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_kfreq(nums=[1,1,1,2,2,3], k=2)[1, 2]
Input: ["aba", "no"]
Output: [True, False]
We can reverse a string with a [::-1] slice just like we reverse a string!
def is_palindrome(x):
return [_x == _x[::-1] for _x in x]is_palindrome(["aba", "no"])[True, False]
tickers = ["AAPL", "GOOG", "XOX", "XOM"]is_palindrome(tickers)[False, True, True, False]
calc_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]Although loop counters are un-Pythonic, this calculation is the rare case where I found loop counters more clear.
def calc_returns(p, d):
r = []
for i in range(1, len(p)):
# uncomment this line to watch loop iterations
# print(f'r is {r}, p[i] is {p[i]}, p[i-1] is {p[i-1]}, d[i] is {d[i]}')
r.append((p[i] - p[i-1] + d[i]) / p[i-1])
return rcalc_returns(p=prices, d=dividends)[0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200]
We do not have to specify the argument names p= and d=, but they help me avoid errors.
calc_returns(prices, dividends)[0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200]
We can do the same calculation without indexing! Instead, we can use zip() to simultaneously loop over prices, lagged prices, and dividends.
def calc_returns_zip(p, d):
r = []
for _p, _plag, _d in zip(p[1:], p[:-1], d[1:]):
r.append((_p - _plag + _d) / _plag)
return rcalc_returns_zip(p=prices, d=dividends)[0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200]
calc_returns_zip(p=prices, d=dividends) == calc_returns_zip(p=prices, d=dividends)True
calc_returns() as calc_returns_2() so it returns lists of returns, capital gains yields, and dividend yields.def calc_returns_2(p, d):
r, cg, dp = [], [], []
for i in range(1, len(p)):
r.append((p[i] + d[i] - p[i-1]) / p[i-1])
cg.append((p[i] - p[i-1]) / p[i-1])
dp.append(d[i] / p[i-1])
return {'r':r, 'cg':cg, 'dp': dp}calc_returns_2(p=prices, d=dividends){'r': [0.5100, -0.3267, -0.4900, 1.0400, 0.5200, -0.3200, 0.5200],
'cg': [0.5000, -0.3333, -0.5000, 1.0000, 0.5000, -0.3333, 0.5000],
'dp': [0.0100, 0.0067, 0.0100, 0.0400, 0.0200, 0.0133, 0.0200]}
calc_returns(p=prices, d=dividends) == calc_returns_2(p=prices, d=dividends)['r']True
rescale() to 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]
nums = [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(nums)[0.7500, 0.0000, 0.5000, 1.0000, 0.5000]