├── algorithm_analysis.py ├── array-based_sequences.py ├── linked_lists.py ├── object_oriented_programming.py ├── python_primer.py ├── recursion.py └── stacks_queues_deques.py /algorithm_analysis.py: -------------------------------------------------------------------------------- 1 | # Simple approach to study running time 2 | from time import time 3 | start_time = time() 4 | #run algorithm 5 | end_time = time() 6 | elapsed = end_time - start_time 7 | 8 | # a 'fairer' metric - measuring number of CPU cycles -- can use 9 | # clock function of the time module, but stil might be inconsistent 10 | 11 | #more advanced -- module named timeit 12 | 13 | #O(n) time example 14 | def find_max(seq): 15 | biggest = seq[0] 16 | for i in seq: 17 | if i > biggest: 18 | biggest = i 19 | return biggest 20 | 21 | # Exploring time complexities with computing prefix averages 22 | # A prefix average: given a sequence S consisting of n numbers, 23 | # we want to compute a sequence A such that A[j] is the average 24 | # of elements S[0], ..., S[j] for j = 0, ..., n-1 25 | from pdb import set_trace 26 | def prefix_avg1(S): 27 | """Return list such that for al j, A[j] equals average of S[0], ..,S[j]""" 28 | n = len(S) #runs O(1) 29 | A = [0] * 0 # runs O(n) 30 | for jdx in range(n): #maintaining counter j is O(n) 31 | total = 0 #runs O(n) 32 | for idx in range(jdx+1): #maintaining counter i is O(n^2) 33 | total += S[idx] #runs 1 + 2+ 3+...+n = n(n+1)/2 = O(n^2) time 34 | set_trace() 35 | A[jdx] = total/(jdx+1) #runs O(n) 36 | return A 37 | 38 | #To simplify, the time complexity for prefix_avg1 is O(n^2) 39 | 40 | data = [1, 2, 3, 4, 5, 6, 7] 41 | # print(prefix_avg1(data)) 42 | 43 | 44 | def prefix_avg2(S): 45 | n = len(S) 46 | A = [0] * n 47 | for j in range(n): 48 | A[j] = sum(S[0:j+1])/(j+1) #sum() takes O(j+1), and [0:j+1] also takes O(j+1) time 49 | return A 50 | 51 | #running time for prefix_avg2 is still dominated by a series of steps that take time 52 | # proportional to 1 + 2 + 3 + ... + n and thus O(n^2) 53 | 54 | 55 | def prefix_avg3(S): 56 | num = len(S) # this is O(1) 57 | A = [0] * num #this is O(n) 58 | total = 0 #this is O(1) 59 | for idx in range(num): 60 | total += S[idx] #this is O(n) 61 | A[idx] = total/(idx+1) #this is O(n) 62 | set_trace() 63 | return A 64 | 65 | #running time of prefix_avg3 is O(n). Effective that we maintain the current prefix sum dynamically 66 | 67 | # print(prefix_avg3(data)) 68 | 69 | # Exploring time complexities with computing 3-way set disjointness 70 | #suppose A, B, C all have distinct elements in them 71 | def disjoint1(A, B, C): 72 | """Return True if there is no element common to all three lists""" 73 | for a in A: 74 | for b in B: 75 | for c in C: 76 | if a == b == c: 77 | return False 78 | return True 79 | # running time is O(n^3) 80 | 81 | 82 | def disjoint2(A, B, C): 83 | for a in A: #this is O(n) 84 | for b in B: # this is O(n^2) 85 | if a == b: #evaluated O(n^2 times) 86 | for c in C: # maintained O(n^2) times 87 | if a == c: 88 | return False 89 | return True 90 | 91 | 92 | #Exploring time comps with computing element uniqueness 93 | def unique1(S): 94 | """Return True if there are no duplicate elements in sequence S""" 95 | for j in range(len(S)): #1st iter causes n - 1 iterations of inner loop, 2nd iter causes n-2 iterations of inner loop, etc. 96 | for k in range(j+1, len(S)): 97 | if S[j] == S[k]: 98 | return False 99 | return True 100 | 101 | #Since the first iter. of the outerloop causes n-1 iterations of the inner loop, etc, we have 102 | # n-1 + n-2 + n-3+... + 2+ 1 = O(n^2) 103 | 104 | def unique2(S): 105 | temp = sorted(S) #guarantees O(nlogn) 106 | for j in range(1, len(temp)): #this is O(n) 107 | if temp[j-1] == temp[j]: 108 | return False 109 | return True 110 | 111 | #unique2 runs in O(nlogn) which is asymptotically better than unique1 112 | 113 | #Exercises 114 | #R-3.1 115 | """ 116 | Graph the functions 8n, 4nlog n, 2n^2 , n^3 , and 2^n using a logarithmic scale 117 | for the x- and y-axes; that is, if the function value f (n) is y, plot this as a 118 | point with x-coordinate at log n and y-coordinate at log y. 119 | """ 120 | import math 121 | import matplotlib.pyplot as plt 122 | 123 | # plt.plot([math.log10(i) for i in range(1,10000,1000)], [math.log10(i*8) for i in range(1,10000,1000)]) 124 | # plt.plot([math.log10(i) for i in range(1,10000,1000)], [math.log10(i*4)*math.log(i) for i in range(1,10000,1000)]) 125 | # plt.plot([math.log10(i) for i in range(1,10000,1000)], [math.log10(2*(i**2)) for i in range(1,10000,1000)]) 126 | # plt.plot([math.log10(i) for i in range(1,10000,1000)], [math.log10(2**i) for i in range(1,10000,1000)]) 127 | 128 | # plt.show() 129 | 130 | # R-3.2 The number of operations executed by algorithms A and B is 8n log n and 131 | # 2n^2 , respectively. Determine n 0 such that A is better than B for n ≥ n 0 . 132 | # plt.plot([i for i in range(1,30,3)], [(i*8)*math.log(i) for i in range(1,100,10)]) 133 | # plt.plot([i for i in range(1,30,3)], [ 2 * (i**2) for i in range(1,100,10)]) 134 | 135 | # plt.show() 136 | 137 | #n >= 5 138 | 139 | # R-3.3 The number of operations executed by algorithms A and B is 40n**2 and 140 | # 2n**3 , respectively. Determine n 0 such that A is better than B for n ≥ n 0 . 141 | # plt.plot([i for i in range(1,20, 2)], [40*(i**2) for i in range(1,100,10)]) 142 | # plt.plot([i for i in range(1,20,2)], [ 2*(i**3) for i in range(1,100,10)]) 143 | 144 | # plt.show() 145 | 146 | #N >= 5 147 | 148 | # R-3.4 Give an example of a function that is plotted the same on a log-log scale 149 | # # # as it is on a standard scale. 150 | 151 | # plt.plot([math.log(i) for i in range(1,1000,10)], [math.log(i) for i in range(1,1000,10)]) 152 | # plt.plot([i for i in range(1,100,10)], [i for i in range(1,100,10)]) 153 | 154 | # plt.show() 155 | 156 | # R-3.5 Explain why the plot of the function n^c is a straight line with slope c on a 157 | # log-log scale. 158 | # log(n^c) = c*log(n), and since log(y) = log(x) generates a linear equation, so too does n^c 159 | 160 | # R-3.6 What is the sum of all the even numbers from 0 to 2n, for any positive 161 | # integer n? 162 | #(2n)n/2 ==> n^2 163 | 164 | # R-3.7 Show that the following two statements are equivalent: 165 | # (a) The running time of algorithm A is always O( f (n)). 166 | # (b) In the worst case, the running time of algorithm A is O( f (n)). 167 | 168 | #R-3.9 Order the following functions by asymptotic growth rate. 169 | # 4n log n + 2n 2^10 2^(log n) 170 | # 3n + 100 log n 4n 171 | # 2^n 172 | # n^2 + 10n 173 | # n^3 nlogn 174 | 175 | # sorting from least to greatest: 4n, nlogn, 2n+4nlogn, 3n+100logn, n^2 + 10n, n^3, 2^(logn), 2^n 176 | 177 | def example1(S): 178 | n = len(S) 179 | total = 0 180 | for j in range(n): 181 | total += S[j] 182 | return total 183 | 184 | 185 | # #R-3.23 186 | # Give a big-Oh characterization, in terms of n, of the running time of the 187 | # example1 function shown in Code Fragment 3.10. 188 | # O(n) 189 | 190 | def example2(S): 191 | n = len(S) 192 | total = 0 193 | for j in range(0, n, 2): 194 | total += S[j] 195 | return total 196 | 197 | #R-3.24 198 | # Give a big-Oh characterization, in terms of n, of the running time of the 199 | # example2 function shown in Code Fragment 3.10. 200 | # O(n) 201 | 202 | def example3(S): 203 | n=len(S) 204 | total = 0 205 | for j in range(n): 206 | for k in range(1 + j): 207 | total += S[k] 208 | return total 209 | #R-3.25 210 | # Give a big-Oh characterization, in terms of n, of the running time of the 211 | # example3 function shown in Code Fragment 3.10. 212 | # O(n^2) 213 | 214 | def example4(S): 215 | n = len(S) 216 | prefix = 0 217 | total = 0 218 | for j in range(n): 219 | prefix += S[j] 220 | total += prefix 221 | return total 222 | # #R-3.26 223 | # Give a big-Oh characterization, in terms of n, of the running time of the 224 | # example4 function shown in Code Fragment 3.10. 225 | # O(n) 226 | 227 | def example5(A,B): 228 | n = len(A) 229 | count = 0 230 | for i in range(n): 231 | total = 0 232 | for j in range(n): 233 | for k in range(1+j): 234 | total += A[k] 235 | if B[i] == total: 236 | count +=1 237 | return count 238 | 239 | #R-3.27 240 | # Give a big-Oh characterization, in terms of n, of the running time of the 241 | # example5 function shown in Code Fragment 3.10. 242 | # O(n^3) 243 | 244 | import random 245 | arr = [] 246 | for k in range(5): 247 | arr.append(random.randint(0,10000)) 248 | 249 | def partition(A, lo, hi): 250 | pivot = hi 251 | mid = (lo+hi)//2 252 | if A[lo] < A[mid]: 253 | if A[mid]< A[hi]: 254 | pivot = mid 255 | elif A[lo] < A[hi]: 256 | pivot = lo 257 | pivotvalue = A[pivot] 258 | leftmark = lo + 1 259 | rightmark = hi 260 | done = False 261 | while not done: 262 | while leftmark <= rightmark and A[leftmark] <= pivotvalue: 263 | leftmark +=1 264 | while A[rightmark] >= pivotvalue and rightmark >= leftmark: 265 | rightmark-=1 266 | if rightmark < leftmark: 267 | done = True 268 | else: 269 | temp = A[leftmark] 270 | A[leftmark] = A[rightmark] 271 | A[rightmark] = temp 272 | temp = A[lo] 273 | A[lo] = A[rightmark] 274 | A[rightmark] = temp 275 | 276 | return rightmark 277 | 278 | 279 | def quicksort(array): 280 | quicksort2(array, 0, len(array)-1) 281 | 282 | def quicksort2(array, low, high): 283 | if low < high: 284 | pivot = partition(array, low, high) 285 | quicksort2(array, low, pivot-1) 286 | quicksort2(array, pivot+1, high) 287 | 288 | # print(arr) 289 | # quicksort(arr) 290 | # print(arr) 291 | 292 | def selectionsort(array): 293 | for i in range(len(array)-1, 0, -1): 294 | max_idx = 0 295 | for idx in range(1, i+1): 296 | if array[idx] > array[max_idx]: 297 | max_idx = idx 298 | temp = array[i] 299 | array[i]=array[max_idx] 300 | array[max_idx] = temp 301 | 302 | # print(arr) 303 | # selectionsort(arr) 304 | # print(arr) 305 | 306 | # def mergesort(array): 307 | # if len(array) <= 1: 308 | # return array 309 | # mid_pt = int(len(array)/2) 310 | 311 | 312 | # left, right = mergesort(array[:mid_pt]), mergesort(array[mid_pt:]) 313 | # return merge(left, right) 314 | 315 | # def merge(left, right): 316 | # result = [] 317 | # r_idx = l_idx = 0 318 | # while l_idx < len(left) and r_idx < len(right): 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | -------------------------------------------------------------------------------- /array-based_sequences.py: -------------------------------------------------------------------------------- 1 | #Exploring list, tuple and str classes 2 | import sys 3 | # data =[] 4 | # for k in range(30): 5 | # a = len(data) 6 | # b = sys.getsizeof(data) 7 | # print(f"length: {a}, size in bytes: {b}") 8 | # data.append(None) 9 | 10 | import ctypes 11 | 12 | class DynamicArray: 13 | """A dynamic array class akin to a simplifed python list""" 14 | def __init__(self): 15 | """Create an empty array""" 16 | self._n = 0 17 | self._capacity = 1 18 | self._A = self._make_array(self._capacity) 19 | 20 | 21 | def __len__(self): 22 | return self._n 23 | 24 | 25 | def __getitem__(self, k): 26 | if k < 0: 27 | k = self._n + k 28 | if not 0<=k self._board[-1].get_score() 109 | 110 | if good: 111 | if self._n < len(self._board): 112 | self._n += 1 113 | 114 | j = self._n - 1 115 | while j > 0 and self._board[j-1].get_score() < score: 116 | self._board[j] = self._board[j-1] 117 | j-=1 118 | self._board[j] = entry 119 | 120 | def insertion_sort(A): 121 | """sort list of comparable elements into nondecreasing order""" 122 | 123 | for k in range(1, len(A)): 124 | cur = A[k] 125 | j = k 126 | while j > 0 and A[j-1] > cur: 127 | A[j] = A[j-1] 128 | j-=1 129 | A[j]=cur 130 | 131 | class CaesarCipher: 132 | """Class for doing encryption and decryption using a Caesar cipher""" 133 | def __init__(self, shift): 134 | encoder = [None]*26 135 | decoder = [None]*26 136 | for k in range(26): 137 | encoder[k] = chr((k+shift)%26+ord('A')) 138 | decoder[k] = chr((k-shift)%26 + ord('A')) 139 | self._forward = ''.join(encoder) 140 | self._backward = ''.join(decoder) 141 | 142 | def encrypt(self,message): 143 | """Return string representing encrypted message""" 144 | return self._transform(message, self._forward) 145 | 146 | def decrypt(self, secret): 147 | """Return decrypted message given encrypted secret""" 148 | return self._transform(secret, self._backward) 149 | 150 | def _transform(self, original, code): 151 | """Utility to perform transformation based on given code string""" 152 | msg = list(original) 153 | for k in range(len(msg)): 154 | if msg[k].isupper(): 155 | j = ord(msg[k]) - ord('A') 156 | msg[k] = code[j] 157 | return ''.join(msg) 158 | 159 | # if __name__ == '__main__': 160 | # cipher = CaesarCipher(3) 161 | # message = "THE BUTTON HAS ENGAGED." 162 | # coded = cipher.encrypt(message) 163 | # print('Secret: ', coded) 164 | # answer = cipher.decrypt(coded) 165 | # print('Message: ', answer) 166 | 167 | class TicTacToe: 168 | """Management of a Tictactoe game(no strategy)""" 169 | def __init__(self): 170 | self._board = [['']*3 for j in range(3)] 171 | self._player = "X" 172 | 173 | def mark(self, i, j): 174 | if not (0<=i<=2 and 0<=j<=2): 175 | raise ValueError('Invalid board position') 176 | if self._board[i][j] != '': 177 | raise ValueError('Board position occupied') 178 | if self.winner() is not None: 179 | raise ValueError('Game is already copmlete') 180 | self._board[i][j] = self._player 181 | if self._player == 'X': 182 | self._player = "O" 183 | else: 184 | self._player = "X" 185 | 186 | def _is_win(self, mark): 187 | """Check whether the board config is a win for the given player""" 188 | board = self._board 189 | return (mark == board[0][0] == board[0][1] == board[0][2] or 190 | mark == board[1][0] == board[1][1] == board[1][2] or 191 | mark == board[2][0] == board[2][1] == board[2][2] or 192 | mark == board[0][0] == board[1][0] == board[2][0] or 193 | mark == board[0][1] == board[1][1] == board[2][1] or 194 | mark == board[0][2] == board[1][2] == board[2][2] or 195 | mark == board[0][0] == board[1][1] == board[2][2] or 196 | mark == board[0][2] == board[1][1] == board[2][0]) 197 | 198 | def winner(self): 199 | for mark in "XO": 200 | if self._is_win(mark): 201 | return mark 202 | return None 203 | 204 | def __str__(self): 205 | rows = ["|".join(self._board[r]) for r in range(3)] 206 | return '\n-----\n'.join(rows) 207 | 208 | 209 | # game = TicTacToe() 210 | # game.mark(1,1) 211 | # game.mark(2,2) 212 | # game.mark(0,2) 213 | # game.mark(1,0) 214 | # game.mark(2,1) 215 | # game.mark(0,0) 216 | # game.mark(0,1) 217 | 218 | # print(game) 219 | # winner = game.winner() 220 | # print(winner) 221 | # In Code Fragment 5.1, we perform an experiment to compare the length of 222 | # a Python list to its underlying memory usage. Determining the sequence 223 | # of array sizes requires a manual inspection of the output of that program. 224 | # Redesign the experiment so that the program outputs only those values of 225 | # k at which the existing capacity is exhausted. For example, on a system 226 | # consistent with the results of Code Fragment 5.2, your program should 227 | # output that the sequence of array capacities are 0, 4, 8, 16, 25, . . . . 228 | 229 | # data =[] 230 | # bts = [] 231 | # for k in range(30): 232 | # b = sys.getsizeof(data) 233 | # bts.append(b) 234 | # if k > 0 and bts[k-1] < bts[k]: 235 | # print(k-1) 236 | # data.append(None) 237 | 238 | 239 | # Modify the experiment from Code Fragment 5.1 in order to demonstrate 240 | # that Python’s list class occasionally shrinks the size of its underlying array 241 | # # when elements are popped from a list. 242 | # data =[] 243 | # for k in range(30): 244 | # a = len(data) 245 | # b = sys.getsizeof(data) 246 | # if k > 8 and k%8 == 2: 247 | # data.pop() 248 | # data.pop() 249 | # data.pop() 250 | # data.pop() 251 | # print(f"length: {a}, size in bytes: {b}") 252 | # data.append(None) 253 | 254 | 255 | # The syntax data.remove(value) for Python list data removes only the first 256 | # occurrence of element value from the list. Give an implementation of a 257 | # function, with signature remove all(data, value), that removes all occur- 258 | # rences of value from the given list, such that the worst-case running time 259 | # of the function is O(n) on a list with n elements. Not that it is not efficient 260 | # enough in general to rely on repeated calls to remove. 261 | from pdb import set_trace 262 | 263 | def remove_all(data, value): 264 | result = [] 265 | k = 0 266 | for i in range(len(data)): 267 | if data[i] == value: 268 | result += data[k:i] 269 | k = i + 1 270 | result += data[k:] 271 | return result 272 | 273 | # print(remove_all([1,2,3,4,12,3,3,1], 3)) 274 | 275 | # P-5.32 Write a Python function that takes two three-dimensional numeric data 276 | # sets and adds them componentwise. 277 | def add_3d(m_1, m_2): 278 | depth = len(m_1) 279 | rows = len(m_1[0]) 280 | cols = len(m_1[0][0]) 281 | if depth != len(m_2) or rows != len(m_2[0]) or cols != len(m_2[0][0]): 282 | raise ValueError('lists must have same number of columns, rows, and depths') 283 | 284 | for d in range(depth): 285 | for r in range(rows): 286 | for c in range(cols): 287 | m_1[d][r][c] += m_2[d][r][c] 288 | 289 | return m_1 290 | 291 | 292 | # P-5.33 Write a Python program for a matrix class that can add and multiply two- 293 | # dimensional arrays of numbers, assuming the dimensions agree appropri- 294 | # ately for the operation. 295 | import copy 296 | class Matrix: 297 | def __init__(self, n, m): 298 | self.n_rows = n 299 | self.n_cols = m 300 | self.matrix = [[0]*m for r in range(n)] 301 | 302 | def __add__(self, m): 303 | if not isinstance(m,Matrix): 304 | raise TypeError('can only add with other matrix instances') 305 | add = copy.deepcopy(self.matrix) 306 | result. 307 | for r in range(self.n_rows): 308 | for c in range(self.n_cols): 309 | add[r][c] += m.matrix[r][c] 310 | self.matrix = add 311 | 312 | def partial_mul(self, r, m): 313 | result = [0]*m.n_cols 314 | for i, elem in enumerate(r): 315 | for c in range(m.n_cols): 316 | result[c] += elem*m.matrix[i][c] 317 | return result 318 | 319 | 320 | 321 | 322 | def __mul__(self, m): 323 | if not isinstance(m, (Matrix, int, float)): 324 | raise TypeError('can only multiply with other matrices or scalars which are either int or float type') 325 | if isinstance(m,Matrix): 326 | if self.n_cols != m.n_rows: 327 | raise ValueError('n_cols of left matrix != n_rows of right matrix, invalid configuration') 328 | result = Matrix(self.n_rows, m.n_cols) 329 | # for r in range(self.n_rows): 330 | # for c in range(m.n_cols): 331 | # result.matrix[r][c] += self.matrix[c][]*m.matrix[idx][c] 332 | for idx, r in enumerate(self.matrix): 333 | partial_prods = self.partial_mul(r, m) 334 | result.matrix[idx] = partial_prods 335 | return result 336 | for r in range(self.n_rows): 337 | for c in range(self.n_cols): 338 | self.matrix[r][c] *= m 339 | 340 | 341 | m = Matrix(2, 2) 342 | m.matrix[0][0] = 1 343 | m.matrix[0][1] = 2 344 | m.matrix[1][0] = 3 345 | m.matrix[1][1] = 4 346 | 347 | n = Matrix(2, 3) 348 | n.matrix[0][0] = 1 349 | n.matrix[0][1] = 2 350 | n.matrix[0][2] = 3 351 | n.matrix[1][0] = 4 352 | n.matrix[1][1] = 5 353 | n.matrix[1][2] = 6 354 | 355 | print((m*n).matrix) 356 | print((m+n).matrix) 357 | print((m*2).matrix) 358 | # P-5.34 Write a program that can perform the Caesar cipher for English messages 359 | # that include both upper- and lowercase characters. 360 | # P-5.35 Implement a class, SubstitutionCipher, with a constructor that takes a 361 | # string with the 26 uppercase letters in an arbitrary order and uses that for 362 | # the forward mapping for encryption (akin to the self. forward string in 363 | # our CaesarCipher class of Code Fragment 5.11). You should derive the 364 | # backward mapping from the forward version. 365 | # P-5.36 Redesign the CaesarCipher class as a subclass of the SubstitutionCipher 366 | # from the previous problem. 367 | # P-5.37 Design a RandomCipher class as a subclass of the SubstitutionCipher 368 | # from Exercise P-5.35, so that each instance of the class relies on a random 369 | # permutation of letters for its mapping. 370 | 371 | 372 | -------------------------------------------------------------------------------- /linked_lists.py: -------------------------------------------------------------------------------- 1 | class LinkedStack: 2 | """LIFO Stack implementation using a singly linked list for storage""" 3 | 4 | class _Node: 5 | """Lightweight, nonpublic class for storing a singly linked node""" 6 | __slots__ = "_element", "_next" 7 | 8 | def __init__(self, element, next): 9 | self._element = element 10 | self._next = next 11 | 12 | def __init__(self): 13 | self._head = None 14 | self._size = 0 15 | 16 | def __len__(self): 17 | return self._size 18 | 19 | def is_empty(self): 20 | return self._size == 0 21 | 22 | def push(self, e): 23 | """Add element e to the top of the stack""" 24 | self._head = self._Node(e, self._head) 25 | self._size += 1 26 | 27 | def top(self): 28 | """Return the element at top of stack. Raise Empty Exception if satck empty""" 29 | if self.is_empty(): 30 | raise Empty('Stack is empty') 31 | return self._head._element 32 | 33 | def pop(self): 34 | """Remove and return the element from teh top of the stack (i.e. LIFO)""" 35 | if self.is_empty(): 36 | raise Empty('Stack is empty') 37 | elem = self._head._element 38 | self._head = self._head._next 39 | self._size -= 1 40 | return elem 41 | 42 | 43 | class LinkedQueue: 44 | """FIFO Queue implementation using a singly linked list for storage""" 45 | class _Node: 46 | def __init__(self, element, next): 47 | self._element = element 48 | self._next = next 49 | 50 | def __init__(self): 51 | self._head = None 52 | self._tail = None 53 | self._size = 0 54 | 55 | def __len__(slef): 56 | return self._size 57 | 58 | def is_empty(self): 59 | return self._size == 0 60 | 61 | def first(self): 62 | if self.is_empty(): 63 | raise Empty('Queue is empty') 64 | return self._head._element 65 | 66 | def dequeue(self): 67 | if self.is_empty(): 68 | raise Empty('Queue is empty') 69 | elem = self.first() 70 | self._size -=1 71 | self._head = self._head._next 72 | if self.is_empty(): 73 | self._tail = None 74 | return elem 75 | 76 | def enqueue(self, e): 77 | newest = self._Node(e, None) 78 | if self.is_empty(): 79 | self._head = newest 80 | else: 81 | self._tail._next = newest 82 | self._tail = newest 83 | self._size += 1 84 | 85 | 86 | 87 | 88 | class CircularQueue: 89 | class _Node: 90 | def __init__(self, element, next): 91 | self._element = element 92 | self._next = next 93 | 94 | def __init__(self): 95 | self._tail = None 96 | self._size = 0 97 | 98 | def __len__(self): 99 | return self._size 100 | 101 | def is_empty(self): 102 | return self._size == 0 103 | 104 | def first(self): 105 | if self.is_empty(): 106 | raise Empty('Queue is empty') 107 | head = self._tail._next 108 | return head._element 109 | 110 | def dequeue(self): 111 | if self.is_empty(): 112 | raise Empty('Queue is empty') 113 | oldhead = self._tail._next 114 | if self._size == 1: 115 | self._tail = None 116 | else: 117 | self._tail._next = oldhead._next 118 | self._size -= 1 119 | return oldhead._element 120 | 121 | def rotate(self): 122 | if self._size > 0: 123 | self._tail = self._tail._next 124 | 125 | 126 | class _DoublyLinkedBase: 127 | class _Node: 128 | __slots__ = '_element', '_prev', '_next' 129 | 130 | def __init__(self, element, prev, next): 131 | self._element = element 132 | self._prev = prev 133 | self._next = next 134 | 135 | def __init__(self): 136 | self._header = self._Node(None, None, None) 137 | self._trailer = self._Node(None, None, None) 138 | self._header._next = self._trailer 139 | self._trailer._prev = self._header 140 | self._size = 0 141 | 142 | def __len__(self): 143 | return self._size 144 | 145 | def is_empty(self): 146 | return self._size == 0 147 | 148 | def _insert_between(self, e, predecessor, successor): 149 | newest = self._Node(e, predecessor, successor) 150 | predecessor._next = newest 151 | successor._prev = newest 152 | self._size += 1 153 | return newest 154 | 155 | def _delete_node(self, node): 156 | predecessor = node._prev 157 | successor = node._next 158 | predecessor._next = successor 159 | successor._prev = predecessor 160 | self._size -=1 161 | element = node._element 162 | node._prev = node._next = node._element = None #deprecate the node 163 | return element 164 | 165 | 166 | class LinkedDeque(_DoublyLinkedBase): 167 | 168 | def first(self): 169 | if self.is_empty(): 170 | raise Empty('Dequeue is empty.') 171 | return self._header._next._element 172 | 173 | def last(self): 174 | if self.is_empty(): 175 | raise Empty('Dequeue is empty') 176 | return self._trailer._prev._element 177 | 178 | def insert_first(self, e): 179 | self._insert_between(e, self._header, self._header._next) 180 | 181 | def insert_last(self, e): 182 | self._insert_between(e, self._trailer._prev, self._trailer) 183 | 184 | def delete_first(self): 185 | if self.is_empty(): 186 | raise Empty('Dequeue is empty') 187 | return self._delete_node(self._header._next) 188 | 189 | def delete_last(self): 190 | if self.is_empty(): 191 | raise Empty('Dequeue is empty') 192 | return self._delete_node(self._trailer._prev) 193 | 194 | 195 | class PositionalList(_DoublyLinkedBase): 196 | """A sequential container of elements allowing positional access""" 197 | class Position: 198 | def __init__(self, container, node): 199 | self._container = container 200 | self._node = node 201 | 202 | def element(self): 203 | return self._node._element 204 | 205 | def __eq__(self, other): 206 | return type(other) is type(self) and other._node is self._node 207 | 208 | def __ne__(self, other): 209 | return not (self == other) 210 | 211 | def _validate(self, p): 212 | if not isinstance(p, self.Position): 213 | raise TypeError('p must be a proper Position type') 214 | if p._container is not self: 215 | raise ValueError('p does not belong to this container') 216 | if p._node._next is None: 217 | raise ValueError('p is no longer valid') 218 | return p._node 219 | 220 | def _make_position(self, node): 221 | if node is self._header or node is self._trailer: 222 | return None 223 | else: 224 | return self.Position(self, node) 225 | 226 | def first(self): 227 | return self._make_position(self._header._next) 228 | 229 | def last(self): 230 | return self._make_position(self._trailer._prev) 231 | 232 | def before(self, p): 233 | node = self._validate(p) 234 | return self._make_position(node._prev) 235 | 236 | def after(self, p): 237 | node = self._validate(p) 238 | return self._make_position(node._next) 239 | 240 | def __iter__(self): 241 | cursor = self.first() 242 | while cursor is not None: 243 | yield cursor.element() 244 | cursor = self.after(cursor) 245 | 246 | def _insert_between(self, e, predecessor, successor): 247 | node = super()._insert_between(e, predecessor, successor) 248 | return self._make_position(node) 249 | 250 | def add_first(self, e): 251 | return self._insert_between(e, self._header, self._header._next) 252 | 253 | def add_last(self, e): 254 | return self._insert_between(e, self._trailer._prev, self._trailer) 255 | 256 | def add_before(self, p, e): 257 | original = self._validate(p) 258 | return self._insert_between(e,original._prev, original) 259 | 260 | def add_later(self, p, e): 261 | original = self._validate(p) 262 | return self._insert_between(e, original, original._next) 263 | 264 | def delete(self, p): 265 | original = self._validate(p) 266 | return self._delete_node(original) 267 | 268 | def replace(self, p, e): 269 | original = self._validate(p) 270 | old_value = original._element 271 | original._element = e 272 | return old_value 273 | 274 | # L = PositionalList(). 275 | 276 | def insertion_sort(L): 277 | if len(L) > 1: 278 | marker = L.first() 279 | while marker != L.last(): 280 | pivot = L.after(marker) 281 | value = pivot.element() 282 | if value > marker.element(): 283 | marker = pivot 284 | else: 285 | walk = marker 286 | while walk != L.first() and L.before(walk).element() > value: 287 | walk = L.before(walk) 288 | L.delete(pivot) 289 | L.add_before(walk, value) #reinsert value before walk 290 | 291 | 292 | class FavoritesList: 293 | """List of elements ordered from most frequently accessed to least""" 294 | class _Item: 295 | __slots__ = '_value', '_count' 296 | def __init__(self, e): 297 | self._value = e 298 | self._count = 0 299 | 300 | def _find_position(self, e): 301 | """Search for element e and return its Position (or None if not found)""" 302 | walk = self._data.first() 303 | while walk is not None and walk.element()._value != e: 304 | walk = self._data.after(walk) 305 | return walk 306 | 307 | def _move_up(self, p): 308 | """Move item at Position p earlier in the list based on access count.""" 309 | if p != self._data.first(): 310 | cnt = p.element()._count 311 | walk = self._data.before(p) 312 | if cnt > walk.element()._count: 313 | while (walk != self._data.first() and cnt > self._data.before(walk).element()._count): 314 | walk = self._data.before(walk) 315 | self._data.add_before(walk, self._data.delete(p)) 316 | 317 | def __init__(self): 318 | self._data = PositionalList() 319 | 320 | def __len__(self): 321 | return len(self._data) 322 | 323 | def is_empty(self): 324 | return len(self._data == 0) 325 | 326 | def access(self, e): 327 | p = self._find_position(e) 328 | if p is None: 329 | p = self._data.add_last(self._Item(e)) 330 | p.element()._count += 1 331 | self._move_up(p) 332 | 333 | def remove(self, e): 334 | p = self._find_position(e) 335 | if p is not None: 336 | self._data.delete(p) 337 | 338 | def top(self, k): 339 | if not 1<=k<= len(self): 340 | raise ValueError('Illegal value for k') 341 | walk = self._data.first() 342 | for j in range(k): 343 | item = walk.element() 344 | yield item._value 345 | walk = self._data.after(walk) 346 | 347 | from pdb import set_trace 348 | 349 | class FavoritesListMTF(FavoritesList): 350 | """list of elements ordered with move-to-front heuristic""" 351 | 352 | def _move_up(self, p): 353 | if p != self._data.first(): 354 | self._data.add_first(self._data.delete(p)) 355 | 356 | def top(self, k): 357 | if not 1<=k<=len(self): 358 | raise ValueError('Illegal value for k') 359 | 360 | temp = PoisitionalList() 361 | for item in self._data: 362 | temp.add_last(item) 363 | 364 | for j in range(k): 365 | highPos = temp.first() 366 | walk = temp.after(highPos) 367 | while walk is not None: 368 | if walk.element()._count > highPos.element()._count: 369 | highPos = walk 370 | walk = temp.after(walk) 371 | yield highPos.element()._value 372 | temp.delete(highPos) 373 | 374 | 375 | # L = PositionalList() 376 | # char = 'e' 377 | 378 | # R-7.1 Give an algorithm for finding the second-to-last node in a singly linked 379 | # list in which the last node is indicated by a next reference of None. 380 | 381 | 382 | 383 | # R-7.2 Describe a good algorithm for concatenating two singly linked lists L and 384 | # M, given only references to the first node of each list, into a single list L  385 | # that contains all the nodes of L followed by all the nodes of M. 386 | # R-7.3 Describe a recursive algorithm that counts the number of nodes in a singly 387 | # linked list. 388 | 389 | # S = LinkedStack() 390 | # for i in range(5): 391 | # S.push(i) 392 | 393 | def count_nodes(S): 394 | if len(S) != 0: 395 | S.pop() 396 | return 1 + count_nodes(S) 397 | else: 398 | return 0 399 | # print(count_nodes(S)) 400 | 401 | 402 | # R-7.5 Implement a function that counts the number of nodes in a circularly 403 | # linked list. 404 | class CircularLinkList: 405 | class _Node: 406 | def __init__(self, element, next): 407 | self._element = element 408 | self._next = next 409 | 410 | def __init__(self): 411 | # self._header = self._Node(None, None, None) 412 | self._tail= None 413 | # self._header._next = self._trailer 414 | 415 | def add_element(self, e): 416 | newest = self._Node(e, None) 417 | if self._tail == None: 418 | newest._next = newest 419 | # set_trace() 420 | else: 421 | newest._next = self._tail._next 422 | self._tail._next = newest 423 | # set_trace() 424 | # set_trace() 425 | self._tail = newest 426 | 427 | def first(self): 428 | head = self._tail._next 429 | return head._element 430 | 431 | def rotate(self): 432 | # set_trace() 433 | self._tail = self._tail._next 434 | 435 | # L = CircularLinkList() 436 | # L.add_element(3) 437 | # L.add_element(5) 438 | # print(L.first()) 439 | # L.rotate() 440 | # print(L.first()) 441 | # L.rotate() 442 | # print(L.first()) 443 | # L.rotate() 444 | # print(L.first()) 445 | 446 | # def count(L, stack): 447 | # set_trace() 448 | # if L.first() in stack: 449 | # return 0 450 | # else: 451 | # stack.add(L.first()) 452 | # L.rotate() 453 | # return 1 + count(L, stack) 454 | 455 | # print(count(L, set())) 456 | 457 | 458 | # R-7.9 Give a fast algorithm for concatenating two doubly linked lists L and M, 459 | # with header and trailer sentinel nodes, into a single list L' . 460 | class SimpleDoublyLinkedList(_DoublyLinkedBase): 461 | def __init__(self): 462 | super().__init__() 463 | 464 | def add_element(self, e): 465 | self._insert_between(e, self._trailer._prev, self._trailer) 466 | 467 | def first(self): 468 | return self._header._next._element 469 | 470 | def last(self): 471 | return self._trailer._prev._element 472 | 473 | def delete_first(self): 474 | return self._delete_node(self._header._next) 475 | 476 | 477 | # L = SimpleDoublyLinkedList() 478 | # M = SimpleDoublyLinkedList() 479 | 480 | # for i in range(5): 481 | # L.add_element(i) 482 | 483 | # for i in range(5, 10): 484 | # M.add_element(i) 485 | 486 | # print(L.first()) 487 | # print(M.last()) 488 | 489 | def cat_and_reduce(L, M): 490 | """ 491 | inputs: L, M both SimpleDoublyLinkedList types 492 | output: concatenation of L and M as a single list 493 | """ 494 | while len(M) != 0: 495 | L.add_element(M.delete_first()) 496 | 497 | return L 498 | 499 | # print(len(cat_and_reduce(L, M)), cat_and_reduce(L, M).first(), cat_and_reduce(L, M).last()) 500 | 501 | 502 | # R-7.19 Suppose that we have made k*n total accesses to the elements in a list L of 503 | # n elements, for some integer k ≥ 1. What are the minimum and maximum 504 | # number of elements that have been accessed fewer than k times? 505 | 506 | # min is 0 (one node gets accessed k*n times, hence no other node gets accessed less than k times), 507 | # max is n-1 (second highest number to k is k - 1; if n-1 nodes get accessed k-1 times, and 508 | # and 1 node gets accessed n+k-1 times) 509 | 510 | 511 | # 7.42, C-7.42 Write a Scoreboard class that maintains the top 10 scores for a game ap- 512 | #plication using a singly linked list, rather than the array that was used in 513 | #Section 5.5.1. 514 | class GameEntry: 515 | """Represents one entry of a list of high scores""" 516 | def __init__(self, name, score): 517 | self._name = name 518 | self._score = score 519 | 520 | def get_name(self): 521 | return self._name 522 | 523 | def get_score(self): 524 | return self._score 525 | 526 | def __str__(self): 527 | return f'({self._name}, {self._score})' 528 | 529 | class ScoreBoard: 530 | """Fixed-length sequence of high scores in nondecreasing order""" 531 | def __init__(self, capacity=10): 532 | self._board = [None]*capacity 533 | self._n = 0 534 | 535 | def __getitem__(self, k): 536 | return self._board[k] 537 | 538 | def __str__(self): 539 | return '\n'.join(str(self._board[j]) for j in range(self._n)) 540 | 541 | def add(self, entry): 542 | score = entry.get_score() 543 | good = self._n < len(self._board) or score > self._board[-1].get_score() 544 | 545 | if good: 546 | if self._n < len(self._board): 547 | self._n += 1 548 | 549 | j = self._n - 1 550 | while j > 0 and self._board[j-1].get_score() < score: 551 | self._board[j] = self._board[j-1] 552 | j-=1 553 | self._board[j] = entry 554 | 555 | 556 | class newScoreBoard(LinkedStack): 557 | def __init__(self): 558 | super().__init__() 559 | self._min = GameEntry('', float("-inf")) 560 | 561 | def add_entry(self, val): 562 | last_visited = [] 563 | score = val.get_score() 564 | 565 | if self._size == 0: 566 | self._head = self._Node(val, self._head) 567 | self._size += 1 568 | elif self._size < 10: 569 | self._size += 1 570 | temp_head = self._head 571 | while True: 572 | if temp_head._element.get_score() > score: 573 | last_visited.append(temp_head) 574 | if temp_head._next: 575 | temp_head = temp_head._next 576 | else: 577 | temp_head._next = self._Node(val, temp_head) 578 | break 579 | elif len(last_visited) != 0: 580 | left_node = last_visited.pop() 581 | left_node._next = self._Node(val, temp_head) 582 | break 583 | else: 584 | self._head = self._Node(val, temp_head) 585 | break 586 | 587 | elif self._size == 10: 588 | temp_head = self._head 589 | while True: 590 | if score < self._min.get_score(): 591 | break 592 | elif temp_head._element.get_score() >= score: 593 | last_visited.append(temp_head) 594 | if temp_head._next: 595 | temp_head = temp_head._next 596 | else: 597 | break 598 | else: 599 | left_node = last_visited.pop() 600 | new_node = self._Node(val, temp_head) 601 | left_node._next = new_node 602 | new_node._next = temp_head 603 | break 604 | self._min = val 605 | 606 | 607 | game_entries = [] 608 | 609 | 610 | for char in 'elizabthklshn': 611 | game_entries.append(GameEntry(char, ord(char))) 612 | 613 | # for i in game_entries: 614 | # print(i.get_score()) 615 | # print(i.get_name()) 616 | 617 | 618 | # score_board = newScoreBoard() 619 | 620 | # for i in game_entries: 621 | # score_board.add_entry(i) 622 | 623 | # # set_trace() 624 | # i = 0 625 | # while i < len(score_board): 626 | # print(score_board._head._element.get_score()) 627 | # score_board._head = score_board._head._next 628 | # i += 1 629 | 630 | # for i in game_entries: 631 | # print(i.get_score()) 632 | 633 | 634 | # P.45 An array A is sparse if most of its entries are empty (i.e., None). A list 635 | # L can be used to implement such an array efficiently. In particular, for 636 | # each nonempty cell A[i], we can store an entry (i, e) in L, where e is the 637 | # element stored at A[i]. This approach allows us to represent A using O(m) 638 | # storage, where m is the number of nonempty entries in A. Provide such 639 | # a SparseArray class that minimally supports methods getitem (j) and 640 | # setitem (j, e) to provide standard indexing operations. Analyze the 641 | # efficiency of these methods. 642 | class SparseArray: 643 | def __init__(self, A): 644 | self._orig = A 645 | self._storage = {} 646 | for idx, elem in enumerate(A): 647 | if elem != None: 648 | self._storage[idx] = elem 649 | 650 | def __len__(self): 651 | return len(self._storage) 652 | 653 | def __getitem__(j): 654 | return self._storage[j] 655 | 656 | def __setitem__(j, e): 657 | self._storage[j] = e 658 | 659 | 660 | # sparse = [None]*1000 + [1] + [None]*1000 + [2] 661 | 662 | # new_sparse = SparseArray(sparse) 663 | 664 | # print(len(new_sparse)) 665 | 666 | # set_trace() 667 | 668 | # P-7.47 Implement a CardHand class that supports a person arranging a group of 669 | # cards in his or her hand. The simulator should represent the sequence of 670 | # cards using a single positional list ADT so that cards of the same suit are 671 | # kept together. Implement this strategy by means of four “fingers” into the 672 | # hand, one for each of the suits of hearts, clubs, spades, and diamonds, 673 | # so that adding a new card to the person’s hand or playing a correct card 674 | # from the hand can be done in constant time. The class should support the 675 | # following methods: 676 | # • add_card(r, s): Add a new card with rank r and suit s to the hand. 677 | # • play(s): Remove and return a card of suit s from the player’s hand; 678 | # if there is no card of suit s, then remove and return an arbitrary card 679 | # from the hand. 680 | # •__iter__( ): Iterate through all cards currently in the hand. 681 | # • all_of_suit(s): Iterate through all cards of suit s that are currently in 682 | # the hand. 683 | # suits = ['X', 'H', 'D', 'C'] 684 | # cards = [] 685 | # for s in suits: 686 | # for i in range(1, 14): 687 | # cards.append(s+ str(i)) 688 | 689 | # import random 690 | 691 | # random.shuffle(cards) 692 | 693 | # print(cards) 694 | 695 | 696 | # class CardHand: 697 | # DECK_OF_CARDS = cards 698 | # def __init__(self): 699 | # for i in range(4): 700 | 701 | # # x = CardHand() 702 | 703 | # Pos = PositionalList() 704 | 705 | # print(Pos.add_first('a')) 706 | # set_trace() 707 | 708 | class CardHand(PositionalList): 709 | def __init__(self): 710 | super().__init__() 711 | self._heart = self._Node(None, None, None) 712 | self._club = self._Node(None, None, None) 713 | self._spade = self._Node(None, None, None) 714 | self._diamond = self._Node(None, None, None) 715 | self._header._next = self._heart 716 | self._heart._next = self._spade 717 | self._spade._prev= self._heart 718 | self._spade._next = self._diamond 719 | self._diamond._prev = self._spade 720 | self._diamond._next = self._trailer 721 | self._trailer._prev = self._diamond 722 | self._heart_size = 0 723 | self._club_size = 0 724 | self._spade_size = 0 725 | self._diamond_size = 0 726 | 727 | def all_of_suit(self, s): 728 | if (s == 'heart') and (self._heart_size >0): 729 | cursor = self._heart._next 730 | while cursor._element is not None: 731 | yield cursor._element 732 | cursor = cursor._next 733 | if (s == 'club') and (self._club_size >0): 734 | cursor = self._club._next 735 | while cursor._element is not None: 736 | yield cursor._element 737 | cursor = cursor._next 738 | if (s == 'spade') and (self._spade_size >0): 739 | cursor = self._spade._next 740 | while cursor._element is not None: 741 | yield cursor._element 742 | cursor = cursor._next 743 | if (s == 'diamond') and (self._diamond >0): 744 | cursor = self._diamond._next 745 | while cursor._element is not None: 746 | yield cursor._element 747 | cursor = cursor._next 748 | 749 | def add_card(self, r, s): 750 | if s=='heart': 751 | p=self._heart._next 752 | while (p != None) and (p._element!=None) and (r>p._element): 753 | p = p._next 754 | self._insert_between(r, p._prev, p) 755 | self._heart_size +=1 756 | elif s=='club': 757 | p=self._club._next 758 | while (p != None) and (p._element!=None) and (r>p._element): 759 | p = p._next 760 | self._insert_between(r, p._prev, p) 761 | self._club_size +=1 762 | elif s=='spade': 763 | p=self._spade._next 764 | while (p != None) and (p._element!=None) and (r>p._element): 765 | p = p._next 766 | self._insert_between(r, p._prev, p) 767 | self._spade_size +=1 768 | elif s=='diamond': 769 | p=self._diamond._next 770 | while (p != None) and (p._element!=None) and (r>p._element): 771 | p = p._next 772 | self._insert_between(r, p._prev, p) 773 | self._diamond_size +=1 774 | 775 | def autoplay(self): 776 | if self.is_empty(): 777 | return 0 778 | elif self._heart_size: 779 | self._heart_size -=1 780 | return self._delete_node(self._heart._next) 781 | elif self._club_size: 782 | self._club_size -=1 783 | return self._delete_node(self._club._next) 784 | elif self._spade_size: 785 | self._spade_size -=1 786 | return self._delete_node(self._spade._next) 787 | elif self._diamond_size: 788 | self._diamond_size -=1 789 | return self._delete_node(self._diamond._next) 790 | 791 | 792 | def play(self, s): 793 | if self.is_empty(): 794 | return 0 795 | elif s == 'heart': 796 | if self._heart_size ==0: 797 | return self._autoplay() 798 | else: 799 | self._heart_size -=1 800 | return self._delete_node(self._heart._next) 801 | elif s == 'club': 802 | if self._club_size ==0: 803 | return self._autoplay() 804 | else: 805 | self._club_size -=1 806 | return self._delete_node(self._club._next) 807 | elif s == 'spade': 808 | if self._spade_size ==0: 809 | return self._autoplay() 810 | else: 811 | self._heart_size -=1 812 | return self._delete_node(self._spade._next) 813 | elif s == 'diamond': 814 | if self._diamond_size ==0: 815 | return self._autoplay() 816 | else: 817 | self._dimaond_size -=1 818 | return self._delete_node(self._diamond._next) 819 | 820 | L = CardHand() 821 | -------------------------------------------------------------------------------- /object_oriented_programming.py: -------------------------------------------------------------------------------- 1 | from pdb import set_trace 2 | 3 | 4 | class CreditCard: 5 | 6 | def __init__(self, customer, bank, acnt, limit, balance = 0): 7 | """ 8 | Create a new credit card instance. 9 | The initial balance is zero. 10 | """ 11 | self._customer = customer 12 | self._bank = bank 13 | self._account = acnt 14 | self._limit = limit 15 | self._balance = balance 16 | self._charge_count = 0 17 | 18 | def get_customer(self): 19 | """Return the name of the customer""" 20 | return self._customer 21 | 22 | def get_bank(self): 23 | """Return the bank's name""" 24 | return self._bank 25 | 26 | def get_account(self): 27 | """Return the card identifying number (typically a str)""" 28 | return self._account 29 | 30 | def get_limit(self): 31 | """Return current credit limit""" 32 | return self._limit 33 | 34 | def get_balance(self): 35 | """Return current balance""" 36 | return self._balance 37 | 38 | def charge(self, price): 39 | """ 40 | Charge given price to the card, assuming sufficient credit limit. 41 | 42 | Return True if charge was processed, False if charge was denied 43 | """ 44 | if not isinstance(price, (float, int)): 45 | raise ValueError('price must be a number.') 46 | if price + self._balance > self._limit: 47 | print(f"Credit card with account number {self._account} denied. Accrued balance over limit.") 48 | return False 49 | else: 50 | self._balance += price 51 | self._charge_count += 1 52 | if self._charge_count > 10: 53 | self._balance += 1 #$1 surcharge for all calls after 10 charges 54 | return True 55 | 56 | def make_payment(self, amount): 57 | if not isinstance(amount, (int, float)): 58 | raise ValueError('payment must be a number') 59 | elif amount < 0: 60 | raise ValueError('payment must be positive') 61 | """Process customer payment that reduces the balance""" 62 | self._balance -= amount 63 | 64 | from datetime import datetime 65 | 66 | class PredatoryCreditCard(CreditCard): 67 | """An extension to CredicCard that compounds interest and fees""" 68 | def __init__(self, customer, bank, acnt, limit, apr): 69 | """Create a new predatory credit cart instance.""" 70 | 71 | super().__init__(customer, bank, acnt, limit) #call super constructor 72 | self._apr = apr 73 | self._cycle = 0 74 | self._minpay = 0 75 | self._late_fee = False 76 | self._override_limit = False 77 | 78 | def _set_balance(self, b): 79 | self._balance = b 80 | 81 | def charge(self, price): 82 | """Charge given price to the card, assuming sufficient credit limit. 83 | Return True if charge was processed 84 | Return False and assess $5 fee if charge is denied""" 85 | 86 | success = super().charge(price) #call inherited method 87 | if not success: 88 | self._balance += 5 #casses penalty 89 | return success 90 | 91 | def get_balance(self): 92 | return round(self._balance, 2) 93 | 94 | def process_month(self): 95 | """Asses monthly interest on outstanding balance""" 96 | fee = 10 97 | if self._late_fee: 98 | success = self.charge(fee) 99 | self._cycle += 1 100 | prev_month = self._balance 101 | self._minpay = round(.4*prev_month, 2) 102 | if self._balance > 0: 103 | if self._balance >= self._limit: 104 | self._override_limit = True 105 | monthly_factor = pow(1 + self._apr, 1/12) 106 | self._set_balance(self._balance*monthly_factor) 107 | # set_trace() 108 | 109 | def make_payment(self, amount): 110 | super().make_payment(amount) 111 | if self._minpay > 0: 112 | self._late_fee = True 113 | self._minpay -= amount 114 | if self._minpay >= 0: 115 | self._late_fee = False 116 | else: 117 | self._late_fee = False 118 | 119 | 120 | 121 | 122 | 123 | def get_apr(self): 124 | return str(100*self._apr) + '%' 125 | 126 | # c = CreditCard('liz', 'bofa', '123 123 123 123', 2500) 127 | # print(c.get_balance()) 128 | # c = CreditCard('liz', 'bofa', 'xxxx xxxx xxxx 1234', 2500, 553.20) 129 | # print(c.get_balance()) 130 | 131 | 132 | # if __name__ == '__main__': 133 | # wallet = [] 134 | # wallet.append(PredatoryCreditCard('John Bowman', 'California Savings', '5391 0375 9387 5309', 2500, .195)) 135 | # wallet.append(PredatoryCreditCard('John Bowman', 'California Federal', '3485 1235 1235 1522', 3500, .20)) 136 | # wallet.append(PredatoryCreditCard('John Bowman', 'California Finance', '1524 5107 3587 2357', 5000, .21)) 137 | 138 | # for val in range(1, 30): 139 | # wallet[0].charge(val) 140 | # wallet[1].charge(2*val) 141 | # wallet[2].charge(3*val) 142 | # wallet[0].process_month() 143 | # wallet[1].process_month() 144 | # wallet[2].process_month() 145 | 146 | # for c in range(3): 147 | # print('Customer = ', wallet[c].get_customer()) 148 | # print('Bank = ', wallet[c].get_bank()) 149 | # print('Account = ', wallet[c].get_account()) 150 | # print('Limit = ', wallet[c].get_limit()) 151 | # print('Balance = ', wallet[c].get_balance()) 152 | # print('APR = ', wallet[c].get_apr()) 153 | # while wallet[c].get_balance()>100: 154 | # wallet[c].make_payment(100) 155 | # print('New balance = ', wallet[c].get_balance()) 156 | # print() 157 | # if __name__ == '__main__': 158 | # wallet = [] 159 | # wallet.append(CreditCard('John Bowman', 'California Savings', '5391 0375 9387 5309', 2500)) 160 | # wallet.append(CreditCard('John Bowman', 'California Federal', '3485 1235 1235 1522', 3500)) 161 | # wallet.append(CreditCard('John Bowman', 'California Finance', '1524 5107 3587 2357', 5000)) 162 | 163 | # for val in range(1, 25): 164 | # wallet[0].charge(val*val) 165 | # wallet[1].charge(val*val) 166 | # wallet[2].charge(val*val) 167 | 168 | # for c in range(3): 169 | # print('Customer = ', wallet[c].get_customer()) 170 | # print('Bank = ', wallet[c].get_bank()) 171 | # print('Account = ', wallet[c].get_account()) 172 | # print('Limit = ', wallet[c].get_limit()) 173 | # print('Balance = ', wallet[c].get_balance()) 174 | # # while wallet[c].get_balance()>100: 175 | # # wallet[c].make_payment(100) 176 | # # print('New balance = ', wallet[c].get_balance()) 177 | # print() 178 | 179 | from copy import deepcopy 180 | 181 | class Vector: 182 | """Represent a vector in multidimensional space""" 183 | 184 | def __init__(self,data): 185 | """Create d-dimensional vector of zeros""" 186 | is_list = False 187 | 188 | try: 189 | for i in data: 190 | break 191 | is_list = True 192 | except: 193 | is_list = False 194 | 195 | if is_list: 196 | copy_data = deepcopy(list(data)) 197 | for i in copy_data: 198 | if not isinstance(i, (int, float)): 199 | raise TypeError('Iterable must contain sequence of numbers only.') 200 | self._coords = copy_data 201 | elif isinstance(data,int): 202 | self._coords = [0] * data 203 | else: 204 | raise TypeError('Arg passed must be an iterable of numbers or int') 205 | 206 | def __len__(self): 207 | """return the dimension of the vector""" 208 | return len(self._coords) 209 | 210 | def __getitem__(self, j): 211 | """return the jth coordinate of vector""" 212 | return self._coords[j] 213 | 214 | def __setitem__(self,j,val): 215 | """Set jth coordinate of vector to given value""" 216 | self._coords[j] = val 217 | 218 | def __add__(self, other): 219 | """return sum of two vectors""" 220 | if len(self) != len(other): #relies on __len__method 221 | raise ValueError('dimensions must agree') 222 | result = Vector(len(self)) 223 | for j in range(len(self)): 224 | result[j] = self[j] + other[j] 225 | return result 226 | 227 | def __radd__(self, other): 228 | """To ensure that the sum of other + vector will work, i.e. if other 229 | is on the right side.""" 230 | return self + other 231 | 232 | def __mul__(self, other): 233 | """Return product of two vectors""" 234 | if not isinstance(other, (int, float, Vector, list)): 235 | raise ValueError('factor must be a number or vector') 236 | if isinstance(other, (int, float)): 237 | product = Vector(len(self)) 238 | for j in range(len(self)): 239 | product[j] = self[j]*other 240 | return product 241 | elif len(self) != len(other): 242 | raise ValueError('dimensions must agree') 243 | else: 244 | dot_product = 0 245 | for i in range(len(self)): 246 | dot_product += self[i] * other[i] 247 | return dot_product 248 | 249 | def __rmul__(self, other): 250 | """To ensure that the product of scalar * vector will work, i.e. if scalar 251 | is on the right side.""" 252 | return self * other 253 | 254 | def __neg__(self): 255 | """return the negation of vector""" 256 | return self * -1 257 | 258 | def __sub__(self, other): 259 | """return difference of two vectors""" 260 | other = -other 261 | return self + other 262 | 263 | def __eq__(self, other): 264 | """Return True if vector has same coordinates as other""" 265 | return self._coords == other._coords 266 | 267 | def __ne__(self, other): 268 | """Return True if vectors differ from each other""" 269 | return not self == other #relies on existing __eq__method 270 | 271 | def __str__(self): 272 | """Produce string representation of vector.""" 273 | return '<' + str(self._coords)[1:-1] + '>' #adapt list representation 274 | 275 | if __name__ == '__main__': 276 | # x = Vector(3) 277 | # x[0] = 1 278 | # x[1] = 2 279 | # x[2] = 3 280 | # y = Vector(3) 281 | # y[0] = 4 282 | # y[1] = 8 283 | # y[2] = 10 284 | 285 | # z= 3*x 286 | # z = x * 2 287 | z = Vector([1, 2,3]) 288 | y = Vector(4) 289 | 290 | # print(y) #why does z have the <> notation printed on screen? 291 | # print(-z) 292 | # print(str(z)) 293 | 294 | 295 | class SequenceIterator: 296 | """An Iterator for any of python's sequence types""" 297 | def __init__(self, sequence): 298 | self._seq = sequence 299 | self._k = -1 #will increment to 0 on first call to next 300 | 301 | def __next__(self): 302 | """Return the next element, or else raise StopIteration error.""" 303 | self._k += 1 304 | if self._k < len(self._seq): 305 | return (self._seq[self._k]) 306 | else: 307 | raise StopIteration() 308 | 309 | def __iter__(self): 310 | """By convention, an iterator must return itself as an iterator""" 311 | return self 312 | 313 | class ReversedSequenceIterator: 314 | def __init__(self, sequence): 315 | seq_copy = deepcopy(list(sequence)) 316 | self._seq = sequence 317 | self._k = len(sequence) 318 | 319 | def __next__(self): 320 | self._k -= 1 321 | if self._k > -1: 322 | return (self._seq[self._k]) 323 | else: 324 | raise StopIteration() 325 | 326 | def __iter__(self): 327 | return self 328 | 329 | 330 | r = ReversedSequenceIterator([1,2,3,4]) 331 | 332 | # for i in range(len(r._seq)): 333 | # print(next(r)) 334 | # if __name__ == "__main__": 335 | # # y = SequenceIterator([3,2,4,5]) 336 | # # a = next(y) 337 | # # print(y) 338 | 339 | # # while True: 340 | # # print(a) 341 | # # a = next(y) 342 | # r = range(8, 140, 5) 343 | # print(len(r)) 344 | # print(next(r)) 345 | 346 | class Range: 347 | """A class that mimics the built-in range class""" 348 | 349 | def __init__(self, start, stop=None, step=1): 350 | if step ==0: 351 | raise ValueError('step cannot be zero') 352 | 353 | if stop is None: #special case of range(n) 354 | start, stop = 0, start #should be treated as if range(0,n) 355 | 356 | #calculate the effective length once 357 | self._length = max(0, (stop - start + step - 1) // step) 358 | 359 | #need knowledge of start and step (but not stop) to support __getitem__ 360 | self._start = start 361 | self._step = step 362 | self._stop = stop 363 | 364 | def __len__(self): 365 | """Return number of entries in the range""" 366 | return self._length 367 | 368 | def __getitem__(self, k): 369 | """Return entry at index k (using standard interpretation if negative)""" 370 | if k < 0: 371 | k += len(self) #attempt to convert negative index 372 | 373 | if not 0<= k < self._length: 374 | raise IndexError('index out of range') 375 | # set_trace() 376 | return self._start + k*self._step 377 | 378 | def __contains__(self, k): 379 | start = self._start 380 | slope = self._step 381 | if k >= self._stop or k < start: 382 | raise IndexError('index out of range') 383 | elif (k-start)%slope == 0: 384 | return True 385 | else: 386 | return False 387 | 388 | 389 | 390 | # r = Range(10) 391 | r = Range(10, 200, 10) 392 | # print(200 in r) 393 | 394 | class Progression: 395 | """Iterator producing a generic progression. 396 | 397 | Default iterator produces the whole numbers 0, 1, 2...""" 398 | 399 | def __init__(self, start=0): 400 | """Initialize current to the first value of the progression""" 401 | self._current = start 402 | 403 | def _advance(self): 404 | """Update self._current to a new val. 405 | 406 | This should be overriden by a subclass to customize progression. 407 | 408 | By convention, if current is set to None, this designates the 409 | end of a finite progression""" 410 | 411 | self._current += 1 412 | 413 | def __next__(self): 414 | """Return the next element, or else raise StopIteration error.""" 415 | if self._current is None: #our convention to end a progression 416 | raise StopIteration() 417 | else: 418 | answer = self._current #record current value to return 419 | self._advance() #advance to prepare for next time 420 | return answer #return the answer 421 | 422 | def __iter__(self): 423 | """By convention, an iterator must return itself as an iterator""" 424 | return self 425 | 426 | def print_progression(self, n): 427 | """print next n values of the progression""" 428 | print(" ".join(str(next(self)) for i in range(n))) 429 | 430 | 431 | class SqrRt(Progression): 432 | def __init__(self, start = 65536): 433 | super().__init__(start) 434 | 435 | def _advance(self): 436 | self._current = self._current**(0.5) 437 | 438 | # s = SqrRt() 439 | # s.print_progression(4) 440 | class AbsValueDiff(Progression): 441 | """ 442 | a Python class that extends the Progression class so that each value 443 | in the progression is the absolute value of the difference between the pre- 444 | vious two values. 445 | """ 446 | def __init__(self, first = 2, second = 200): 447 | super().__init__(first) 448 | self._prev = abs(second - first) 449 | 450 | def _advance(self): 451 | self._prev, self._current = self._current, abs(self._current - self._prev) 452 | 453 | 454 | # p = Progression() 455 | # p.print_progression(10) 456 | 457 | class ArithmeticProgression(Progression): 458 | """Iterator producing an arithmetic pogression""" 459 | 460 | def __init__(self, increment = 1, start = 0): 461 | """create a new arithmetic progression 462 | increment the fixed constant to add to each term (default 1) 463 | start the first term of the progression (default 0) 464 | """ 465 | 466 | super().__init__(start) #initialize base class 467 | self._increment = increment 468 | 469 | def _advance(self): 470 | self._current += self._increment 471 | 472 | 473 | # a = ArithmeticProgression(10, 3) 474 | # a.print_progression(100) 475 | 476 | class GeometricProgression(Progression): 477 | def __init__(self, base = 2, start = 1): 478 | super().__init__(start) 479 | self._base = base 480 | 481 | def _advance(self): 482 | self._current *= self._base 483 | 484 | 485 | # g = GeometricProgression(3, 1) 486 | # g.print_progression(20) 487 | 488 | 489 | class FibonacciProgression(Progression): 490 | 491 | def __init__(self, first = 0, second = 1): 492 | 493 | super().__init__(first) 494 | self._prev = second - first 495 | 496 | def _advance(self): 497 | self._prev, self._current = self._current, self._prev + self._current 498 | 499 | # f = FibonacciProgression() 500 | # f.print_progression(20) 501 | 502 | 503 | from abc import ABCMeta, abstractmethod 504 | 505 | class Sequence(metaclass=ABCMeta): 506 | """Our own version of collections.Sequence abstract base class""" 507 | 508 | @abstractmethod 509 | def __len__(self): 510 | """return the length of the sequence""" 511 | 512 | @abstractmethod 513 | def __getitem__(self,j): 514 | """Return the element at index j of the sequence""" 515 | 516 | def __contains__(self, val): 517 | for j in range(len(self)): 518 | if self[j] == val: 519 | return True 520 | return False 521 | 522 | def __eq__(self, other): 523 | """checks if two sequences are equivalent""" 524 | if len(self) != len(other): 525 | return False 526 | for j in range(len(self)): 527 | for i in range(len(other)): 528 | if self[j] == other[i]: 529 | pass 530 | else: 531 | return False 532 | return True 533 | 534 | def __lt__(self, other): 535 | """Checks if one sequence is lexigraphically less than the other sequence""" 536 | for j in range(len(self)): 537 | if self[j] > other[j]: 538 | return False 539 | return True 540 | 541 | def index(self, val): 542 | """Return leftmost index at which val is found (or raise ValueError)""" 543 | for j in range(len(self)): 544 | if self[j] == val: 545 | return j 546 | raise ValueError('value not in sequence') 547 | 548 | def count(self, val): 549 | """Return the number of elements equal to given value""" 550 | k = 0 551 | for j in range(len(self)): 552 | if self[j] == val: 553 | k += 1 554 | return k 555 | 556 | # R-2.1 557 | """ 558 | Give three examples of life-critical software applications. 559 | 560 | 1) Self-driving cars -- if there is a software failure, it can lead to fatal accidents. 561 | 2) insulin checker -- a person who has diabetes must always track his/her insulin intake, and they rely on 562 | machines that can check their blood 563 | 3) Air traffic control with radar signalling -- software is used to process the radar signals, making it vital 564 | for air travel safety 565 | """ 566 | 567 | #R-2.2 568 | """ 569 | Give an example of a software application in which adaptability can mean 570 | the difference between a prolonged lifetime of sales and bankruptcy. 571 | 572 | Being adaptable means that the code is flexible so that it can run in different kinds of hardware and different 573 | operating systems. A company should be able to respond quickly when environment changes. 574 | """ 575 | 576 | #R-2.3 577 | """ 578 | Describe a component from a text-editor GUI and the methods that it en- 579 | capsulates. 580 | 581 | One component is language recognition so that it colors the code appropriately. 582 | its methods would include checking the file extension. If a file extension is not inserted, it will 583 | use its default color coding scheme on the text. 584 | """ 585 | 586 | #R-2.4 587 | """ 588 | Write a Python class, Flower, that has three instance variables of type str, 589 | int, and float, that respectively represent the name of the flower, its num- 590 | ber of petals, and its price. Your class must include a constructor method 591 | that initializes each variable to an appropriate value, and your class should 592 | include methods for setting the value of each type, and retrieving the value 593 | of each type. 594 | """ 595 | class Flower: 596 | def __init__(self, name, petals, price): 597 | self._name = name 598 | self._petals = petals 599 | self._price = price 600 | 601 | def get_name(self): 602 | return self._name 603 | 604 | def get_petals(self): 605 | return self._petals 606 | 607 | def get_price(self): 608 | return self._price 609 | 610 | def set_name(self, name): 611 | self._name = name 612 | 613 | def set_petals(self, petals): 614 | self._petals = petals 615 | 616 | def set_price(self, price): 617 | self._price = price 618 | 619 | # f = Flower('sunflower', 24, 1.25) 620 | # print(f.get_name()) 621 | # print(f.get_petals()) 622 | # print(f.get_price()) 623 | # f.set_name('rose') 624 | # f.set_petals(32) 625 | # f.set_price(1.45) 626 | # print(f.get_name()) 627 | # print(f.get_petals()) 628 | # print(f.get_price()) 629 | 630 | #R-2.5 631 | """ 632 | Use the techniques of Section 1.7 to revise the charge and make payment 633 | methods of the CreditCard class to ensure that the caller sends a number 634 | as a parameter. 635 | 636 | see line 43-44 and 52-53 637 | """ 638 | 639 | 640 | #R-2.6 641 | """ 642 | If the parameter to the make payment method of the CreditCard class 643 | were a negative number, that would have the effect of raising the balance 644 | on the account. Revise the implementation so that it raises a ValueError if 645 | a negative value is sent. 646 | 647 | See line 54-55 648 | """ 649 | 650 | #R-2.7 651 | """ 652 | The CreditCard class of Section 2.3 initializes the balance of a new ac- 653 | count to zero. Modify that class so that a new account can be given a 654 | nonzero balance using an optional fifth parameter to the constructor. The 655 | four-parameter constructor syntax should continue to produce an account 656 | with zero balance. 657 | 658 | See lines 6-15 659 | """ 660 | 661 | #R-2.8 662 | """ 663 | Modify the declaration of the first for loop in the CreditCard tests, from 664 | Code Fragment 2.3, so that it will eventually cause exactly one of the three 665 | credit cards to go over its credit limit. Which credit card is it? 666 | 667 | See line 46. It was the credit card from California Savings. 668 | """ 669 | 670 | #R-2.9 671 | """ 672 | Implement the sub method for the Vector class of Section 2.3.3, so 673 | that the expression u−v returns a new vector instance representing the 674 | difference between two vectors. 675 | 676 | See lines 220-223 677 | """ 678 | 679 | #R-2.10 680 | """ 681 | Implement the neg method for the Vector class of Section 2.3.3, so 682 | that the expression −v returns a new vector instance whose coordinates 683 | are all the negated values of the respective coordinates of v. 684 | 685 | See lines 216-218 686 | """ 687 | 688 | #R-2.11 689 | """ 690 | In Section 2.3.3, we note that our Vector class supports a syntax such as 691 | v = u + [5, 3, 10, −2, 1], in which the sum of a vector and list returns 692 | a new vector. However, the syntax v = [5, 3, 10, −2, 1] + u is illegal. 693 | Explain how the Vector class definition can be revised so that this syntax 694 | generates a new vector. 695 | 696 | See lines 189-192 697 | """ 698 | 699 | #R-2.12 700 | """ 701 | Implement the mul method for the Vector class of Section 2.3.3, so 702 | that the expression v 3 returns a new vector with coordinates that are 3 703 | times the respective coordinates of v. 704 | 705 | See lines 194-202 706 | """ 707 | 708 | #R-2.13 709 | """" 710 | Exercise R-2.12 asks for an implementation of mul , for the Vector 711 | class of Section 2.3.3, to provide support for the syntax v * 3. Implement 712 | the rmul method, to provide additional support for syntax 3 * v. 713 | 714 | See lines 211-214 715 | """ 716 | 717 | #R-2.14 718 | """ 719 | Implement the mul method for the Vector class of Section 2.3.3, so 720 | that the expression u v returns a scalar that represents the dot product of 721 | the vectors. 722 | 723 | See lines 205-209 724 | """ 725 | 726 | #R-2.15 727 | """ 728 | The Vector class of Section 2.3.3 provides a constructor that takes an in- 729 | teger d, and produces a d-dimensional vector with all coordinates equal to 730 | 0. Another convenient form for creating a new vector would be to send the 731 | constructor a parameter that is some iterable type representing a sequence 732 | of numbers, and to create a vector with dimension equal to the length of 733 | that sequence and coordinates equal to the sequence values. For example, 734 | Vector([4, 7, 5]) would produce a three-dimensional vector with coordi- 735 | nates <4, 7, 5>. Modify the constructor so that either of these forms is 736 | acceptable; that is, if a single integer is sent, it produces a vector of that 737 | dimension with all zeros, but if a sequence of numbers is provided, it pro- 738 | duces a vector with coordinates based on that sequence. 739 | 740 | See lines 153-166 741 | """ 742 | 743 | #R-2.16 744 | """ 745 | Our Range class, from Section 2.3.5, relies on the formula 746 | max(0, (stop − start + step − 1) // step) 747 | to compute the number of elements in the range. It is not immediately ev- 748 | ident why this formula provides the correct calculation, even if assuming 749 | a positive step size. Justify this formula, in your own words. 750 | 751 | Interpretation of denominator: 752 | You have to divide by the step as that will account for the partitioning 753 | of the range by an equal amount. You have to use floor division because 754 | the range function should only output countable numbers. 755 | Interpretation of numerator: 756 | You have to subtract the numerator by stop - start as that represents the distance. 757 | You have to subtract by 1 because the end is not inclusive. 758 | You have to also add the step value to ensure the last element will be outputted. 759 | 760 | You have to use max to ensure there'll be an output, to avoid bugs 761 | """ 762 | 763 | #R-2.18 764 | """ 765 | When using the ArithmeticProgression class of Section 2.4.2 with an in- 766 | crement of 128 and a start of 0, how many calls to next can we make 767 | before we reach an integer of 2^63 or larger? 768 | """ 769 | a = ArithmeticProgression(increment = 128) 770 | # a.print_progression(72050000000000000) 771 | #Technically, you can figure this out by computing 2^63/128, but this would take a long time to compute 772 | 773 | #R-2.19 774 | """ 775 | Give a short fragment of Python code that uses the progression classes 776 | from Section 2.4.2 to find the 8 th value of a Fibonacci progression that 777 | starts with 2 and 2 as its first two values. 778 | """ 779 | f = FibonacciProgression(first = 2, second= 2) 780 | # f.print_progression(8) 781 | #8th value is 42 782 | 783 | #R-2.20 784 | """ 785 | What are some potential efficiency disadvantages of having very deep in- 786 | heritance trees, that is, a large set of classes, A, B, C, and so on, such that 787 | B extends A, C extends B, D extends C, etc.? 788 | 789 | The higher you go in your heirarchy, the more prone you will be for unknown bugs. 790 | If you invoke an instantiation of class A and is not successful, you'd have to inspect 791 | the code going down the hierarchy, which can take lots of time. 792 | """ 793 | 794 | #R-2.21 795 | """ 796 | What are some potential efficiency disadvantages of having very shallow 797 | inheritance trees, that is, a large set of classes, A, B, C, and so on, such 798 | that all of these classes extend a single class, Z? 799 | 800 | if you have a really shallow inheritance tree, it may imply that Z is not robust enough 801 | to generate an appropriate abstraction for these 'child' trees. This means that 802 | many parts of Z will be overriden, which defeats the purpose of inheritance. 803 | """ 804 | 805 | #R-2.22 806 | """ 807 | The collections.Sequence abstract base class does not provide support for 808 | comparing two sequences to each other. Modify our Sequence class from 809 | Code Fragment 2.14 to include a definition for the eq method, so 810 | that expression seq1 == seq2 will return True precisely when the two 811 | sequences are element by element equivalent. 812 | 813 | See lines 429-439 814 | """ 815 | 816 | #R-2.23 817 | """ 818 | In similar spirit to the previous problem, augment the Sequence class with 819 | method lt , to support lexicographic comparison seq1 < seq2. 820 | 821 | see lines 441-446 822 | """ 823 | 824 | #C-2.24 825 | """ 826 | Suppose you are on the design team for a new e-book reader. What are the 827 | primary classes and methods that the Python software for your reader will 828 | need? You should include an inheritance diagram for this code, but you 829 | do not need to write any actual code. Your software architecture should 830 | at least include ways for customers to buy new books, view their list of 831 | purchased books, and read their purchased books. 832 | 833 | Book class --> get_price(), get_id(), get_author(), num_of_pages(), get_summary() 834 | Book(title, author, price, unique_id, summary, pages) 835 | Nested in Book will be a Page class --> read_text(), highlight_chars(seq_of_chars), 836 | get_page_number(), next_page(), go_to_page(n) 837 | Page(page_number, text) 838 | Customer Class --> get_account_info(), see_shelf(), read_book(title), buy_book(title) 839 | Customer(account_info, shelf) 840 | """ 841 | 842 | #C-2.25 843 | """ 844 | Exercise R-2.12 uses the __mul__ method to support multiplying a Vector 845 | by a number, while Exercise R-2.14 uses the mul method to support 846 | computing a dot product of two vectors. Give a single implementation of 847 | Vector. mul that uses run-time type checking to support both syntaxes 848 | u*v and u*k, where u and v designate vector instances and k represents 849 | a number. 850 | 851 | SEe lines 194-202 852 | """ 853 | 854 | #C-2.26 855 | 856 | """ 857 | The SequenceIterator class of Section 2.3.4 provides what is known as a 858 | forward iterator. Implement a class named ReversedSequenceIterator that 859 | serves as a reverse iterator for any Python sequence type. The first call to 860 | next should return the last element of the sequence, the second call to next 861 | should return the second-to-last element, and so forth. 862 | 863 | See lines 280-294 864 | 865 | """ 866 | 867 | #C-2.27 868 | """ 869 | In Section 2.3.5, we note that our version of the Range class has im- 870 | plicit support for iteration, due to its explicit support of both len 871 | and getitem . The class also receives implicit support of the Boolean 872 | test, “k in r” for Range r. This test is evaluated based on a forward itera- 873 | tion through the range, as evidenced by the relative quickness of the test 874 | 2 in Range(10000000) versus 9999999 in Range(10000000). Provide a 875 | more efficient implementation of the contains method to determine 876 | whether a particular value lies within a given range. The running time of 877 | your method should be independent of the length of the range. 878 | 879 | SEe lines 345-353 880 | """ 881 | 882 | #C-2.28 883 | """ 884 | The PredatoryCreditCard class of Section 2.4.1 provides a process month 885 | method that models the completion of a monthly cycle. Modify the class 886 | so that once a customer has made ten calls to charge in the current month, 887 | each additional call to that function results in an additional $1 surcharge. 888 | 889 | See lines 16 and 50-54 890 | """ 891 | 892 | #C-2.29 893 | """ 894 | Modify the PredatoryCreditCard class from Section 2.4.1 so that a cus- 895 | tomer is assigned a minimum monthly payment, as a percentage of the 896 | balance, and so that a late fee is assessed if the customer does not subse- 897 | quently pay that minimum amount before the next monthly cycle. 898 | 899 | see lines 100-210 900 | """ 901 | 902 | #c-2.30 903 | """ 904 | At the close of Section 2.4.1, we suggest a model in which the CreditCard 905 | class supports a nonpublic method, set balance(b), that could be used 906 | by subclasses to affect a change to the balance, without directly accessing 907 | the balance data member. Implement such a model, revising both the 908 | CreditCard and PredatoryCreditCard classes accordingly. 909 | 910 | see lines 194-205 911 | """ 912 | 913 | #C-2.31 914 | """ 915 | Write a Python class that extends the Progression class so that each value 916 | in the progression is the absolute value of the difference between the pre- 917 | vious two values. You should include a constructor that accepts a pair of 918 | numbers as the first two values, using 2 and 200 as the defaults. 919 | 920 | See lines 431-441 921 | """ 922 | 923 | #C-2.32 924 | """ 925 | Write a Python class that extends the Progression class so that each value 926 | in the progression is the square root of the previous value. (Note that 927 | you can no longer represent each value with an integer.) Your construc- 928 | tor should accept an optional parameter specifying the start value, using 929 | 65, 536 as a default. 930 | 931 | See lines 431-436 932 | """ 933 | 934 | #P-2.33 935 | """ 936 | Write a Python program that inputs a polynomial in standard algebraic 937 | notation and outputs the first derivative of that polynomial. 938 | """ 939 | 940 | def compare(a, b): 941 | if a < 0 and b < 0: 942 | return None 943 | elif b < 0: 944 | return a 945 | elif a < 0: 946 | return b 947 | else: 948 | return min(a,b) 949 | 950 | def find_parts(t): 951 | exp_check = t.find('^') 952 | if exp_check == -1: 953 | exp_check = t.find('x') 954 | if exp_check == -1: 955 | return '' 956 | else: 957 | return t[:exp_check] 958 | else: 959 | coeff, deg = t[:exp_check], t[exp_check+1:] 960 | coeff = coeff[:coeff.find('x')] 961 | float_check = coeff.find('.') 962 | if float_check == -1: 963 | coeff = int(coeff) 964 | else: 965 | coeff = float(coeff) 966 | deg = int(deg) 967 | return coeff, deg 968 | 969 | # def first_derivative_polynomial(polynomial): 970 | # idx = 0 971 | # pos_idx = polynomial.find('+', idx) 972 | # neg_idx = polynomial.find('-', idx) 973 | # start_loop = compare(pos_idx, neg_idx) 974 | # expr = [] 975 | 976 | # while start_loop: 977 | # term = polynomial[idx:start_loop] 978 | # derived_term = find_parts(term) 979 | # expr.append(new_coeff+'x^'+new_deg) 980 | 981 | 982 | #P-2.34 983 | """ 984 | Write a Python program that inputs a document and then outputs a bar- 985 | chart plot of the frequencies of each alphabet character that appears in 986 | that document. 987 | """ 988 | import matplotlib.pyplot as plt 989 | 990 | 991 | def char_analysis(file): 992 | f = open(file, 'rb') 993 | abc_dict = dict() 994 | for i in range(26): 995 | abc_dict[chr(ord('a')+i)] = 0 996 | for line in f: 997 | sline = line.strip(b'\n') 998 | for char in sline: 999 | l_c = chr(char).lower() 1000 | if l_c in abc_dict: 1001 | abc_dict[l_c] += 1 1002 | 1003 | plt.bar(range(len(abc_dict)), list(abc_dict.values()), align='center') 1004 | plt.xticks(range(len(abc_dict)), list(abc_dict.keys())) 1005 | plt.show() 1006 | 1007 | 1008 | 1009 | 1010 | f = 'LICENSE.txt' 1011 | # char_analysis(f) 1012 | 1013 | #P-2.35 1014 | """ 1015 | Write a set of Python classes that can simulate an Internet application in 1016 | which one party, Alice, is periodically creating a set of packets that she 1017 | wants to send to Bob. An Internet process is continually checking if Alice 1018 | has any packets to send, and if so, it delivers them to Bob’s computer, and 1019 | Bob is periodically checking if his computer has a packet from Alice, and, 1020 | if so, he reads and deletes it. 1021 | """ 1022 | 1023 | # class WorkerA: 1024 | # def __init__(self) 1025 | 1026 | #P-2.36 1027 | """ 1028 | Write a Python program to simulate an ecosystem containing two types 1029 | of creatures, bears and fish. The ecosystem consists of a river, which is 1030 | modeled as a relatively large list. Each element of the list should be a 1031 | Bear object, a Fish object, or None. In each time step, based on a random 1032 | process, each animal either attempts to move into an adjacent list location 1033 | or stay where it is. If two animals of the same type are about to collide in 1034 | the same cell, then they stay where they are, but they create a new instance 1035 | of that type of animal, which is placed in a random empty (i.e., previously 1036 | None) location in the list. If a bear and a fish collide, however, then the 1037 | fish dies (i.e., it disappears). 1038 | """ 1039 | import random 1040 | class Bear: 1041 | def __init__(self, river): 1042 | while True: 1043 | r = random.randint(0, len(river)-1) 1044 | if river[r] == None: 1045 | break 1046 | river[r] = self 1047 | self._pos = r 1048 | self._river = river 1049 | # self._right = r + 1 1050 | # self._left = r - 1 1051 | 1052 | def consequence(self, rand_act): 1053 | curr_pos = self._pos 1054 | if rand_act == 'move_right': 1055 | r_idx = curr_pos + 1 1056 | test = self._river[r_idx] 1057 | if test == None: 1058 | self._pos += 1 1059 | self._river[curr_pos] = None 1060 | self._river[r_idx] = self 1061 | elif isinstance(test, Fish): 1062 | self._pos += 1 1063 | self._river[curr_pos] = None 1064 | self._river[r_idx] = self 1065 | elif isinstance(test, Bear): 1066 | cub = Bear(self._river) 1067 | if rand_act == 'move_left': 1068 | l_idx = curr_pos - 1 1069 | test = self._river[l_idx] 1070 | if test == None: 1071 | self._pos -= 1 1072 | self._river[curr_pos] = None 1073 | self._river[l_idx] = self 1074 | elif isinstance(test, Fish): 1075 | self._pos -= 1 1076 | self._river[curr_pos] = None 1077 | self._river[l_idx] = self 1078 | print(f'{type(self)} hunts') 1079 | elif isinstance(test, Bear): 1080 | cub = Bear(self._river) 1081 | print('new cub is born!') 1082 | if rand_act == 'do_nothing': 1083 | pass 1084 | 1085 | def respond_to_request(self): 1086 | actions = {'move_right': 1, 'move_left':2, 'do_nothing': None} 1087 | rand_act = random.sample(list(actions.keys()),1)[0] 1088 | print(f'{type(self)} performed {rand_act} at {self._pos}') 1089 | result = self.consequence(rand_act) 1090 | return self._river 1091 | 1092 | 1093 | 1094 | class Fish: 1095 | def __init__(self, river): 1096 | while True: 1097 | r = random.randint(0, len(river)-1) 1098 | if river[r] == None: 1099 | break 1100 | river[r] = self 1101 | self._pos = r 1102 | self._river = river 1103 | 1104 | def consequence(self, rand_act): 1105 | curr_pos = self._pos 1106 | if rand_act == 'move_right': 1107 | r_idx = curr_pos + 1 1108 | test = self._river[r_idx] 1109 | if test == None: 1110 | self._pos += 1 1111 | self._river[curr_pos] = None 1112 | self._river[r_idx] = self 1113 | elif isinstance(test, Fish): 1114 | Fish(self._river) 1115 | print('new fish is born!') 1116 | elif isinstance(test, Bear): 1117 | self._river[curr_pos] = None 1118 | print(f'{type(self)} has died.') 1119 | 1120 | if rand_act == 'move_left': 1121 | l_idx = curr_pos - 1 1122 | test = self._river[l_idx] 1123 | if test == None: 1124 | self._pos -= 1 1125 | self._river[curr_pos] = None 1126 | self._river[l_idx] = self 1127 | elif isinstance(test, Fish): 1128 | Fish(self._river) 1129 | elif isinstance(test, Bear): 1130 | self._river[curr_pos] = None 1131 | 1132 | if rand_act == 'do_nothing': 1133 | pass 1134 | 1135 | def respond_to_request(self): 1136 | actions = {'move_right': 1, 'move_left':2, 'do_nothing': None} 1137 | rand_act = random.sample(list(actions.keys()),1)[0] 1138 | print(f'{type(self)} performed {rand_act} at {self._pos}') 1139 | result = self.consequence(rand_act) 1140 | return self._river 1141 | 1142 | 1143 | 1144 | # river = [None]*1000 1145 | # bear_one = Bear(river) 1146 | # fish_one = Fish(river) 1147 | # bear_two = Bear(river) 1148 | # fish_two = Fish(river) 1149 | # counter = 0 1150 | # while True: 1151 | # bear_one.respond_to_request() 1152 | # fish_one.respond_to_request() 1153 | # bear_two.respond_to_request() 1154 | # river_two = fish_two.respond_to_request() 1155 | # counter +=1 1156 | # if counter > 1000: 1157 | # break 1158 | 1159 | # # print(river_two) 1160 | # print(river) 1161 | 1162 | #P-2.37 1163 | """ 1164 | Write a simulator, as in the previous project, but add a Boolean gender 1165 | field and a floating-point strength field to each animal, using an Animal 1166 | class as a base class. If two animals of the same type try to collide, then 1167 | they only create a new instance of that type of animal if they are of differ- 1168 | ent genders. Otherwise, if two animals of the same type and gender try to 1169 | collide, then only the one of larger strength survives. 1170 | """ 1171 | class Animal: 1172 | def __init__(self, river, gender, strength): 1173 | k = 0 1174 | while True: 1175 | r = random.randint(0, len(river)-1) 1176 | if not river[r]: 1177 | break 1178 | k += 1 1179 | if k >= int(0.8*len(river)): 1180 | river += [None]*100 1181 | 1182 | river[r] = self 1183 | self._pos = r 1184 | self._river = river 1185 | self._gender = gender 1186 | self._strength = strength 1187 | 1188 | 1189 | def move(self, sgn): 1190 | curr_pos = self._pos 1191 | if sgn == 'move_right': 1192 | r_idx = curr_pos + 1 1193 | self._pos = r_idx 1194 | self._river[self._pos] = self 1195 | self._river[curr_pos] = None 1196 | return 200 1197 | 1198 | if sgn == 'move_left': 1199 | l_idx = curr_pos - 1 1200 | self._pos = l_idx 1201 | self._river[self._pos] = self 1202 | self._river[curr_pos] = None 1203 | return 200 1204 | return -1 1205 | 1206 | 1207 | 1208 | def consequence(self, action): 1209 | curr_pos = self._pos 1210 | print(curr_pos) 1211 | do_more = True 1212 | if action == 'move_right': 1213 | r_idx = self._pos + 1 1214 | elem = self._river[r_idx] 1215 | if elem is None: 1216 | return self.move(action) 1217 | else: 1218 | return elem 1219 | if action == 'move_left': 1220 | l_idx = self._pos - 1 1221 | elem = self._river[l_idx] 1222 | if elem is None: 1223 | return self.move(action) 1224 | else: 1225 | return elem 1226 | else: 1227 | return 200 1228 | 1229 | 1230 | 1231 | def respond_to_request(self): 1232 | actions = ['move_left', 'move_right', 'do_nothing'] 1233 | rand_act = random.sample(actions,1)[0] 1234 | print(f'{type(self)} performed {rand_act} at {self._pos}') 1235 | result = self.consequence(rand_act) 1236 | 1237 | 1238 | 1239 | class Bear(Animal): 1240 | def __init__(self, river, gender, strength): 1241 | super().__init__(river, gender, strength) 1242 | 1243 | def consequence(self, action): 1244 | result = super().consequence(action) 1245 | if result == 200: 1246 | pass 1247 | elif isinstance(elem, Bear): 1248 | if elem._gender != self._gender: 1249 | cub = Bear(self._river, random.sample([True, False],1)[0], float(random.randint(0, 25))) 1250 | else: 1251 | if self._strength > elem._strength: 1252 | return self.move(action) 1253 | else: 1254 | self._river[self._pos] = None 1255 | elif elem._strength > self._strength: 1256 | self._river[self._pos] = None 1257 | else: 1258 | self.move(action) 1259 | 1260 | 1261 | 1262 | # river = [None]*100 1263 | # b = Bear(river, True, 12.9) 1264 | # b.respond_to_request() 1265 | 1266 | #P - 2.38 1267 | """ 1268 | Write a Python program that simulates a system that supports the func- 1269 | tions of an e-book reader. You should include methods for users of your 1270 | system to “buy” new books, view their list of purchased books, and read 1271 | their purchased books. Your system should use actual books, which have 1272 | expired copyrights and are available on the Internet, to populate your set 1273 | of available books for users of your system to “purchase” and read. 1274 | """ 1275 | books = {'East of Eden': 16.99, "Gilead": 12.99} 1276 | 1277 | def load_data(title): 1278 | #suppose you make get request to remote server that has stored data 1279 | #of the book 1280 | pass 1281 | 1282 | class Book: 1283 | def __init__(self, title): 1284 | self._title = title 1285 | self._data = load_data(title) 1286 | 1287 | 1288 | 1289 | class User: 1290 | def __init__(self, name, acnt): 1291 | self._name = name 1292 | self._acnt = acnt 1293 | self._shelf = [] 1294 | self._balance = 0 1295 | 1296 | def buy_book(self, book): 1297 | self._balance += books[book] 1298 | self._shelf += Book(book) 1299 | 1300 | def read_book(self, book): 1301 | book = self._shelf[self._shelf.find(book)] 1302 | 1303 | 1304 | 1305 | -------------------------------------------------------------------------------- /python_primer.py: -------------------------------------------------------------------------------- 1 | from pdb import set_trace 2 | ### 1.1 Preview of Python Program 3 | 4 | def preview_python(): 5 | 6 | print('Welcome to the GPA calculator') 7 | print('Please enter all your letter grades, one per line.') 8 | print('Enter a blank line to designate the end.') 9 | 10 | points = {'A+' : 4.0, 'A':4.0, 'A-':3.67, 'B+':3.33, 'B': 3.0, 'B-': 2.67, 'C+':2.33, 'C': 2.0, 'C-': 1.67, 'D+':1.33, 11 | 'D': 1.0, 'F': 0.0} 12 | 13 | num_courses = 0 14 | total_points = 0 15 | done = False 16 | while not done: 17 | grade = input() 18 | if grade == '': 19 | done = True 20 | elif grade not in points: 21 | print(f"Unknown grade {grade} being ignored.") 22 | else: 23 | num_courses += 1 24 | total_points += points[grade] 25 | if num_courses > 0: 26 | print(f'Your GPA is {total_points/num_courses:.3}') 27 | 28 | 29 | # preview_python() 30 | 31 | 32 | ############################################################# 33 | 34 | ### 1.2 Objects in Python 35 | 36 | # print(0o52) 37 | # print(0b1011) 38 | # print(0x7f) 39 | 40 | # print('20\u20AC') 41 | # print('liz\n\n\tkeleshian\n\n') 42 | 43 | # b = [1, 2] 44 | # array = [1, 'a', b] 45 | # print(len(array)) 46 | 47 | # del array[2][0] 48 | # print(len(array)) 49 | # del array[0] 50 | 51 | # print(array) 52 | # print(len(array)) 53 | 54 | set_one = set('elizabeth keleshian') 55 | set_two = set('edgar aroutiounian') 56 | 57 | # print(set_one | set_two) #union 58 | # print(len(set_one | set_two)) 59 | # print(set_one & set_two) #intersection 60 | # print(set_one - set_two) #elements in s1 and not in s2 61 | # print(set_two - set_one) 62 | # print(set_one ^ set_two) 63 | 64 | 65 | ############################################################### 66 | 67 | ### 1.5 Built-in Functions 68 | 69 | # array = [1, 2, 3, -4] 70 | 71 | array = [0,1] 72 | 73 | a = [2,3,4] 74 | b = 4 75 | c = 5.4 76 | d= set() 77 | 78 | # print(all(array)) 79 | # print(any(array)) 80 | 81 | # print(hash(a)) #unhashable type: 'list' 82 | # print(b, hash(b)) 83 | # print(c, hash(c)) 84 | # # print(hash(d)) #unhashable type: 'set' 85 | # print(id(a)) 86 | # print(id(b)) 87 | # print(id(c)) 88 | # print(id(d)) 89 | 90 | # print(next(a)) 91 | # print(hash(-10.0)) 92 | 93 | # print(-10.0 == -10) 94 | 95 | # print(type(10.0)) 96 | 97 | # print(-10.0 is -10) 98 | 99 | # print(isinstance(-10, tuple)) 100 | 101 | ################################################################ 102 | 103 | ### 1.6 Input and Output 104 | 105 | # reply = input('Enter x and y, separated by spaces: ') 106 | # pieces = reply.split() 107 | # x = float(pieces[0]) 108 | # y = float(pieces[1]) 109 | # print(x, y) 110 | 111 | 112 | ################################################################ 113 | 114 | ### 1.7 Exception Handling 115 | 116 | def sum(values): 117 | if not isinstance(values, (list, tuple, str)): 118 | raise TypeError('parameter must be an interable type') 119 | total = 0 120 | for v in values: 121 | if not isinstance(v, (int, float)): 122 | raise TypeError('elements must be numeric') 123 | total = total+v 124 | 125 | return total 126 | 127 | # print(sum((1,2,'a',4))) 128 | # from pdb import set_trace 129 | 130 | # age = -1 131 | # while age <= 0: 132 | # try: 133 | # assert False 134 | # age = int(input('Enter your age in years: ')) 135 | # if age <= 0: 136 | # print('Your age must be positive.') 137 | # except Exception as e: 138 | # print(e) 139 | # set_trace() 140 | # break 141 | 142 | 143 | 144 | ################################################################ 145 | 146 | ### 1.8 Iterators and Generators 147 | 148 | # data = [1,2,3,4] 149 | 150 | # i = iter(data) 151 | 152 | # print(next(i)) 153 | # print(next(i)) 154 | # data[2] = 0 155 | # print(next(i)) 156 | # print(next(i)) 157 | # print(i) 158 | 159 | # d = dict([('a', 1), ('b', 2), ('c', 3)]) 160 | 161 | # print(d.values()) 162 | 163 | # a = d.items() 164 | 165 | # b = list(a) 166 | 167 | # # print(next(a)) 168 | # i = iter(b) 169 | # a = next(i) 170 | # print(b) 171 | 172 | # print(b[0][0], b[1][0], b[2][0]) 173 | 174 | def factors(n): 175 | for k in range(1, n+1): 176 | if n%k == 0: 177 | yield k 178 | 179 | # print(vars(factors(10))) 180 | # print(factors(10).close()) 181 | 182 | 183 | ################################################################ 184 | 185 | ### Exercises 186 | 187 | #R-1.1 188 | """ 189 | Write a short Python function is multiple(n, m), that takes two integer 190 | values and returns True if n is a multiple of m, that is, n = mi for some 191 | integer i, and False otherwise 192 | """ 193 | def is_multiple(n,m): 194 | if n < 0 or m < 0: 195 | n, m = abs(n), abs(m) 196 | elif n == 0 or m == 0: 197 | print('must pass nonzero integers') 198 | return False 199 | for i in range(1,int(n/2)+1): 200 | if n == i*m: 201 | return True 202 | return False 203 | 204 | # print(is_multiple(-100,0)) 205 | 206 | 207 | #R-1.2 208 | 209 | """ 210 | Write a short Python function, is even(k), that takes an integer value and 211 | returns True if k is even, and False otherwise. However, your function 212 | cannot use the multiplication, modulo, or division operators. 213 | """ 214 | 215 | def even(k): 216 | if k < 0: 217 | k = abs(k) 218 | while k > 0: 219 | k -= 2 220 | if k == 0: 221 | return True 222 | else: 223 | return False 224 | 225 | # print(even(10)) 226 | # print(even(11)) 227 | # print(even(-12)) 228 | # print(even(-101)) 229 | 230 | #R-1.3 231 | """ 232 | Write a short Python function, minmax(data), that takes a sequence of 233 | one or more numbers, and returns the smallest and largest numbers, in the 234 | form of a tuple of length two. Do not use the built-in functions min or 235 | max in implementing your solution. 236 | """ 237 | 238 | def minmax(data): 239 | min_val = data[0] 240 | max_val = data[0] 241 | if len(data) == 1: 242 | return data[0], data[0] 243 | for i in data[1:]: 244 | if i < min_val: 245 | min_val = i 246 | elif i > max_val: 247 | max_val = i 248 | return min_val, max_val 249 | 250 | # print(minmax([1, 4, 2, 10, -12, 100, 4, 10])) 251 | # print(minmax([1, 2])) 252 | # print(minmax([1])) 253 | 254 | #R-1.4 255 | """ 256 | Write a short Python function that takes a positive integer n and returns 257 | the sum of the squares of all the positive integers smaller than n. 258 | """ 259 | def sum_of_squares(n): 260 | total = 0 261 | if n == 1: 262 | return 0 263 | for i in range(1,n): 264 | total += i*i 265 | return total 266 | 267 | # print(sum_of_squares(4)) 268 | 269 | 270 | #R-1.5 271 | """ 272 | Give a single command that computes the sum from Exercise R-1.4, rely- 273 | ing on Python’s comprehension syntax and the built-in sum function. 274 | """ 275 | # print(sum(i*i for i in range(1,4))) 276 | 277 | 278 | #R-1.6 279 | """ 280 | Write a short Python function that takes a positive integer n and returns 281 | the sum of the squares of all the odd positive integers smaller than n. 282 | """ 283 | def num_of_odd_squares(n): 284 | total = 0 285 | for i in range(1, n, 2): 286 | total += i*i 287 | return total 288 | 289 | # print(num_of_odd_squares(4)) 290 | 291 | #R-1.7 292 | """ 293 | Give a single command that computes the sum from Exercise R-1.6, rely- 294 | ing on Python’s comprehension syntax and the built-in sum function. 295 | """ 296 | # print(sum(i*i for i in range(1, 4, 2))) 297 | 298 | 299 | #R-1.8 300 | """ 301 | Python allows negative integers to be used as indices into a sequence, 302 | such as a string. If string s has length n, and expression s[k] is used for in- 303 | dex −n ≤ k < 0, what is the equivalent index j ≥ 0 such that s[j] references 304 | the same element? 305 | """ 306 | # j = -k - 1, so if k == -1, j = 0, and k == -2, j = 1 307 | 308 | 309 | #R-1.9 310 | """ 311 | What parameters should be sent to the range constructor, to produce a 312 | range with values 50, 60, 70, 80? 313 | """ 314 | # print(list(range(50, 81, 10))) 315 | 316 | #R-1.10 317 | """ 318 | What parameters should be sent to the range constructor, to produce a 319 | range with values 8, 6, 4, 2, 0, −2, −4, −6, −8? 320 | """ 321 | 322 | # print(list(range(-8, 9, 2))) 323 | 324 | #R-1.11 325 | """ 326 | Demonstrate how to use Python’s list comprehension syntax to produce 327 | the list [1, 2, 4, 8, 16, 32, 64, 128, 256]. 328 | """ 329 | 330 | # print([2**i for i in range(9)]) 331 | 332 | #R-1.12 333 | """ 334 | Python’s random module includes a function choice(data) that returns a 335 | random element from a non-empty sequence. The random module in- 336 | cludes a more basic function randrange, with parameterization similar to 337 | the built-in range function, that return a random choice from the given 338 | range. Using only the randrange function, implement your own version 339 | of the choice function 340 | """ 341 | from random import randrange 342 | def my_choice(data): 343 | min_val = min(data) 344 | max_val = max(data) 345 | candidate = randrange(min_val, max_val+1) 346 | 347 | while True: 348 | if candidate in data: 349 | return candidate 350 | else: 351 | candidate = randrange(min_val, max_val+1) 352 | 353 | my_data = [-10, 12, 15, 13, -15] 354 | # print(my_choice(my_data)) 355 | 356 | 357 | #C-1.13 358 | """ 359 | Write a pseudo-code description of a function that reverses a list of n 360 | integers, so that the numbers are listed in the opposite order than they 361 | were before, and compare this method to an equivalent Python function 362 | for doing the same thing. 363 | """ 364 | def reverse_list(array, first = 0): 365 | last = len(array) - 1 - first 366 | if first <= last: 367 | old_first = array[first] 368 | old_last = array[last] 369 | array[first] = old_last 370 | array[last] = old_first 371 | return reverse_list(array, first+1) 372 | else: 373 | return array 374 | 375 | # print(reverse_list([1, 2, 3, 4,5])) 376 | 377 | #python function that does the same thing 378 | # array = [1,2,3,4,5] 379 | # array.reverse() 380 | # print(array) 381 | 382 | 383 | #C-1.14 384 | """ 385 | Write a short Python function that takes a sequence of integer values and 386 | determines if there is a distinct pair of numbers in the sequence whose 387 | product is odd. 388 | """ 389 | seq = [1 ,2, 1, 4] 390 | seq_one = [1, 2, 4, 6, 8] 391 | seq_two = [1, 4, 2, 6, 3] 392 | 393 | def is_product_odd(data): 394 | count = 0 395 | seen = set() 396 | for i in data: 397 | if i%2 != 0 and i not in seen: 398 | seen.add(i) 399 | count+= 1 400 | if count >= 2: 401 | return True 402 | else: 403 | return False 404 | 405 | # print(is_product_odd(seq)) 406 | 407 | # print(is_product_odd(seq_one)) 408 | 409 | # print(is_product_odd(seq_two)) 410 | 411 | 412 | #C-1.15 413 | """ 414 | Write a Python function that takes a sequence of numbers and determines 415 | if all the numbers are different from each other (that is, they are distinct). 416 | """ 417 | seq_one = [1, 4, 5, 6] 418 | seq_two = [1, 1, 3, 4] 419 | 420 | def is_unique(data): 421 | count = 0 422 | seen = set() 423 | for i in data: 424 | if i not in seen: 425 | seen.add(i) 426 | else: 427 | count +=1 428 | return False if count > 0 else True 429 | 430 | # print(is_unique(seq_one)) 431 | # print(is_unique(seq_two)) 432 | 433 | #C-1.16 434 | """ 435 | In our implementation of the scale function (page 25), the body of the loop 436 | executes the command data[j] *= factor. We have discussed that numeric 437 | types are immutable, and that use of the *= operator in this context causes 438 | the creation of a new instance (not the mutation of an existing instance). 439 | How is it still possible, then, that our implementation of scale changes the 440 | actual parameter sent by the caller? 441 | """ 442 | #data would have to be a list type so that when you access data[j] 443 | # and change its value, that means the jth entry of the array is now pointing to a new 444 | # object which is of int or float. 445 | 446 | #C-1.17 447 | """ 448 | Had we implemented the scale function (page 25) as follows, does it work 449 | properly? 450 | def scale(data, factor): 451 | for val in data: 452 | val *= factor 453 | Explain why or why not. 454 | 455 | No, this wouldn't work. val is an iterator 456 | and only exists inside the scope of the for loop. if you're simply assigning val to be a product 457 | of itself and the factor, you're not mutating the list at all. in fact, since you're not 458 | mutating the list or returning anything, nothing is useful in this function. 459 | """ 460 | 461 | #C-1.18 462 | """ 463 | Demonstrate how to use Python’s list comprehension syntax to produce 464 | the list [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]. 465 | """ 466 | # print([x**2 + x for x in range(10)]) 467 | 468 | #C-1.19 469 | """ 470 | Demonstrate how to use Python’s list comprehension syntax to produce 471 | the list [ a , b , c , ..., z ], but without having to type all 26 such 472 | characters literally. 473 | """ 474 | # print([chr(i) for i in range(ord('a'), ord('a')+26)]) 475 | 476 | #C-1.20 477 | """ 478 | Python’s random module includes a function shuffle(data) that accepts a 479 | list of elements and randomly reorders the elements so that each possi- 480 | ble order occurs with equal probability. The random module includes a 481 | more basic function randint(a, b) that returns a uniformly random integer 482 | from a to b (including both endpoints). Using only the randint function, 483 | implement your own version of the shuffle function. 484 | """ 485 | from random import randint 486 | 487 | def shuffle_data(array): 488 | max_idx = len(array) - 1 489 | seen_idx = set() 490 | length_array = len(array) 491 | new_array = [] 492 | while len(seen_idx) != length_array: 493 | idx = randint(0, max_idx) 494 | if idx not in seen_idx: 495 | seen_idx.add(idx) 496 | new_array.append(array[idx]) 497 | 498 | return new_array 499 | 500 | data = [1, 4, 3, 5, 1, 23, 4] 501 | 502 | # print(shuffle_data(data)) 503 | 504 | #C-1.21 505 | """ 506 | Write a Python program that repeatedly reads lines from standard input 507 | until an EOFError is raised, and then outputs those lines in reverse order 508 | (a user can indicate end of input by typing ctrl-D). 509 | """ 510 | 511 | def reverse_lines_from_input(): 512 | results = [] 513 | try: 514 | print('''Write a few lines of text, where each line is separated by pressing enter. 515 | Type ctrl-D when done.\n''') 516 | while True: 517 | line = input() 518 | results.append(line) 519 | except EOFError as e: 520 | results.reverse() 521 | return results 522 | 523 | 524 | # print(reverse_lines_from_input()) 525 | 526 | #C-1.22 527 | """ 528 | Write a short Python program that takes two arrays a and b of length n 529 | storing int values, and returns the dot product of a and b. That is, it returns 530 | an array c of length n such that c[i] = a[i] · b[i], for i = 0, . . . , n − 1. 531 | """ 532 | 533 | def dot_product(a, b): 534 | for i, (k,v) in enumerate(zip(a,b)): 535 | a[i] = k*v 536 | return a 537 | 538 | # print(dot_product([1, 2, 3], [1, 2, 3])) 539 | 540 | 541 | #C-1.23 542 | """ 543 | Give an example of a Python code fragment that attempts to write an ele- 544 | ment to a list based on an index that may be out of bounds. If that index 545 | is out of bounds, the program should catch the exception that results, and 546 | print the following error message: 547 | “Don’t try buffer overflow attacks in Python!” 548 | """ 549 | def catch_index_error(): 550 | array = [1,2,3] 551 | 552 | try: 553 | for i in range(4): 554 | array[i] = i 555 | except IndexError: 556 | print('Dont try buffer overflow attacks in python!') 557 | return array 558 | 559 | #C-1.24 560 | """ 561 | Write a short Python function that counts the number of vowels in a given 562 | character string. 563 | """ 564 | def count_vowels(s): 565 | vowels = {'a', 'e', 'i', 'o', 'u', 'y'} 566 | counter = 0 567 | for i in s: 568 | counter +=1 if i in vowels else 0 569 | return counter 570 | 571 | # print(count_vowels('edgar aroutiounian')) 572 | 573 | #C-1.25 574 | """ 575 | Write a short Python function that takes a string s, representing a sentence, 576 | and returns a copy of the string with all punctuation removed. For exam- 577 | ple, if given the string "Let's try, Mike.", this function would return 578 | "Lets try Mike". 579 | """ 580 | def remove_punctuations(s): 581 | puncs = {';', "'", '.',"\"",'!', '?', ',', ':' } 582 | array = list(s) 583 | for i, char in enumerate(array): 584 | if char in puncs: 585 | set_trace() 586 | del array[i] 587 | # set_trace() 588 | new_s = ''.join(array) 589 | 590 | return new_s 591 | 592 | # print(remove_punctuations("hello, my name\'s liz. what is your name? \"my name is: Edgar.\".")) 593 | 594 | #C-1.26 595 | """ 596 | Write a short program that takes as input three integers, a, b, and c, from 597 | the console and determines if they can be used in a correct arithmetic 598 | formula (in the given order), like “a + b = c,” “a = b − c,” or “a ∗ b = c.” 599 | """ 600 | def evaluate(): 601 | abc = [] 602 | print("Enter three numbers, separated by new line. Press ctrl-D when done.") 603 | try: 604 | while True: 605 | abc.append(input()) 606 | except EOFError: 607 | arith_ops = {'+', '-', '*', '/'} 608 | a, b, c = abc 609 | for i in arith_ops: 610 | res = eval(a+i+b) 611 | if res == int(c): 612 | return a+i+b+'='+c 613 | res = eval(b+i+c) 614 | if res == int(a): 615 | return a+'='+b+i+c 616 | 617 | # print(evaluate()) 618 | 619 | #C-1.27 620 | """ 621 | In Section 1.8, we provided three different implementations of a generator 622 | that computes factors of a given integer. The third of those implementa- 623 | tions, from page 41, was the most efficient, but we noted that it did not 624 | yield the factors in increasing order. Modify the generator so that it reports 625 | factors in increasing order, while maintaining its general performance ad- 626 | vantages. 627 | """ 628 | def factors(n): 629 | k = 1 630 | stored_values = [] 631 | while k*k < n: 632 | if n%k == 0: 633 | yield k 634 | stored_values.append(n//k) 635 | k+=1 636 | if k*k == n: 637 | yield k 638 | stored_values = sorted(stored_values) 639 | a = iter(stored_values) 640 | try: 641 | while a: 642 | yield(next(a)) 643 | except StopIteration as e: 644 | yield e 645 | 646 | 647 | # result = factors(256) 648 | # try: 649 | # while True: 650 | # print(next(result)) 651 | # except StopIteration as e: 652 | # print(e) 653 | 654 | #C-1.28 655 | """ 656 | The p-norm of a vector v = (v 1 , v 2 , . . . , v n ) in n-dimensional space is de- 657 | fined as 658 | 659 | For the special case of p = 2, this results in the traditional Euclidean 660 | norm, which represents the length of the vector. Give an implemen- 661 | tation of a function named norm such that norm(v, p) returns the p-norm 662 | value of v and norm(v) returns the Euclidean norm of v. You may assume 663 | that v is a list of numbers. 664 | """ 665 | def norm(v,p): 666 | total = [] 667 | for elem in v: 668 | total.append(elem**p) 669 | return f'{sum(total)**(1/p):.3}' 670 | 671 | 672 | # print(norm([4,3],2)) 673 | 674 | # P-1.29 675 | """ 676 | Write a Python program that outputs all possible strings formed by using 677 | the characters c , a , t , d , o , and g exactly once. 678 | """ 679 | from itertools import permutations 680 | def all_possible_strings(s): 681 | perm = permutations(s) 682 | perm = list(perm) 683 | for i in list(perm): 684 | print(''.join(i)) 685 | 686 | print(len(perm)) 687 | 688 | # all_possible_strings('catdog') 689 | 690 | # P-1.30 691 | """ 692 | Write a Python program that can take a positive integer greater than 2 as 693 | input and write out the number of times one must repeatedly divide this 694 | number by 2 before getting a value less than 2. 695 | """ 696 | def square_root_approx(n): 697 | count = 0 698 | while n >= 2: 699 | count += 1 700 | n = n/2 701 | return count 702 | 703 | # print(square_root_approx(100000)) 704 | 705 | # P-1.31 706 | """ 707 | Write a Python program that can “make change.” Your program should 708 | take two numbers as input, one that is a monetary amount charged and the 709 | other that is a monetary amount given. It should then return the number 710 | of each kind of bill and coin to give back as change for the difference 711 | between the amount given and the amount charged. The values assigned 712 | to the bills and coins can be based on the monetary system of any current 713 | or former government. Try to design your program so that it returns as 714 | few bills and coins as possible. 715 | """ 716 | from collections import Counter 717 | 718 | def make_change(amnt_due, amnt_paid): 719 | change = amnt_paid - amnt_due 720 | total_bills = [] 721 | denominations = { 722 | 0.01: 'penny(ies)', 723 | 0.05: 'nickel(s)', 724 | 0.1: 'dime(s)', 725 | 0.25: 'quarter(s)', 726 | 1: 'one dollar bill(s)', 727 | 5: 'five dollar bill(s)', 728 | 10: 'ten dollar bill(s)', 729 | 20: 'twenty dollar bill(s)', 730 | 50: 'fifty dollar bill(s)', 731 | 100: 'one hundred dollar bill(s)' 732 | } 733 | while change > 0: 734 | min_change = 0.01 735 | for key in denominations: 736 | # set_trace() 737 | if min_change <= key <= change: 738 | min_change = key 739 | total_bills.append(denominations[min_change]) 740 | change -= min_change 741 | change = round(change, 2) 742 | 743 | a = dict(Counter(total_bills)) 744 | 745 | return a 746 | 747 | 748 | # print(make_change(90, 100)) 749 | 750 | # P-1.32 751 | """ 752 | Write a Python program that can simulate a simple calculator, using the 753 | console as the exclusive input and output device. That is, each input to the 754 | calculator, be it a number, like 12.34 or 1034, or an operator, like + or =, 755 | can be done on a separate line. After each such input, you should output 756 | to the Python console what would be displayed on your calculator. 757 | """ 758 | def calculator(): 759 | expression = [] 760 | a = '' 761 | while True: 762 | a = input().strip() 763 | if a == '=': 764 | break 765 | expression.append(a) 766 | return eval(''.join(expression)) 767 | 768 | # print(calculator()) 769 | 770 | # P-1.33 771 | """ 772 | Write a Python program that simulates a handheld calculator. Your pro- 773 | gram should process input from the Python console representing buttons 774 | that are “pushed,” and then output the contents of the screen after each op- 775 | eration is performed. Minimally, your calculator should be able to process 776 | the basic arithmetic operations and a reset/clear operation. 777 | """ 778 | 779 | def find_ops(expression, sym): 780 | idx = -1 781 | 782 | while True: 783 | idx = expression.find(sym, idx+1) 784 | if idx == -1: 785 | break 786 | else: 787 | expression = find_nums(expression, sym, idx) 788 | 789 | return expression 790 | 791 | 792 | def get_answer(l,r,s): 793 | if l.find('.') == -1: 794 | l = int(l) 795 | else: 796 | l = float(l) 797 | if r.find('.') == -1: 798 | r = int(r) 799 | else: 800 | r = float(r) 801 | if s == '*': 802 | return l*r 803 | elif s == '+': 804 | return l+r 805 | elif s == '/': 806 | return l/r 807 | elif s == '-': 808 | return l - r 809 | else: 810 | print('no valid symbols found') 811 | 812 | 813 | 814 | def find_nums(expr, sym, idx): 815 | lft_num= [] 816 | rgt_num = [] 817 | symbols = ['*', '+', '-', '/'] 818 | num = '' 819 | bckwrd = idx -1 820 | frwrd = idx +1 821 | 822 | while True: 823 | if expr[bckwrd] not in symbols: 824 | lft_num.append(expr[bckwrd]) 825 | if bckwrd == 0: 826 | break 827 | bckwrd -= 1 828 | else: 829 | break 830 | 831 | while True: 832 | if expr[frwrd] not in symbols: 833 | rgt_num.append(expr[frwrd]) 834 | if frwrd == len(expr) - 1: 835 | break 836 | frwrd += 1 837 | else: 838 | break 839 | 840 | lft_num.reverse() 841 | lft_num = ''.join(lft_num) 842 | rgt_num = ''.join(rgt_num) 843 | answer = get_answer(lft_num, rgt_num, sym) 844 | 845 | if bckwrd == 0 and frwrd != len(expr)-1: 846 | expr = str(answer) + expr[frwrd:] 847 | elif bckwrd != 0 and frwrd == len(expr) - 1: 848 | expr = expr[:bckwrd+1] + str(answer) 849 | elif bckwrd != 0 and frwrd != len(expr) - 1: 850 | expr = expr[:bckwrd+1]+str(answer)+expr[frwrd:] 851 | else: 852 | expr = str(answer) 853 | # set_trace() 854 | return expr 855 | 856 | 857 | def handheld_calculator(): 858 | expression = [] 859 | 860 | while True: 861 | a = input().strip() 862 | if a == '': 863 | break 864 | expression.append(a) 865 | 866 | expr = expression[0] 867 | primary_ops = ['*', '/'] 868 | secondary_ops = ['+', '-'] 869 | 870 | for i in primary_ops: 871 | expr = find_ops(expr, i) 872 | 873 | for i in secondary_ops: 874 | expr = find_ops(expr, i) 875 | 876 | print(expr) 877 | 878 | 879 | # handheld_calculator() 880 | 881 | # P-1.34 882 | """ 883 | A common punishment for school children is to write out a sentence mul- 884 | tiple times. Write a Python stand-alone program that will write out the 885 | following sentence one hundred times: “I will never spam my friends 886 | again.” Your program should number each of the sentences and it should 887 | make eight different random-looking typos. 888 | """ 889 | from random import choice 890 | def punishment(): 891 | results = [] 892 | seq = range(100) 893 | for i in seq: 894 | results.append(f"{i+1}. I will never spam my friends again.") 895 | rand_typos = { 896 | 'friends': 'freinds', 897 | 'never': 'nver', 898 | 'again': 'agian', 899 | 'I': 'i', 900 | 'again.': 'again', 901 | 'will': 'wil', 902 | 'spam': 'spamm', 903 | 'I will': 'ill' 904 | } 905 | idxs = [] 906 | for typo in rand_typos: 907 | idx = choice(seq) 908 | idxs.append(idx) 909 | results[idx] = results[idx].replace(typo, rand_typos[typo]) 910 | 911 | print(results) 912 | print(idxs) 913 | 914 | # punishment() 915 | 916 | # P-1.35 917 | """ 918 | The birthday paradox says that the probability that two people in a room 919 | will have the same birthday is more than half, provided n, the number of 920 | people in the room, is more than 23. This property is not really a paradox, 921 | but many people find it surprising. Design a Python program that can test 922 | this paradox by a series of experiments on randomly generated birthdays, 923 | which test this paradox for n = 5, 10, 15, 20, . . . , 100. 924 | """ 925 | 926 | def generate_birthdays(n): 927 | birthdays = [] 928 | month = 0 929 | day = 0 930 | for i in range(n): 931 | month = randint(1 ,12) 932 | if month in [1,3,5,7,8, 10, 12]: 933 | day = randint(1, 31) 934 | elif month in [4, 6, 9, 11]: 935 | day = randint(1, 30) 936 | else: 937 | day = randint(1, 28) 938 | birthdays.append(str(month) + '/' + str(day)) 939 | unique_birthdays = set(birthdays) 940 | return f"There are {len(birthdays) - len(unique_birthdays)} overlapping birthdays when n = {n}" 941 | 942 | # for i in range(5, 101, 5): 943 | # print(generate_birthdays(i)) 944 | 945 | # P-1.36 946 | """ 947 | Write a Python program that inputs a list of words, separated by white- 948 | space, and outputs how many times each word appears in the list. You 949 | need not worry about efficiency at this point, however, as this topic is 950 | something that will be addressed later in this book. 951 | """ 952 | 953 | def word_counter(): 954 | words = [] 955 | while True: 956 | a = input() 957 | if a == '': 958 | break 959 | words.append(a) 960 | words = words[0] 961 | words = words.split(' ') 962 | 963 | seen = {} 964 | for w in words: 965 | if w in seen: 966 | seen[w] += 1 967 | else: 968 | seen[w] = 1 969 | 970 | print(seen) 971 | 972 | word_counter() 973 | -------------------------------------------------------------------------------- /recursion.py: -------------------------------------------------------------------------------- 1 | def factorial(n): 2 | if n == 0: #when you bottom out 3 | return 1 4 | else: 5 | return n*factorial(n-1) 6 | 7 | #Each function call generates an activation record that gets suspended each time another function call gets invoked. 8 | #Recursive functions follow this pattern: wind the stack, and then bottom out once it reaches the base case, and then 9 | #unwind the stack until it reaches the top of the stack. 10 | 11 | #recursive approach to ruler drawing: For each inch, place tick with numeric label. denote length of tick designating 12 | #whole inch as major tick length. B/w marks for whole inches, ruler contains series of minor ticks, at 1/2, 1/4 inch 13 | #intervals, etc. As interval decreases by 1/2, tick length decreases by one. 14 | 15 | #This pattern is an exmaple of a fractal. 16 | 17 | def draw_line(tick_length, tick_label=''): 18 | """Draw one line with given tick length (followed by optional label)""" 19 | line = '-'*tick_length 20 | if tick_label: 21 | line+= ' ' + tick_label 22 | print(line) 23 | 24 | def draw_interval(center_length): 25 | """Draw tick interval based upon a central tick length""" 26 | if center_length > 0: 27 | draw_interval(center_length-1) 28 | draw_line(center_length) 29 | draw_interval(center_length-1) 30 | 31 | 32 | def draw_ruler(num_inches, major_length): 33 | """Draw english ruler with given number of inches, major tick length""" 34 | draw_line(major_length, '0') 35 | for j in range(1, 1 + num_inches): 36 | draw_interval(major_length - 1) 37 | draw_line(major_length, str(j)) 38 | 39 | 40 | # draw_ruler(12, 5) 41 | 42 | ###############################333 43 | 44 | #Binary Search 45 | #data should be sorted 46 | def binary_search(data, target, low, high): 47 | """Return True if target is found in indicated portion of a Python list. 48 | 49 | The search only considers the porition from data[low] to data[high] inclusive. 50 | 51 | """ 52 | 53 | if low > high: 54 | return False #interval is empty; no match 55 | else: 56 | mid = (low+high)//2 57 | if target == data[mid]: 58 | return True 59 | elif target < data[mid]: 60 | return binary_search(data, target, low, mid-1) 61 | elif target > data[mid]: 62 | return binary_search(data, target, mid+1, high) 63 | #runs O(log(n)) time as opposed to sequential search O(n) time 64 | 65 | # print(binary_search([1,2,3,12,1,34,4],3, 5, 7)) 66 | 67 | #################################################3 68 | 69 | import os 70 | 71 | def disk_usage(path): 72 | """Return the number of bytes used by a file/folder and any descendants""" 73 | total = os.path.getsize(path) 74 | if os.path.isdir(path): 75 | for filename in os.listdir(path): 76 | childpath = os.path.join(path, filename) 77 | total += disk_usage(childpath) 78 | print(f'{total} {path}') 79 | return total 80 | #there are O(n) recursive calls, each of which runs in O(n) time leading to overall 81 | #O(n^2). But actually its O(n) because the overall are overall O(n) recursive calls 82 | #each of which uses O(1) time outside the loop and that the overall number of operations due to the loop 83 | # is O(n). Summing everything, the overall number of operations is O(n). This technique 84 | #of achieving a tighter bound on the series of operations by considering a cumalative effect is called 85 | #amoritization 86 | # print(disk_usage('/home/mathlizard/smartFont')) 87 | 88 | #recognizing and avoiding pitfalls when applying recusion 89 | #element uniqueness problem 90 | def unique3(S, start, stop): 91 | #base case, elemnts are trivially unique 92 | if stop - start <= 1: return True #at most one item 93 | elif not unique3(S, start, stop-1): return False 94 | elif not unique3(S, start+1, stop): return False 95 | else: return S[start] != S[stop-1] 96 | #terrible use of recursion, running time is O(2^n) 97 | 98 | def bad_fibonacci(n): 99 | if n<=1: 100 | return n 101 | else: 102 | return bad_fibonacci(n-2) + bad_fibonacci(n-1) 103 | #after computing F_n-2, the call to compute F_n-1 requires its own recursive call to comput 104 | # F_n-2, as it doesn't have knowledge of the value of F_n-2 that was computed earlier level of recrusion 105 | #snowballing affect leads to exponential running time 106 | 107 | def good_fibonacci(n): 108 | if n <= 1: 109 | return (n,0) 110 | else: 111 | (a,b) = good_fibonacci(n-1) 112 | return (a+b, a) 113 | #this takes O(n) time 114 | # print(good_fibonacci(5)) 115 | 116 | #Another pitfall is running into infinite recursion (no base case) 117 | #programmer should ensure each recursive call is in som eway progressing toward a base case 118 | 119 | #interpreter can be dynamically reconfigred to change default recursive limit 120 | # import sys 121 | # old = sys.getrecursionlimit() 122 | # print(old) 123 | # sys.setrecursionlimit(1000000) 124 | 125 | 126 | def linear_sum(S, n): 127 | if n == 0: 128 | return 0 129 | else: 130 | return linear_sum(S, n-1)+ S[n-1] 131 | 132 | def reverse(S, start, stop): 133 | if start < stop - 1: 134 | S[start], S[stop-1] = S[stop-1], S[start] 135 | reverse(S, start+1, stop-1) 136 | 137 | #Reversing the elements of a sequence using linear recursion 138 | 139 | def power(x,n): 140 | if n == 0: 141 | return 1 142 | else: 143 | return x *power(x, n-1) 144 | 145 | #this version is more efficient, recurision call is at most half of exponent 146 | def power(x, n): 147 | if n == 0: 148 | return 1 149 | else: 150 | partial = power(x, n//2) 151 | result = partial*partial 152 | if n%2 == 1: 153 | result *= x 154 | return result 155 | 156 | def binary_sum(S, start, stop): 157 | if start >= stop: 158 | return 0 159 | elif start == stop-1: 160 | return S[start] 161 | else: 162 | mid = (start+stop)//2 163 | return binary_sum(S, start, mid) + binary_sum(S, mid, stop) 164 | 165 | U = {0,1,2,3,4,5,6,7,8,9} 166 | 167 | ################################################### 168 | 169 | # R-4.1 Describe a recursive algorithm for finding the maximum element in a se- 170 | # quence, S, of n elements. What is your running time and space usage? 171 | 172 | def find_max(s): 173 | def find_m(S, stop): 174 | if stop == 0: 175 | return S[stop] 176 | else: 177 | return max(S[stop],find_m(S, stop-1)) 178 | return find_m(S, len(S) - 1) 179 | 180 | # print(find_max([1,2,3,1,5], 4)) 181 | #running time is O(n) and space usage is also O(n) 182 | 183 | 184 | # R-4.2 Draw the recursion trace for the computation of power(2, 5), using the 185 | # traditional function implemented in Code Fragment 4.11. 186 | # Drawn on notepad. Stack of 6. Bottomed out at power(2,0), returning 1. 187 | 188 | 189 | # R-4.3 Draw the recursion trace for the computation of power(2, 18), using the 190 | # repeated squaring algorithm, as implemented in Code Fragment 4.12. 191 | # Drawn on notepad. Stack of 6. 192 | 193 | # R-4.6 Describe a recursive function for computing the n th Harmonic number, 194 | # H n = ∑ ni=1 1/i. 195 | def harmonic_num(n): 196 | if n == 1: 197 | return 1 198 | else: 199 | return (1/n) + harmonic_num(n-1) 200 | 201 | # print(harmonic_num(5)) 202 | 203 | # R-4.7 Describe a recursive function for converting a string of digits into the in- 204 | # teger it represents. For example, 13531 represents the integer 13, 531. 205 | def rec_str_to_int(s,n): 206 | if n == len(s) - 1: 207 | return int(s[n]) 208 | else: 209 | return int(s[n])*10**(len(s) - 1 - n) + rec_str_to_int(s, n+1) 210 | 211 | # print(rec_str_to_int('12345', 0)) 212 | 213 | # R-4.8 Isabel has an interesting way of summing up the values in a sequence A of 214 | # n integers, where n is a power of two. She creates a new sequence B of half 215 | # the size of A and sets B[i] = A[2i] + A[2i + 1], for i = 0, 1, . . . , (n/2) − 1. If 216 | # B has size 1, then she outputs B[0]. Otherwise, she replaces A with B, and 217 | # repeats the process. What is the running time of her algorithm? 218 | 219 | #running time is log(n) 220 | 221 | # C-4.9 Write a short recursive Python function that finds the minimum and max- 222 | # imum values in a sequence without using any loops. 223 | def find_max_min(S): 224 | def lwr_bnd(S, n): 225 | if n == 0: 226 | return S[n] 227 | else: 228 | return min(S[n], lwr_bnd(S, n-1)) 229 | def uppr_bnd(S, n): 230 | if n == 0: 231 | return S[n] 232 | else: 233 | return max(S[n], uppr_bnd(S, n-1)) 234 | print(lwr_bnd(S, len(S)-1), uppr_bnd(S, len(S)-1)) 235 | 236 | # find_max_min([112,4,5,16,7,-1, 10]) 237 | 238 | 239 | # C-4.10 Describe a recursive algorithm to compute the integer part of the base-two 240 | # logarithm of n using only addition and integer division. 241 | def log(n): 242 | if n == 1: 243 | return 0 244 | else: 245 | return 1 + log(n/2) 246 | 247 | # print(log(8)) 248 | 249 | 250 | # C-4.11 Describe an efficient recursive function for solving the element unique- 251 | # ness problem, which runs in time that is at most O(n**2) in the worst case 252 | # without using sorting. 253 | 254 | def rec_unique_set(S): 255 | if len(S) == 1: 256 | return True 257 | else: 258 | cand = S.pop() 259 | for elem in S: 260 | if cand == elem: 261 | return False 262 | return rec_unique_set(S) 263 | 264 | # print(rec_unique_set([1,2,3,4,3])) 265 | 266 | # C-4.12 Give a recursive algorithm to compute the product of two positive integers, 267 | # m and n, using only addition and subtraction. 268 | def prod(m, n): 269 | if n == 0: 270 | return 0 271 | else: 272 | return m + prod(m, n-1) 273 | # print(prod(3,4)) 274 | 275 | 276 | # C-4.14 In the Towers of Hanoi puzzle, we are given a platform with three pegs, a, 277 | # b, and c, sticking out of it. On peg a is a stack of n disks, each larger than 278 | # the next, so that the smallest is on the top and the largest is on the bottom. 279 | # The puzzle is to move all the disks from peg a to peg c, moving one disk 280 | # at a time, so that we never place a larger disk on top of a smaller one. 281 | # See Figure 4.15 for an example of the case n = 4. Describe a recursive 282 | # algorithm for solving the Towers of Hanoi puzzle for arbitrary n. (Hint: 283 | # Consider first the subproblem of moving all but the n th disk from peg a to 284 | # another peg using the third as “temporary storage.”) 285 | def hanoi_puzzle(peg_a): 286 | temp = [] 287 | peg_b = [] 288 | def helper2(): 289 | if len(temp) == 0: 290 | return peg_b 291 | else: 292 | peg_b.append(temp.pop()) 293 | return helper2() 294 | 295 | def helper(): 296 | if len(peg_a) == 0: 297 | return temp 298 | else: 299 | temp.append(peg_a.pop()) 300 | return helper() 301 | temp = helper() 302 | peg_b = helper2() 303 | return peg_b 304 | # print(helper(pyramid,0)) 305 | 306 | # print(hanoi_puzzle(['bottom', 'middle', 'top'])) 307 | 308 | # C-4.15 Write a recursive function that will output all the subsets of a set of n 309 | # elements (without repeating any subsets). 310 | # def find subsets(S): 311 | # def helper(S, w): 312 | 313 | 314 | # C-4.16 Write a short recursive Python function that takes a character string s and 315 | # outputs its reverse. For example, the reverse of pots&pans would be 316 | # snap&stop . 317 | def rec_reverse_string(s): 318 | def helper(s, left, right): 319 | if left < right: 320 | s[left], s[right] = s[right], s[left] 321 | return helper(s, left+1, right -1) 322 | else: 323 | return s 324 | 325 | s_list = [char for char in s] 326 | result = helper(s_list, 0, len(s_list)-1) 327 | return ''.join(result) 328 | 329 | # print(rec_reverse_string('pots&pans')) 330 | 331 | # C-4.17 Write a short recursive Python function that determines if a string s is a 332 | # palindrome, that is, it is equal to its reverse. For example, racecar and 333 | # gohangasalamiimalasagnahog are palindromes. 334 | def is_palindrome(s): 335 | def helper(s, left, right): 336 | if left == -1: 337 | return True 338 | else: 339 | if s[left] != s[right]: 340 | return False 341 | return helper(s, left-1, right+1) 342 | n = len(s) 343 | mid_pt = n // 2 344 | if n%2 == 1: 345 | result = helper(s, mid_pt-1, mid_pt+1) 346 | else: 347 | result = helper(s, mid_pt -1, mid_pt) 348 | return result 349 | 350 | # print(is_palindrome('hanna')) 351 | 352 | 353 | # C-4.18 Use recursion to write a Python function for determining if a string s has 354 | # more vowels than consonants. 355 | def more_vowels(s): 356 | counter = 0 357 | def helper(s, n, counter): 358 | if n == len(s): 359 | return counter 360 | else: 361 | if s[n] in {'a', 'e', 'i', 'o', 'u', 'y'}: 362 | counter += 1 363 | return helper(s, n+1, counter) 364 | counter = helper(s, 0, counter) 365 | if counter > len(s)//2: 366 | return True 367 | else: 368 | return False 369 | 370 | # print(more_vowels('liizi')) 371 | 372 | 373 | # C-4.19 Write a short recursive Python function that rearranges a sequence of in- 374 | # teger values so that all the even values appear before all the odd values. 375 | 376 | def sort_even_odd(S): 377 | idxs = [] 378 | def helper(S, n): 379 | if n == len(S): 380 | return 381 | elif S[n]%2 == 0: 382 | idxs.append(n) 383 | helper(S, n+1) 384 | helper(S, 0) 385 | for i in range(len(S)): 386 | if i not in idxs: 387 | idxs.append(i) 388 | for idx,elem in enumerate(idxs): 389 | idxs[idx] = S[elem] 390 | return idxs 391 | 392 | print(sort_even_odd([5,2, 6])) 393 | 394 | #P4.23 Implement a recursive function with signature find(path, filename) that 395 | # reports all entries of the file system rooted at the given path having the 396 | # given file name. 397 | 398 | def find(path, filename): 399 | results = [] 400 | def helper(path, filename): 401 | if os.path.isdir(path): 402 | for f in os.listdir(path): 403 | childpath = os.path.join(path, f) 404 | if f == filename: 405 | results.append(childpath) 406 | else: 407 | helper(childpath, filename) 408 | else: 409 | return None 410 | helper(path, filename) 411 | print(results) 412 | 413 | # find('/home/mathlizard/myblog/ekeleshian.github.io/', 'index.md') 414 | 415 | # Python’s os module provides a function with signature walk(path) that 416 | # is a generator yielding the tuple (dirpath, dirnames, filenames) for each 417 | # subdirectory of the directory identified by string path, such that string 418 | # dirpath is the full path to the subdirectory, dirnames is a list of the names 419 | # of the subdirectories within dirpath, and filenames is a list of the names 420 | # of non-directory entries of dirpath. For example, when visiting the cs016 421 | # subdirectory of the file system shown in Figure 4.6, the walk would yield 422 | # ( /user/rt/courses/cs016 , [ homeworks , programs ], [ grades ]) . 423 | # Give your own implementation of such a walk function. 424 | def walk(path): 425 | full_path = '/home/mathlizard/' + path 426 | fs = [] 427 | dirs = [] 428 | 429 | if os.path.isdir(full_path): 430 | for f in os.listdir(full_path): 431 | if os.path.isdir(full_path+'/'+f): 432 | dirs.append(f) 433 | else: 434 | fs.append(f) 435 | 436 | return (full_path, dirs, fs) 437 | 438 | print(walk('smartFont')) 439 | 440 | -------------------------------------------------------------------------------- /stacks_queues_deques.py: -------------------------------------------------------------------------------- 1 | #simple array-based stack implementation 2 | #to implement a stack using a python list, we use the adapter design pattern 3 | #stacks main methods with python equivalence in parenthesis: S.push(e) (L.append(e))and S.pop() (L.pop()), 4 | #we will also add S.top() (L[-1]), S.is_empty() (len(L) == 0), and len(S) (len(L)) for convenience. 5 | 6 | class Empty(Exception): 7 | """Error attemting to access an element from an empty container""" 8 | pass 9 | class Full(Empty): 10 | pass 11 | 12 | class ArrayStack: 13 | """LIFO stack implementation using apython list as underlying storage""" 14 | def __init__(self, maxlen=None): 15 | if maxlen: 16 | self._data = [None]*maxlen 17 | else: 18 | self._data = [] 19 | self._maxlen = 10 20 | 21 | def __len__(self): 22 | return len(self._data) 23 | 24 | def is_empty(self): 25 | return len(self._data) == 0 26 | 27 | def push(self, e): 28 | if len(self._data) >= self._maxlen: 29 | raise Full('Stack is full') 30 | self._data.append(e) 31 | 32 | def top(self): 33 | if self.is_empty(): 34 | raise Empty('Stack is empty') 35 | return self._data[-1] 36 | 37 | def pop(self): 38 | if self.is_empty(): 39 | raise Empty('Stack is empty') 40 | return self._data.pop() 41 | 42 | from pdb import set_trace 43 | 44 | def is_matched_html(raw): 45 | """Return True if all HTML tags are properly matched; False otherwise""" 46 | stack = ArrayStack() 47 | fst = raw.find('<') 48 | while fst != -1: 49 | sec = raw.find('>', fst+1) 50 | if sec == -1: 51 | return False 52 | tag_idx = raw.find(' ', fst+1) 53 | tag = raw[fst+1:tag_idx] 54 | # set_trace() 55 | if not tag.startswith('/'): 56 | stack.push(tag) 57 | # set_trace() 58 | else: 59 | if stack.is_empty(): 60 | return False 61 | if tag[1:] != stack.pop(): 62 | # set_trace() 63 | return False 64 | fst = raw.find('<', sec+1) 65 | # set_trace() 66 | return stack.is_empty() 67 | 68 | 69 | 70 | # print(is_matched_html('blahblahblah')) 71 | 72 | class ArrayQueue: 73 | """FIFO queue implementation using a python list (w/ circular array design) as underlying storage""" 74 | DEFAULT_CAPACITY = 10 75 | 76 | def __init__(self): 77 | """Create an empty qqueue""" 78 | self._data = [None] * ArrayQueue.DEFAULT_CAPACITY 79 | self._size = 0 80 | self._front = 0 81 | 82 | def __len__(self): 83 | return self._size 84 | 85 | def is_empty(self): 86 | return self._size == 0 87 | 88 | def first(self): 89 | """Return but don't remove the element at front of q.""" 90 | if self.is_empty(): 91 | raise Empty('Queue is empty') 92 | return self._data[self._front] 93 | 94 | def dequeue(self): 95 | """Remove and reutrn the first element of the queue 96 | Raise Empty exception if the q is empty""" 97 | if self.is_empty(): 98 | raise Empty('Queue is empty') 99 | element = self.first() 100 | # set_trace() 101 | self._data[self._front] = None 102 | self._front = (self._front+1)%len(self._data) 103 | self._size -= 1 104 | return element 105 | # set_trace() 106 | 107 | def enqueue(self, e): 108 | """Add an element to the back of q""" 109 | if self._size == len(self._data): 110 | self._resize(2*len(self._data)) 111 | # set_trace() 112 | back_idx = (self._front+self._size)%len(self._data) 113 | self._data[back_idx] = e 114 | self._size += 1 115 | # set_trace() 116 | 117 | 118 | def _resize(self, cap): 119 | """Resize to a new list of capacity >= len(self)""" 120 | old = self._data 121 | self._data = [None]*cap 122 | walk = self._front 123 | # set_trace() 124 | for k in range(self._size): 125 | self._data[k] = old[walk] 126 | walk = (1+walk)%len(old) 127 | # set_trace() 128 | # set_trace() 129 | self._front = 0 130 | 131 | 132 | # queue = ArrayQueue() 133 | 134 | # for i in range(20): 135 | # queue.enqueue(i) 136 | 137 | # for i in range(5): 138 | # queue.dequeue() 139 | 140 | # for i in range(100,112): 141 | # queue.enqueue(i) 142 | 143 | # R-6.3 Implement a function with signature transfer(S, T) that transfers all ele- 144 | # ments from stack S onto stack T, so that the element that starts at the top 145 | # of S is the first to be inserted onto T, and the element at the bottom of S 146 | # ends up at the top of T. 147 | def transfer(stack_s, stack_t): 148 | for k in range(len(stack_s)): 149 | stack_t.push(stack_s.pop()) 150 | return stack_t 151 | 152 | stack_s = ArrayStack() 153 | for k in range(3): 154 | stack_s.push(k) 155 | 156 | stack_t = ArrayStack() 157 | 158 | for m in range(6,10): 159 | stack_t.push(m) 160 | 161 | 162 | # print(transfer(stack_s,stack_t).top()) 163 | 164 | # R-6.4 Give a recursive method for removing all the elements from a stack. 165 | def remove_all(stack): 166 | def helper(stack,stack_size): 167 | if len(stack) == 0: 168 | return stack 169 | stack.pop() 170 | if stack is None: 171 | stack = ArrayStack() 172 | return stack 173 | return helper(stack, len(stack)) 174 | stack = helper(stack,len(stack)) 175 | return stack 176 | # print(len(stack_s)) 177 | # print(len(remove_all(stack_s))) 178 | 179 | # R-6.5 Implement a function that reverses a list of elements by pushing them onto 180 | # a stack in one order, and writing them back to the list in reversed order. 181 | def reverse_elem(array): 182 | stack = ArrayStack() 183 | for i in range(len(array)): 184 | stack.push(array[i]) 185 | for i in range(len(array)): 186 | array[i] = stack.pop() 187 | return array 188 | 189 | # arr = [i for i in range(10)] 190 | # print(reverse_elem(arr)) 191 | 192 | # R-6.13 Suppose you have a deque D containing the numbers (1, 2, 3, 4, 5, 6, 7, 8), 193 | # in this order. Suppose further that you have an initially empty queue Q. 194 | # Give a code fragment that uses only D and Q (and no other variables) and 195 | # results in D storing the elements in the order (1, 2, 3, 5, 4, 6, 7, 8). 196 | from collections import deque 197 | # D = deque([i for i in range(1,9)]) 198 | # Q = ArrayQueue() 199 | # for i in range(len(D)): 200 | # print(D[i]) 201 | # n = len(D) 202 | # for i in range(n): 203 | # Q.enqueue(D.popleft()) 204 | # set_trace() 205 | 206 | # for i in range(n): 207 | # D.append(Q.dequeue()) 208 | # for i in range(n): 209 | # print(D[i]) 210 | 211 | # C-6.15 Suppose Alice has picked three distinct integers and placed them into a 212 | # stack S in random order. Write a short, straight-line piece of pseudo-code 213 | # (with no loops or recursion) that uses only one comparison and only one 214 | # variable x, yet that results in variable x storing the largest of Alice’s three 215 | # integers with probability 2/3. Argue why your method is correct. 216 | 217 | #if stack[0]>stack[1]: 218 | # return stack[2] 219 | #else: 220 | # return stack[2] 221 | #My method depicts the monty hall problem. Stack [0] has a 1/3 chance of 222 | #being correct. After knowing that stack[1] is less, there is still a 223 | #1/3 chance that stack[0] is the largest, but now there are 2 chances of 3 that 224 | # the third number is the largest. #the initial choice (which is determined 225 | #in the comparison) has only one chance in three being of being the largest, 226 | #while the third remaining number as two chances in three. 227 | 228 | # C-6.16 Modify the ArrayStack implementation so that the stack’s capacity is lim- 229 | # ited to maxlen elements, where maxlen is an optional parameter to the 230 | # constructor (that defaults to None). If push is called when the stack is at 231 | # full capacity, throw a Full exception (defined similarly to Empty). 232 | 233 | #See lines 9-39 234 | 235 | # C-6.17 In the previous exercise, we assume that the underlying list is initially 236 | # empty. Redo that exercise, this time preallocating an underlying list with 237 | # length equal to the stack’s maximum capacity. 238 | 239 | #see lines 9-39 240 | 241 | # C-6.19 In Code Fragment 6.5 we assume that opening tags in HTML have form 242 | # , as with
  • . More generally, HTML allows optional attributes 243 | # to be expressed as part of an opening tag. The general form used is 244 | # ; for example, 245 | # a table can be given a border and additional padding by using an opening 246 | # tag of . Modify Code Frag- 247 | # ment 6.5 so that it can properly match tags, even when an opening tag 248 | # may include one or more such attributes. 249 | 250 | #see lines 44-66 251 | 252 | # C-6.20 Describe a nonrecursive algorithm for enumerating all permutations of the 253 | # numbers {1, 2, . . . , n} using an explicit stack. 254 | # stack = ArrayStack() 255 | 256 | # orig_nums = 5 257 | # for i in range(orig_nums): 258 | # stack.push(i) 259 | # permutations = [] 260 | # cursor = 0 261 | # while cursor < orig_nums: 262 | # num = stack.pop() 263 | # result = [i for i in range(orig_nums)] 264 | # for i in range(orig_nums): 265 | # old = result[i] 266 | # result[i] = num 267 | # result[num] = old 268 | # permutations.append(result) 269 | # result = [idx for idx in range(orig_nums)] 270 | 271 | # cursor += 1 272 | 273 | # print(permutations) 274 | 275 | # C-6.21 Show how to use a stack S and a queue Q to generate all possible subsets 276 | # of an n-element set T nonrecursively. 277 | stack = ArrayStack() 278 | queue = ArrayQueue() 279 | # queue.enqueue(set()) 280 | T = [1,2,3, 4, 5] 281 | # for i in T: 282 | # stack.push(i) 283 | # queue.enqueue(i) 284 | 285 | # while stack.is_empty()==False: 286 | # cur = stack.pop() 287 | # set_trace() 288 | # for i in range(len(queue)): 289 | # a = queue.dequeue() 290 | # b = a | {cur} 291 | # queue.enqueue(a) 292 | # queue.enqueue(b) 293 | # set_trace() 294 | 295 | # while queue.is_empty()==False: 296 | # x = queue.dequeue() 297 | # print(x) 298 | # C-6.22 Postfix notation is an unambiguous way of writing an arithmetic expres- 299 | # sion without parentheses. It is defined so that if “(exp 1 ) op (exp 2 )” is a 300 | # normal, fully parenthesized expression whose operation is op, the postfix 301 | # version of this is “pexp 1 pexp 2 op”, where pexp 1 is the postfix version of 302 | # exp 1 and pexp 2 is the postfix version of exp 2 . The postfix version of a sin- 303 | # gle number or variable is just that number or variable. For example, the 304 | # postfix version of “((5 + 2) ∗ (8 − 3))/4” is “5 2 + 8 3 − ∗ 4 /”. Describe 305 | # a nonrecursive way of evaluating an expression in postfix notation. 306 | 307 | 308 | expr = "5 2 + 8 3 - * 4 / " 309 | step = 0 310 | idx = expr.find(' ', step) 311 | while idx != -1: 312 | char = expr[:idx] 313 | expr = expr[idx+1:].strip() 314 | queue.enqueue(char) 315 | idx = expr.find(' ',0) 316 | if len(expr) != 0: 317 | queue.enqueue(expr[0]) 318 | 319 | 320 | 321 | expr = '' 322 | while queue.is_empty() == False: 323 | elem = queue.dequeue() 324 | if elem not in {'+', '-', '*', '/'}: 325 | expr += elem + ' ' 326 | elif len(stack) < 2: 327 | numbers = expr.strip().split(' ') 328 | if len(numbers) < 2: 329 | answer = str(eval(stack.pop() + elem + numbers[0])) 330 | stack.push(answer) 331 | else: 332 | answer = str(eval(numbers[0]+elem+numbers[1])) 333 | stack.push(answer) 334 | expr = '' 335 | else: 336 | numbers = [] 337 | for i in range(2): 338 | numbers.append(stack.pop()) 339 | answer = str(eval(numbers[0]+elem+numbers[1])) 340 | stack.push(answer) 341 | # set_trace() 342 | 343 | # print(stack.pop()) 344 | 345 | # P-6.32 Give a complete ArrayDeque implementation of the double-ended queue 346 | # ADT as sketched in Section 6.3.2. 347 | # P-6.33 Give an array-based implementation of a double-ended queue supporting 348 | # all of the public behaviors shown in Table 6.4 for the collections.deque 349 | # class, including use of the maxlen optional parameter. When a length- 350 | # limited deque is full, provide semantics similar to the collections.deque 351 | # class, whereby a call to insert an element on one end of a deque causes an 352 | # element to be lost from the opposite side. 353 | 354 | 355 | class ArrayDeque(ArrayQueue): 356 | def __init__(self): 357 | super().__init__() 358 | 359 | def add_first(self, e): 360 | """call may need to wrap around beginning of array 361 | self._front = (self._front - 1)%len(self._data) 362 | """ 363 | if self._size == self.DEFAULT_CAPACITY: 364 | self.delete_last() 365 | # set_trace() 366 | self._front = (self._front - 1)%len(self._data) 367 | self._data[self._front] = e 368 | self._size += 1 369 | 370 | 371 | def add_last(self, e): 372 | """will be like enqueue""" 373 | if self._size == self.DEFAULT_CAPACITY: 374 | self.delete_first() 375 | 376 | back_idx = (self._front+self._size)%len(self._data) 377 | self._data[back_idx] = e 378 | self._size += 1 379 | 380 | def delete_first(self): 381 | """Just like dequeue""" 382 | return self.dequeue() 383 | 384 | def delete_last(self): 385 | """similar to dequeue but at end of queue""" 386 | if self.is_empty(): 387 | raise Empty('Queue is empty') 388 | element = self.last() 389 | self._data[(self._front + self._size)%len(self._data)] = None 390 | self._size -= 1 391 | return element 392 | 393 | def last(self): 394 | return self._data[(self._front + self._size - 1)%len(self._data)] 395 | 396 | 397 | D = ArrayDeque() 398 | 399 | for i in range(10): 400 | D.add_last(i) 401 | 402 | for i in range(11,15): 403 | D.add_first(i) 404 | 405 | print(len(D)) 406 | print(D.first()) 407 | print(D.last()) 408 | # # set_trace() 409 | 410 | # D.delete_last() 411 | # for i in range(2): 412 | # D.delete_first() 413 | 414 | # print(len(D)) 415 | # print(D.first()) 416 | # print(D.last()) --------------------------------------------------------------------------------