Skip to content

Instantly share code, notes, and snippets.

@ProbonoBonobo
Last active October 3, 2021 00:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ProbonoBonobo/633eb8c3e44e0b0a84fa2c2c5750c511 to your computer and use it in GitHub Desktop.
Save ProbonoBonobo/633eb8c3e44e0b0a84fa2c2c5750c511 to your computer and use it in GitHub Desktop.
# transcript of live repl session for week 3's lecture
>>> l = [0,1,2,3,4,5]
>>> l[0]
0
>>> l[-1] # negative indices are allowed, and assumed to be relative to the end of the sequence. l[-1] == l[len(l)] == l[5]
5
>>> l[-2] # the second-to-last element...
4
>>> l[-3] # and so on.
3
>>> l = [1,2,3]
>>> l
[1, 2, 3]
>>> first, second, third = l # example of 'unpacking' an iterable value into named variables
>>> first
1
>>> second
2
>>> third
3
>>> first, second = l # but be careful. If the number of elements isn't exactly equal to the number of 'unpacked' variables...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
too many values to unpack (expected 2)
>>> # maybe you only want the first item?
>>> first, *rest = l # fun trick: use the star expression to collect the remaining elements into a single list
>>> first
1
>>> rest # == l[1:]
[2, 3]
>>> # a brief side note
>>> car, *cdr = rest # if you ever go on to learn lisp/scheme/clojure, this might just blow your mind
>>> car
2
>>> cdr
[3]
>>> car, *cdr = cdr
>>> car
3
>>> cdr # cdr shrank again and is now empty. (in recursive situations, frequently a base case that tells us to stop recursing)
[]
>>> # anyway, back to useful Python stuff
>>> l.insert(0, 0) # other useful methods list methods in addition to append/pop. for example, `l.insert(index, item)` inserts `item` at position `l[index]`
>>> l
[0, 1, 2, 3] # elements to the right will be moved in order to accommodate the inserted value at the specified index
>>> l.insert(0, 0)
>>> l
[0, 0, 1, 2, 3]
>>> l.remove(3) # remove can be used to delete an item from a list.
>>> l
[0, 0, 1, 2]
>>> l.remove(0) # if a list contains more than one such element, only the *leftmost* item is removed.
>>> l
[0, 1, 2]
>>> l = ['a', 'b', 'a', 'b', 'c'] # a more illustrative example
>>> l.remove('b') # note which 'b' disappears
>>> l
['a', 'a', 'b', 'c']
>>> my_list = ["Charlie", "Alpha", "Delta", "Bravo"]
>>> my_list[:3] # slicing operator s[i:j] extracts a subsequence from s consisting of the elements with index k, where i<= k < j
['Charlie', 'Alpha', 'Delta']
>>> range(0,10) # similar to the range function, with `start` and `stop` parameters...
range(0, 10)
>>> range(0,10,2) # and an optional `step` parameter...
range(0, 10, 2)
>>> list(range(0,10,2))
[0, 2, 4, 6, 8]
>>> my_list[:3:2] # slicing operator accepts an optional 3rd argument, referred to as the 'stride'.
['Charlie', 'Delta'] # e.g., every 2nd element of the subsequence my_list[0:3]
>>>
>>> my_list[2:] # if the starting or ending index is omitted, the beginning or end of the sequence is assumed, respectively
['Delta', 'Bravo']
>>> my_list[1:3]
['Alpha', 'Delta']
>>> my_list[1:-1]
['Alpha', 'Delta']
>>> 'Romeo' in my_list # Unlike sets, no method to check whether a list contains a particular value. We just use `in`:
False
>>> 'Alpha' in my_list
True
>>> for elem in my_list: # note that when iterating over `my_list`, our iteration variable isn't a bool value like above.
... print(elem.upper()) # It's whatever type each individual element contained in `my_list` happens to be
CHARLIE
ALPHA
DELTA
BRAVO
>>> result = [] # a common situation: doing stuff with the elements of a list, collecting the intermediate values in an empty list
>>> for elem in my_list:
... if elem.endswith('a'):
... changed_elem = elem.upper()
... result.append(changed_elem)
>>> result
['ALPHA', 'DELTA']
>>> # in fact, it's so common that Python actually has special syntax for rewriting lines 67-71 as a single expression, called a 'list comprehension'
>>> result = [elem.upper() for elem in my_list if elem.endswith("a") ] # exactly equivalent
>>> result
['ALPHA', 'DELTA']
>>> for i in range(len(my_list)): # you can do this, but there's a more concise way...
... print(my_list[i])
Charlie
Alpha
Delta
Bravo
>>> for i in my_list: # exactly equivalent
... print(i)
Charlie
Alpha
Delta
Bravo
>>> # strings. immutable (can't append or modify specific indices)...
>>> my_str = "dog" #but two strings can be combined (conatenated) using the `+` operator
>>> my_str + "s"
'dogs'
>>> database = {"bob": "admin123", "karen": "admin123"} # dictionary objects. Keys are unique, but values don't have to be
>>> database
{'bob': 'admin123', 'karen': 'admin123'}
>>> database['bob'] = "weuwirugwoWEGEN3823^#$&#" # when assigning a value to an existing key, any previous mapping of that key will be overwritten with the new value
>>> database
{'bob': 'weuwirugwoWEGEN3823^#$&#', 'karen': 'admin123'}
>>> list(database.items()) # note that database.items() returns an iterable collection of pairs.
[('bob', 'weuwirugwoWEGEN3823^#$&#'), ('karen', 'admin123')]
>>> type(list(database.items())[0]).__name__ # those pairs look familiar... what type are they?
'tuple'
>>> k, v = (1,2) # and recall from earlier that we can 'unpack' a sequence into named variables...
>>> k
1
>>> v
2
>>> for user, password in database.items(): # thus to iterate over individual key/value pairs in `database` (super duper common thing to do) we can use this syntax
... print(f"User is: {user}\nPassword: {password}") # here we're unpacking keys and values of `database` into named (temporary) variables `user` and `password`
User is: bob
Password: weuwirugwoWEGEN3823^#$&#
User is: karen
Password: admin123
>>> database = {"bob": {"last_name": "smith", "password": "ham"}, "karen": {"last_name": "zeidler", "password": "table"}} # a more complex example
>>> for user, user_data in database.items(): # example: accessing all of the passwords contained in the `dict` object called database
... print(user_data["password"]) # since values are themselves `dict` objects, we can access them like so
ham
table
>>> database['password'] # note that this key does not exist on our database object, only its values
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'password'
'password'
>>> database['bob'] # bob's info
{'last_name': 'smith', 'password': 'ham'}
>>> # tuples are immutable lists, basically.
>>> #Often less a heterogeneous collection of objects, than an ordered collection of properties (e.g., a row in a spreadsheet/database)
>>> user = ('bobsmith100', 'bob', 'smith', 'ham') # often the 'meaning' of each datum is only provided by a database schema or a csv header or something, which is itself a tuple/list
>>> columns = ('username', 'first_name', 'last_name', 'password')
>>> user
('bobsmith100', 'bob', 'smith', 'ham')
>>> username, first_name, last_name, password = user # if we know the schema in advance, you can use the unpacking syntax like so
>>> password
'ham'
>>> first_name
'bob'
>>> for k,v in zip(columns, user): # Sometimes, you don't. In such cases, there's a handy built-in function called `zip` that iterates over the 0th elements of some iterables... then the 1st elements... and so on
... print(k, v)
username bobsmith100
first_name bob
last_name smith
password ham
>>> dict(zip(columns, user)) # you could even create a dictionary using those pairs!
{'username': 'bobsmith100', 'first_name': 'bob', 'last_name': 'smith', 'password': 'ham'}
>>> # NOTE: if you find the previous example confusing, forget I mentioned it.
>>> # more of an advanced trick, albeit an extremely useful one
>>> my_tuple = (1, 2, 3, 4, 5)
>>> my_tuple = 1, 2, 3, 4, 5
>>> print(my_tuple)
(1, 2, 3, 4, 5)
>>> my_tuple = 1, 2, 3, 4, 5 # parens optional!
>>> my_tuple = (1*1) # thanks to pemdas, this cannot define a tuple. it's a mathematical expression that simplifies to `1`.
>>> my_tuple
1
>>> my_tuple = (1) # hence this is not a tuple either...
>>> my_tuple = (1,) # and we need the comma when defining a single element tuple, otherwise Python gets confused and will assume we're doing math stuff
>>> my_tuple
(1,)
>>> tuple4 = () # apparently this is ok too. FYI I did not know this. (Because it's immutable, a tuple of length 0 isn't super useful)
>>> tuple4 # themoreyouknow.gif
()
>>> # sets. a bit of an oddball. great for deduplicating lists, as long as you don't care about order
>>> list([1,2,1,2,1,2,1,2])
[1, 2, 1, 2, 1, 2, 1, 2]
>>> list(set(list([1,2,1,2,1,2,1,2]))) # this could theoretically return, [2, 1] though
[1, 2]
>>> my_set = {1,2,3} # sets can only contain hashable values. for most purposes, this means 'immutable types' like numbers and strings
>>> my_set.add(my_set) # adding a set to a set is verboten. and thank goodness, because doing so breaks the universe (see: https://plato.stanford.edu/entries/russell-paradox/)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
unhashable type: 'set'
>>> l = [1,2,3] # lists aren't hashable either
>>> my_set.add(l)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
unhashable type: 'list'
>>> tup_l = tuple(l) # but tuples are hashable. So this is fine
>>> my_set.add(tup_l)
>>> my_set
{1, 2, 3, (1, 2, 3)}
>>> my_list = [] # an empty list...
>>> my_tuple = () # an empty tuple...
>>> my_dict = {} # an empty set... no, wait, what?
>>> my_dict
{}
>>> my_dict['k'] = 'v' # remember that `{}` defines an empty dict, not an empty set.
>>> my_set = set()
>>> my_set.add(1)
>>> my_set.add(2)
>>> my_set.add(3)
>>> my_set.add(4)
>>> my_set.add(5)
>>> my_set.add(6)
>>> evens = set(2,4,6,8,10) # set constructor expects a sequence, so this doesn't work
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: set expected at most 1 argument, got 5
set expected at most 1 argument, got 5
>>> evens = set([2,4,6,8,10]) # but this is fine
>>> # stupid set tricks
>>> my_set.intersection(evens) # set intersection: elements of `my_set` that are also contained in `evens`
{2, 4, 6}
>>> my_set & t # equivalent
{2, 4, 6}
>>> my_set.difference(evens) # set difference: elements of `my_set` that are not contained in `evens`
{1, 3, 5}
>>> my_set - t # equivalent
{1, 3, 5}
>>> my_set.union(evens) # set union: set containing all of the elements of `my_set` and all of the elements of `evens`
{1, 2, 3, 4, 5, 6, 8, 10}
>>> my_set | evens # equivalent
{1, 2, 3, 4, 5, 6, 8, 10}
>>> print(dir.__doc__) # builtin function `dir` returns all the methods/attributes of an object. Here's its docstring:
dir() -> list of strings
If called without an argument, return the names in the current scope.
Else, return an alphabetized list of names comprising (some of) the attributes
of the given object, and of attributes reachable from it.
If the object supplies a method named __dir__, it will be used; otherwise
the default dir() logic is used and returns:
for a module object: the module's attributes.
for a class object: its attributes, and recursively the attributes
of its bases.
for any other object: its attributes, its class's attributes, and
recursively the attributes of its class's base classes.
>>> my_list_attrs = set([attr for attr in dir(my_list) if not attr.startswith("__")])
>>> my_set_attrs = set([attr for attr in dir(my_set) if not attr.startswith("__")])
>>> my_list_attrs
{'index', 'count', 'append', 'clear', 'pop', 'copy', 'remove', 'extend', 'reverse', 'sort', 'insert'}
>>> my_set_attrs
{'issuperset', 'intersection', 'update', 'difference_update', 'discard', 'issubset', 'symmetric_difference', 'difference', 'clear', 'pop', 'symmetric_difference_update', 'copy', 'isdisjoint', 'union', 'remove', 'add', 'intersection_update'}
>>> # Q: what attributes are defined for both sets AND lists?
>>> # A: use set intersection
>>> my_list_attrs & my_set_attrs #alternately: my_list_attrs.intersection(my_set_attrs)
{'copy', 'remove', 'pop', 'clear'}
>>> # Q2: attributes defined for lists but NOT sets?
>>> # A: use set difference
>>> my_list_attrs - my_set_attrs # alternately: my_list_attrs.difference(my_set_attrs)
{'index', 'count', 'append', 'reverse', 'extend', 'sort', 'insert'}
>>> #Q2b: attributes defined for sets but not lists?
>>> my_set_attrs - my_list_attrs # alternately: my_set_attrs.difference(my_list_attrs)
{'issuperset', 'intersection', 'update', 'difference_update', 'discard', 'issubset', 'symmetric_difference', 'difference', 'isdisjoint', 'union', 'symmetric_difference_update', 'add', 'intersection_update'}
>>> #Q3: all attributes defined for either sets or lists (or both)?
>>> # A: use set union
>>> my_set_attrs | my_list_attrs # alternately: my_set_attrs.union(my_list_attrs)
{'intersection', 'discard', 'pop', 'copy', 'remove', 'isdisjoint', 'union', 'issuperset', 'update', 'index', 'difference_update', 'issubset', 'symmetric_difference', 'count', 'append', 'difference', 'clear', 'reverse', 'extend', 'sort', 'symmetric_difference_update', 'add', 'insert', 'intersection_update'}
>>> my_set[0] # sets have no concept of sequence. in the words of noted mathematician Heidi Klum: 'you either in, or you out.'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable
'set' object is not subscriptable
>>> for elem in my_set: # don't count on this to yield elements in order of insertion... it might not!
... print(elem)
1
2
3
4
5
>>> # wrapping up. some examples of more complex structures
>>> rainfall = {"mon": [1,3,5], "tue": [3,5,7], "wed": [0, 0, 0]} # a dictionary with list keys
>>> rainfall
{'mon': [1, 3, 5], 'tue': [3, 5, 7], 'wed': [0, 0, 0]}
>>> for day_of_week, rainfall_amts in rainfall.items(): # iteration works the same with lists as with any other data type
... print(rainfall_amts)
[1, 3, 5]
[3, 5, 7]
[0, 0, 0]
>>> # Q: how to calculate average rainfall amount per day of week?
>>> for day_of_week, rainfall_amts in rainfall.items():
... rainfall_avg = sum(rainfall_amts)/len(rainfall_amts)
... print(f"{day_of_week} average rainfall is: {rainfall_avg}")
mon average rainfall is: 3.0
tue average rainfall is: 5.0
wed average rainfall is: 0.0
>>> # or using list comprehension syntax...
>>> rainfall_avgs = [sum(rainfall_amts)/len(rainfall_amts) for day_of_week, rainfall_amts in rainfall.items()]
>>> rainfall_avgs
[3.0, 5.0, 0.0]
>>> # alternatively, we key these 'values' to the respective items of `rainfall_amts.keys()` using a 'dict comprehension'
>>> rainfall_avgs = {day_of_week : sum(rainfall_amts)/len(rainfall_amts) for day_of_week, rainfall_amts in rainfall.items()}
>>> rainfall_avgs
{'mon': 3.0, 'tue': 5.0, 'wed': 0.0}
>>> rainfall_avgs = {day_of_week : sum(rainfall_amts)/len(rainfall_amts) for day_of_week, rainfall_amts in rainfall.items() if 'e' in day_of_week} # could optionally filter out certain elements doing something like this
>>> rainfall_avgs
{'tue': 5.0, 'wed': 0.0}
>>> rainfall_avgs = {day_of_week : sum(rainfall_amts)/len(rainfall_amts) for day_of_week, rainfall_amts in rainfall.items()}
>>> rainfall_avgs = {} # the long way
>>> for day_of_week, rainfall_amts in rainfall.items():
... rainfall_avg = sum(rainfall_amts)/len(rainfall_amts)
... rainfall_avgs[day_of_week] = rainfall_avg # brrrrr
>>> rainfall_avgs # ultimately though, it's the same thing
{'mon': 3.0, 'tue': 5.0, 'wed': 0.0}
>>> squares = [i**2 for i in range(100)] # list of all numbers 0-99 raised to the power of 2?
>>> squares
[0, 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]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment