Sequence
A sequence is an ordered collection of values.
- Length : A sequence has a finite length .An empty sequence has length 0
- Element Selection : A sequence has an element corresponding to any non-negative integer index less than its length ,starting at 0 for the first element.
Lists*
Any values can be included in a list.
If we want to change other type into a list , we can just use list().
e.g., the result of list(range(5, 8)) is [5, 6, 7]
Some built-in methods of Lists:
-
extend & +
- the difference between + and extend : extend will change the list digits directly ,but add won’t change the digits unless we give an assignment
-
extend() & append()
- we can only extend list , extend other type will cause error
- we can append anything to a list
- append(1) is equivalent to extend([1])
- Neither append() or extend() has the return value , so we can not use assignments.
- the result of lists.extend(lists) is equal to lists*2
- lists.append(lists) will get an infinite list
# Add
>>> digits = [1, 8, 2, 8]
>>> [2, 7] + digits * 2
[2, 7, 1, 8, 2, 8, 1, 8, 2, 8]
>>> x = [1]
>>> y = 2
# extend()
>>> digits.extend(x)
>>> digits
[1, 8, 2, 8, 1]
>>> digits.extend(y)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
# append()
>>> digits.append(y)
>>> digits
[1, 8, 2, 8, 1, 2]
>>> digits.append(x)
>>> digits
[1, 8, 2, 8, 1, [1]]
>>> digits.extend(digits)
>>> digits
[1, 8, 2, 8, 1, [1], 1, 8, 2, 8, 1, [1]]
>>> digits = [1, 8, 2, 8]
# self referencial
>>> digits.append(digits)
>>> digits
[1, 8, 2, 8, [...]]
- pop() & remove() & insert()
- pop() delete the last eement if we do not pass the argument value
- remove() takes exactly one argument, and the argument must be in the list
- insert() takes two arguments. The first is the inserting position (index) , and another is the element you want to insert to the list.
# pop() : delete the last element of the list and return this element
>>> my_list = [1,2,3,[4]]
>>> my_list.pop()
[4]
>>> my_list
[1, 2, 3]
# delete specific value using list index
>>> my_list.pop(0)
1
>>> my_list
[2, 3]
# remove()
>>> r_list = [1,2,3,[4]]
>>> r_list.remove(2)
>>> r_list
[1, 3, [4]]
>>> r_list.remove(0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
# insert(index ,insert_elements)
>>> i_list = [1,2,3,[4]]
>>> i_list.insert(1,['test'])
>>> i_list
[1, ['test'], 2, 3, [4]]
- index() & count()
- index() takes one argument which must exist in the list, and then return the index of the element
- count() takes one argument which must exist in the list, and then return the times that the element appears in the list
# index()
>>> in_list = [1, 2, 3]
>>> in_list.index(2)
1
# count()
>>> c_list = [1,2,4,5,6,3,5]
>>> c_list.count(5)
2
- reverse() & sort()
- Neither of them has the return value and takes arguments
- reverse() is just reversing the position of the elements , no matter how big the elements’ size are.
- sort() is arranged in ascending order and is related the specific elements’ sizes
# reverse()
>>> r_list = [1,2,4,5]
>>> r_list.reverse()
>>> r_list
[5, 4, 2, 1]
# sort()
>>> r_list.sort()
>>> r_list
[1, 2, 4, 5]
>>>
List comprehension
[ <map expression > for <name> in <sequence> if <filter expression>]
# high-order function
def keep_if(filter_fn, s):
return [x for x in s if filter_fn(x)]
# lambda function :
keep_if = lambda filter_fn, s: list(filter(filter_fn, s))
Sequence Abstraction
- Membership
- A value can be tested for membership in a sequence
- Python has two operators in and not in that evaluate to True or False depending on whether an element appears in a sequence.
- Slicing
- A slice of a sequence is any contiguous span of the original sequence
- 0 for the starting index, and the length of the sequence for the ending index.
Strings
-
Multiline Literals
- Triple quotes delimit string literals that span multiple lines. “”“…”“”
-
String Coercion
- A string can be created from any object in Python by calling the str constructor function with an object value as its argument.
>>> """The Zen of Python
claims, Readability counts.
Read more: import this."""
'The Zen of Python\nclaims, "Readability counts."\nRead more: import this.'
>>> digits = [1, 8, 2, 8]
>>> str(2) + ' is an element of ' + str(digits)
'2 is an element of [1, 8, 2, 8]'
Project cats : pawssible_patches
The main idea of this problem is if we want to check whether A is completely equal to B steps by steps, A’s addition is the same as B’s subtraction
def pawssible_patches(start, goal, limit):
"""A diff function that computes the edit distance from START to GOAL.
>>> big_limit = 10
>>> pawssible_patches("cats", "scat", big_limit) # cats -> scats -> scat
2
>>> pawssible_patches("purng", "purring", big_limit) # purng -> purrng -> purring
2
>>> pawssible_patches("ckiteus", "kittens", big_limit) # ckiteus -> kiteus -> kitteus -> kittens
3
"""
# assert False, 'Remove this line'
if limit < 0:
return 0
if not start and not goal:
return 0
elif not start or not goal:
return abs(len(start)-len(goal))
elif start[0] == goal[0]:
return pawssible_pathches(start[1:],goal[1:],limit)
else:
add_diff = pawssible_pathches(start,goal[1:],limit-1)
remove_diff = pawssible_pathches(start[1:],goal,limit-1)
substitute_diff = pawssible_pathches(start[1:],goal[1:],limit-1)
return min(add_diff,remove_diff,substitute_diff) + 1
Mutable Data
List is Mutable
- We can using index to change some part of the list , and the original list will be changed ,too.
- Assignment means two names refer to the same list . If we want to build a new list containing the same elements with the old one but not changing while the old one is changed, we can use list().
>>> old_list = [1,2,4,5]
# assignment
>>> copied = old_list
>>> new_list = list(old_list)
>>> old_list.pop()
5
>>> old_list
[1, 2, 4]
>>> copied
[1, 2, 4]
>>> new_list
[1, 2, 4, 5]
- The list is always variable, no matter where it is. When the list loses square brackets, it no longer changes with the original list.
>>> A = [1, 2, 3]
>>> B = [4, 5]
>>> C = [7]
>>> A.append(B)
>>> A
[1, 2, 3, [4, 5]]
>>> A.extend(C)
>>> A
[1, 2, 3, [4, 5], 7]
>>> B.append(6)
>>> B
[4, 5, 6]
>>> A
[1, 2, 3, [4, 5, 6], 7]
>>> C.extend([8])
>>> C
[7, 8]
>>> A
[1, 2, 3, [4, 5, 6], 7]
- Slicing will create a new list, which means when we change the original list , the slicing part won’t change synchronously
>>> t = [4, 5]
>>> s = [1, 2]
>>> a = s + [t]
>>> b = a[1:]
>>> s
[1, 2]
>>> t
[4, 5]
>>> a
[1, 2, [4, 5]]
>>> b
[2, [4, 5]]
>>> a[1] = 0
>>> a[2][0] = 3
>>> a
[1, 0, [3, 5]]
>>> b
[2, [3, 5]]
>>> t
[3, 5]
>>> s
[1, 2]
# append a slice of a list
>>> x = [1, 2, 4, 5]
>>> y = [6, 7, 8, 9]
>>> x.append(y[1:3])
>>> x
[1, 2, 4, 5, [7, 8]]
>>> y[1]=0
>>> y
[6, 0, 8, 9]
>>> x
[1, 2, 4, 5, [7, 8]]
Immutable Sequence : Strings, Tuples, dictionaries’ keys
- ** A mutable sequence may still change if it contains a mutable value as an element**
- The value of an expression can change because of changes in named or objects.
Tuple
- Tuples use parentheses ().
- If we want to change a list to tuple ,just use tuple([1,2,4])->(1,2,4).
- A tuple that contains only one element is special . We need to add the comma behind the only one element .Like this(2,)
- tuple is immutable which means it can be used as the key of the dictionary
- if s = ([1, 3] , 3) , we can not assign s[0] to other value, but we can change s[0][1]=2, then we call s ,we will get ([1, 2] , 3)
Dictionary
- d = {key1: value1, key2: value2}
- dictionary has no order
- key cannot be a list while value can.
# all keys in d
>>> d = {'k1': 1, 'k2': 2}
>>> for k in d.keys():
... print(k)
...
k1
k2
# all pairs in d
>>> for i in d.items():
... print(i)
...
('k1', 1)
('k2', 2)
# all values in d
>>> for v in d.values():
... print(v)
...
1
2
# find a key's value
>>> d.get('k1','not found')
1
>>> d.get('1','not found')
'not found'
# add elements / change elements
>>> d['k3'] = 3
>>> d
{'k1': 1, 'k2': 2, 'k3': 3}
>>> d['k1'] = 0
>>> d
{'k1': 0, 'k2': 2, 'k3': 3}
Some cons of Mutable values
- Mutable Default arguments are dangerous. A default argument value is part of a function value ,not generated by a call
Local State
Using nonlocal statement in the higher-order function to operate or change the global arguments.
The nonlocal statement changes all of the remaining assignment statements in the function .
The effect of Nonlocal statements
- Future assignments to that name change its pre-existing binding in the first non-local frame of the current environment in which that name is bound.
hw05
Bank
def make_bank(balance):
"""Returns a bank function with a starting balance. Supports
withdrawals and deposits.
write a new dunction make_bank ,which should create a bank account with value balance and should also return another function
This new function should be able to withdraw ans deposit money.
The second function will take in two arguments : message and account
When the message passed in is 'deposit', the bank will deposit amount into the account /
If the account does not have enough money for a withdrawal, the string 'Insufficient funds' will be returned.If the message passed in is neither of the two commands, the function should return 'Invalid message'.
>>> bank = make_bank(100)
>>> bank('withdraw', 40) # 100 - 40
60
>>> bank('hello', 500) # Invalid message passed in
'Invalid message'
>>> bank('deposit', 20) # 60 + 20
80
>>> bank('withdraw', 90) # 80 - 90; not enough money
'Insufficient funds'
>>> bank('deposit', 100) # 80 + 100
180
>>> bank('goodbye', 0) # Invalid message passed in
'Invalid message'
>>> bank('withdraw', 60) # 180 - 60
120
"""
def bank(message, amount):
nonlocal balance
if message == 'withdraw':
if amount < balance:
balance = balance - amount
return balance
else :
return 'Insufficient funds'
elif message == 'deposit':
balance = balance + amount
return balance
else :
return 'Invalid message'
"*** YOUR CODE HERE ***"
return bank
make_withdraw
def make_withdraw(balance, password):
"""Return a password-protected withdraw function.
write a version of the make_withdraw function shown in the previous question that returns password-protected withdraw function
That is make_withdraw should take a password argument in addition to an initial balance .The returned function should take two arguments: an amount to withdraw and a password
A password-protected withdraw function should only process withdrawals that include a password that matched the original.Upon receiving an incorrect password ,the function should:
1.store that incorrect password in a list
2. Return the string 'incorrect password'
If a withdraw function has been called three times with incorrect passwords<p1><p2><P3>, then it is frozen.All subsequent calls to the function should return/
hint : you can use the str function to turn a list into a string. For example, for a list s = [1,2,3], the expression " the list s is" + str(s) simplified to " the list s in [1,2,3]"
>>> w = make_withdraw(100, 'hax0r')
>>> w(25, 'hax0r')
75
>>> error = w(90, 'hax0r')
>>> error
'Insufficient funds'
>>> error = w(25, 'hwat')
>>> error
'Incorrect password'
>>> new_bal = w(25, 'hax0r')
>>> new_bal
50
>>> w(75, 'a')
'Incorrect password'
>>> w(10, 'hax0r')
40
>>> w(20, 'n00b')
'Incorrect password'
>>> w(10, 'hax0r')
"Frozen account. Attempts: ['hwat', 'a', 'n00b']"
>>> w(10, 'l33t')
"Frozen account. Attempts: ['hwat', 'a', 'n00b']"
>>> type(w(10, 'l33t')) == str
True
"""
"*** YOUR CODE HERE ***"
psw_attempt = []
def check_password(amount,password_in):
nonlocal balance
if len(psw_attempt) == 3:
return "Frozen account. Attempts: " + str(psw_attempt)
if password != password_in:
psw_attempt.append(password_in)
return 'Incorrect password'
else:
if amount < balance:
balance = balance - amount
return balance
else:
return 'Insufficient funds'
return check_password
make_joint
def make_joint(withdraw, old_pass, new_pass):
"""Return a password-protected withdraw function that has joint access to
the balance of withdraw.
Suppose that our banking system requires the ability to make joint accounts.
Define a function make_joint that takes 3 arguments:
1. A password-protected withdraw function
2. the password with which that withdraw function was defined
3. A new password that can also access the original account.
If the password is incorrect or cannot be verified because the underlying account is locked, the make_joint should propagate the error.Otherwise, it returns a withdraw function that provides additional access to the original account using either new or old password.Both functions draws from the same balance.Incorrect passwords provided to either function will be stored and cause the functions to be locked after three wrong attempt.
Hint: the solution is short(less than 10 lines) and contains no string literals.The key is to call withdraw with the right password and amount. then interpret the result. You may assume that all failed attempts to withdraw will return some string(for incorrect passwords,locked accounts or insufficient funds),while successful wirhdrawals will return a number.
>>> w = make_withdraw(100, 'hax0r')
>>> w(25, 'hax0r')
75
>>> make_joint(w, 'my', 'secret')
'Incorrect password'
>>> j = make_joint(w, 'hax0r', 'secret')
>>> w(25, 'secret')
'Incorrect password'
>>> j(25, 'secret')
50
>>> j(25, 'hax0r')
25
>>> j(100, 'secret')
'Insufficient funds'
>>> j2 = make_joint(j, 'secret', 'code')
>>> j2(5, 'code')
20
>>> j2(5, 'secret')
15
>>> j2(5, 'hax0r')
10
>>> j2(25, 'password')
'Incorrect password'
>>> j2(5, 'secret')
"Frozen account. Attempts: ['my', 'secret', 'password']"
>>> j(5, 'secret')
"Frozen account. Attempts: ['my', 'secret', 'password']"
>>> w(5, 'hax0r')
"Frozen account. Attempts: ['my', 'secret', 'password']"
>>> make_joint(w, 'hax0r', 'hello')
"Frozen account. Attempts: ['my', 'secret', 'password']"
"""
"*** YOUR CODE HERE ***"
attempt = withdraw(0,old_pass)
if type(attempt) == str:
return attempt
else:
def joint_account(amount,psw_in):
if psw_in == new_pass or psw_in == old_pass:
return withdraw(amount,old_pass)
else :
return withdraw(amount,psw_in)
return joint_account
Data example
Some useful method references.
def min_abs_indices(s):
min_abs = min(map(abs,s))
return [i for i in range(len(s)) if abs(s[i]) == min_abs]
def largest_adj_sum(s):
return max([s[i]+s[j+1] for i in range(len(s)-1)])
# any
def digit_dict(s):
return {d:[x for x in s if x % 10 ==d] for d in range(10) if any([x%10==d for x in s])}
# slicing
def all_have_an_equal(s):
return all([s[i] in s[:i]+s[i+1:] for i in range(len(s))])
return min([sum([1 for y in s if y == x]) for x in s])>1