These are my notes for SICP(Structure and Interpretation of Computer Programs). Hope they’ll be of some help to you.
A sequence is an ordered collection of values.
- strings: sequence of characters
- lists: sequence of values of any data type
All sequences have finite length.
Each element in a sequence has a discrete integer index.
Sequences share common behaviors based on the shared trait of having a finite length and indexed elements(Remember that the elements are indexed from 0
).
Get item: get the ith element
[i] >>> lst = [1, 2, 3, 4, 5] >>> lst[2] 3 >>> lst[-1] #When the index is negative, ‘-’means from right to left. 5 >>> lst[-2] 4 >>> "cs61a"[3] '1'
Check membership: check if the value of is in
in >>> 3 in [1, 2, 3, 4, 5] True >>> 'z' in "socks" False >>> 2 + 4 in [7, 6, 5, 4, 3] True
Slice a subsequence: create a copy of the sequence from i(included) to j(unincluded)
[i:j:skip] >>> lst = [1, 2, 3, 4, 5] >>> lst[1:4] [2, 3, 4] >>> "lolololololol"[3::2] 'ooooo'
Concatenate: combine two sequences into a single sequence
+ >>> [1, 2, 3] + [4, 5] [1, 2, 3, 4, 5] >>> "hello " + "world" "hello world" >>> [-1] + [0] + [1] [-1, 0, 1]
You can use a for statement to iterate through the elements of a sequence:
for
in :
Rules for execution:
For each element in
:
Bind it to
in current frame
Execute
i = 0
for elem in [8, 9, 10]:
print(i, ":", elem)
i += 1
The range function creates a sequence containing the values within a specified range.
range(
, , ) Creates a range object from (inclusive) to (exclusive), skipping every element, which is useful for looping.
You may just input one number using range, and when this happens, start = 0 and skip = 1, what you input is the (exclusive).
>>> for e in range(1, 8, 2):
... print(e)
1
3
5
7
>>> lst = [8, 9, 10]
>>> for i in range(len(lst)):
... print(i, ":", lst[i])
0: 8
1: 9
2: 10
You can create out a list out of a sequence using a list comprehension:
[
for in if ] It is similar to a call expression, which will create a local frame and do the execution in the local frame. So, binding to will not change the bindings in its parent frames.
if
doesn’t have to be there when it is true all the time.>>> [x ** 2 for x in [1, 2, 3]] [1, 4, 9] >>> [c + “0” for c in "cs61a"] ['c0', 's0', '60', '10', 'a0'] >>> [e for e in "skate" if e > "m"] ['s', 't'] >>> [[e, e+1] for e in [1, 2, 3]] [[1, 2], [2, 3], [3, 4]]
Rules for execution
Create an empty result list that will be the value of the list comprehension
For each element in :
Compound values combine other values together
Data abstraction lets us manipulate compound values as units
Isolate two parts of any program that uses data:
Data abstraction: A methodology by which functions enforce an abstraction barrier between representation and use
A rational number equals to a numerator dividing a denominator
Exact representation as fractions includes a pair of integers. However, as soon as division occurs, the exact representation may be lost!
Assume we can compose and decompose rational numbers:
rational(n, d) #returns a rational number x
numer(x) #returns the numerator of x
denom(x) #returns the denominator of x
Here we call rational(n, d) Constructor and call numer(x) and demon(x) Selectors
def mul_rational(x, y):
return rational(numer(x) * numer(y), denom(x) * denom(y))
def add_rational(x, y):
nx, dx = numer(x), denom(x)
ny, dy = numer(y), denom(y)
return rational(nx * dy + ny * dx, dx * dy)
def print_rational(x):
print(numer(x), '/', denom(x))
def rationals_are_equal(x, y):
return numer(x) * denom(y) == numer(y) * denom(x)
def rational(n, d):
"""A representation of the rational number N/D."""
return [n, d] #Construct a list
def numer(x):
"""Return the numerator of rational number X."""
return x[0]
def denom(x):
"""Return the denominator of rational number X."""
return x[1] #Slect item from a list
alternative way
def rational(n, d):
def slect(name):
if name == 'n':
return n
elif name == 'd':
return d
return slect
def numer(x):
return x('n')
def denom(x):
return x('d')
In many programming languages, functions can return functions. And when a function returns a function, the returning function keeps a reference to all the variables that it needs to execute, which are declared in the parent function.
That’s exactly what a closure is. It is a bucket of references to variables a function needs to execute which are declared outside its scope.
from fractions import gcd #Greatest Common Divisor
def rational(n, d):
"""A representation of the rational number N/D."""
g = gcd(n, d) # Always has the sign of d
return [n//g, d//g]
Dictionaries are unordered collections of key-value pairs.
Each value is bound to a key in a dictionary.
message_dict = {
'name' : 'cuijiacai',
'age' : '18',
'sex' : 'male',
'weight' : '63',
'height' : '171'}
Dictionary keys do have two restrictions:
If you want to associate multiple values with a key, store them all in a sequence value
Get item:
>>> <dictionary-name>[<key>]
<value>
Modify:
>>> <dictionary-name>[<key>] = <value>
If the key exists in the dictionary, this assignment statement will change the value corresponding to this key to the new value to the right of =.
If the key doesn’t exist in the dictionary, this assignment statement will create a new key-value bond in the dictionary.
>>> <dictionary-name>.pop(<key>)
value
The method
pop
will delete the key-value bond in the dictionary and return the value corresponding to the deleted bond.
We can still use the built-in function
len
to get the number of key-value bonds in the dictionary.
Compose:
>>> <dictionary1>.update(<dictionary2>)
The method update will compose two dictionaries together. However, if the key-value bonds in dictionary2 exist in dictionary1, they will replace them.
Travel:
for key, value in <dictionary>.items():
#iterate over each key-value bond
for key in <dictionary>.keys():
#iterate over each key
for value in <dictionary>.values():
#iterate over each value