SHOW:
|
|
- or go back to the newest paste.
1 | # ICS 31, Thursday 9 November 2017 | |
2 | ||
3 | """ | |
4 | Control structures alter the FLOW OF CONTROL | |
5 | Sequence | |
6 | Repetition: for while recursion (not ICS 31) | |
7 | Modularity/function call and return | |
8 | ||
9 | Short-circuiting the flow of control: | |
10 | break statement: Takes you out of the current loop (down to next statement after | |
11 | loop body) | |
12 | return statement (only from within a function): Ejector seat from the whole function, | |
13 | back immediately to the code that called the function. | |
14 | continue statement: Out of the current ITERATION. Skips to end of body but repeats | |
15 | the next iteration (next list item or whatever). | |
16 | ||
17 | for i in s: | |
18 | stmt 1 | |
19 | stmt 2 | |
20 | if whatever(): | |
21 | continue # skip stmt3 and stmt4, then go check if you're done repeating. | |
22 | # If there are more repetitions left, do the next one. | |
23 | stmt 3 | |
24 | stmt 4 | |
25 | stmt 5 # We don't get here until we've processed the last i in s. | |
26 | ||
27 | # The continue statement is syntactic sugar for the following if statement: | |
28 | ||
29 | for i in s: | |
30 | stmt 1 | |
31 | stmt 2 | |
32 | if not whatever(): | |
33 | stmt 3 | |
34 | stmt 4 | |
35 | stmt 5 | |
36 | ||
37 | """ | |
38 | ||
39 | ### RESTAURANTS PROGRAM -- ICS 31, FALL 2017 -- Version with external files. | |
40 | ||
41 | from collections import namedtuple | |
42 | ||
43 | Restaurant = namedtuple('Restaurant', 'name cuisine phone dish price') | |
44 | ||
45 | ||
46 | def restaurants(): | |
47 | ''' Main program (controller): Create and maintain a database of restaurants ''' | |
48 | print("Welcome to the Restaurants Program!") | |
49 | print() | |
50 | choice = """If you'd like to read restaurants from an existing file, | |
51 | type the name of that file; if not, just hit RETURN: """ | |
52 | filename = input(choice) | |
53 | if filename == "": | |
54 | our_rests = Collection_new() # Gives us an empty list, respects the abstraction barrier | |
55 | else: | |
56 | our_rests = fill_collection_from_file(filename) | |
57 | our_rests = handle_commands(our_rests) | |
58 | write_file_from_collection(our_rests) | |
59 | print() | |
60 | print("Thank you. Good-bye.") | |
61 | return | |
62 | ||
63 | ||
64 | def handle_commands(L: 'list of Restaurant') -> 'list of Restaurant': | |
65 | pass ## Lets us build a PROGRAM STUB, to be filled in later. | |
66 | ||
67 | ||
68 | MENU = """ | |
69 | Restaurant Collection Program --- Choose one | |
70 | a: Add a new restaurant to the collection | |
71 | r: Remove a restaurant from the collection | |
72 | s: Search the collection for selected restaurants | |
73 | p: Print all the restaurants | |
74 | q: Quit | |
75 | """ | |
76 | ||
77 | # It's legal to have this second version of handle_commands() here | |
78 | # in the code, not commented out. As long as the last version is the | |
79 | # version we want, it's the version we get; later definitions just | |
80 | # supersede the previous ones. (We keep the versions here so you can | |
81 | # see how they developed. This is fine in class notes. Nobody would | |
82 | # do it in commercial software, although there are "version control" | |
83 | # tools that let you save historical information.) | |
84 | ||
85 | # Next version: Read command and just echo it | |
86 | ||
87 | ||
88 | ## In the final version of handle_commands, we made a couple | |
89 | ## of changes that are just for clarity or showing alternatives: | |
90 | ## -- We changed the parameter name in handle_commands from L to | |
91 | ## # RC (for Restaurant Collection). Nothing outside of handle_commands | |
92 | ## needed to change. | |
93 | ## -- For 'list of Restaurant' as a type annotation, we substituted | |
94 | ## [Restaurant]. Either form is fine for us. | |
95 | ||
96 | ||
97 | def handle_commands(RC: [Restaurant]) -> [Restaurant]: | |
98 | ''' Handle the commands the user enters, affecting the collection. ''' | |
99 | while True: | |
100 | command = input(MENU) | |
101 | if command == 'q': | |
102 | break # Bail out if user wants to quit | |
103 | elif command == 'a': | |
104 | # print("You want to add a new restaurant") | |
105 | new_rest = Restaurant_get_info() | |
106 | RC = Collection_add(RC, new_rest) | |
107 | elif command == 'r': | |
108 | # print("You want to remove a restaurant") | |
109 | name_to_remove = input("Please enter the name of the " + | |
110 | "restaurant to remove: ") | |
111 | RC = Collection_remove_by_name(RC, name_to_remove) | |
112 | elif command == 's': | |
113 | # print("You want to search for a restaurant") | |
114 | name_to_find = input("Please enter the name of the " + | |
115 | "restaurant to search for: ") | |
116 | print(Collection_to_str(Collection_search_by_name(RC, name_to_find))) | |
117 | # No change to RC itself | |
118 | elif command == 'p': | |
119 | # print("You want to print the restaurants") | |
120 | print(Collection_to_str(RC)) | |
121 | else: | |
122 | print("You typed '", command, "'. ", | |
123 | "That isn't a valid command. ", | |
124 | "Please try again.", sep='') | |
125 | ## Note that the break statement takes us here, the statement after the loop. | |
126 | return RC | |
127 | ||
128 | ||
129 | ######################## | |
130 | ## MODEL part of the program: | |
131 | ## -- Restaurant (built with namedtuple) | |
132 | ## -- Collection (built as a list of Restaurant objects) | |
133 | ######################## | |
134 | ||
135 | ## RESTAURANTS | |
136 | ||
137 | # For testing: | |
138 | R1 = Restaurant('Taillevent', 'French', '00-11-22-33-44-55', 'Escargots', 23.50) | |
139 | #print(R1) | |
140 | R2 = Restaurant('Thai Dishes', 'Thai', '334-4433', 'Mee Krob', 8.95) | |
141 | R3 = Restaurant('Thai Touch', 'Thai', '444-3333', 'Larb Gai', 11.00) | |
142 | ||
143 | def Restaurant_print(R: Restaurant) -> None: # Just prints | |
144 | ''' Print a human-readable Restaurant ''' | |
145 | print('Name: ', R.name) | |
146 | print('Cuisine: ', R.cuisine) | |
147 | print('Phone: ', R.phone) | |
148 | print('Dish: ', R.dish) | |
149 | print('Price: $', R.price) | |
150 | return | |
151 | # Restaurant_print(R1) | |
152 | ||
153 | # We can't use assert statements with a function that prints its output | |
154 | # rather than returning a value. When we call it, it prints; but that | |
155 | # means that we have to print to test it, and assert doesn't do anything | |
156 | # about printed output. | |
157 | # So here's what we'll do, both for added flexibility and to enable | |
158 | # using assert statements,we'll write a function to RETURN A STRING | |
159 | # containing the restaurant info. That gives to the person who CALLS | |
160 | # our function the choice of how to use that string. | |
161 | ||
162 | def Restaurant_to_str(R: Restaurant) -> str: | |
163 | ''' Create a string with the restaurant info in readable form ''' | |
164 | return 'Name: ' + R.name + '\n' + \ | |
165 | 'Cuisine: ' + R.cuisine + '\n' + \ | |
166 | 'Phone: ' + R.phone + '\n' + \ | |
167 | 'Dish: ' + R.dish + '\n' + \ | |
168 | 'Price: $' + str(R.price) + '\n\n' | |
169 | # The backslashes above tell Python there's more to this statement on the next line | |
170 | #print(Restaurant_to_str(R3)) | |
171 | assert Restaurant_to_str(R3) == """Name: Thai Touch | |
172 | Cuisine: Thai | |
173 | Phone: 444-3333 | |
174 | Dish: Larb Gai | |
175 | Price: $11.0 | |
176 | ||
177 | """ | |
178 | ||
179 | """ | |
180 | We could write out restaurants in the form above and read them back in, | |
181 | stripping off the labels. But more compact and more common would be to use a | |
182 | TAB-DELIMITED string. | |
183 | """ | |
184 | ||
185 | def Restaurant_to_tabbed_string(r: Restaurant) -> str: | |
186 | ''' Return a string containing the restaurant's fields, | |
187 | separated by tabs | |
188 | ''' | |
189 | return r.name + "\t" + r.cuisine + "\t" + r.phone + "\t" + r.dish + "\t" + str(r.price) | |
190 | assert Restaurant_to_tabbed_string(R3) == "Thai Touch\tThai\t444-3333\tLarb Gai\t11.0" | |
191 | ||
192 | # If you try typing this assertion, you'll have to get every single space and | |
193 | # character perfect before the equality is true and the assertion passes. | |
194 | ||
195 | # We'll learn how to print dollars-and-cents amounts perfectly in a few weeks. | |
196 | ||
197 | # In Python you can continue a statement from one line to the next with backslashes, | |
198 | # as above. But you can omit the backslashes if you're typing something that's in | |
199 | # parentheses or square brackets or similar paired symbols, because Python "knows" | |
200 | # there's more to expect when it hasn't seen the matching (closing) symbol. The | |
201 | # version below works just the same way as the one above. | |
202 | ||
203 | def Restaurant_to_str(R: Restaurant) -> str: | |
204 | ''' Create a string with the restaurant info in readable form ''' | |
205 | return ('Name: ' + R.name + '\n' + | |
206 | 'Cuisine: ' + R.cuisine + '\n' + | |
207 | 'Phone: ' + R.phone + '\n' + | |
208 | 'Dish: ' + R.dish + '\n' + | |
209 | 'Price: ' + f'${R.price:0.2f}' + '\n\n') | |
210 | #print(Restaurant_to_str(R3)) | |
211 | assert Restaurant_to_str(R3) == """Name: Thai Touch | |
212 | Cuisine: Thai | |
213 | Phone: 444-3333 | |
214 | Dish: Larb Gai | |
215 | Price: $11.00 | |
216 | ||
217 | """ | |
218 | ||
219 | def Restaurant_get_info() -> Restaurant: | |
220 | ''' Return a Restaurant created from the user's responses ''' | |
221 | n = input("Please enter the restaurant's name: ") | |
222 | c = input("Please enter the kind of food served: ") | |
223 | ph = input("Please enter the phone number: ") | |
224 | d = input("Please enter the name of the best dish: ") | |
225 | p = input("Please enter the price of that dish: ") | |
226 | return Restaurant(n, c, ph, d, float(p)) | |
227 | #R4 = Restaurant_get_info() | |
228 | #print(R4) | |
229 | #print(Restaurant_to_str(R4)) | |
230 | ||
231 | ||
232 | ## COLLECTION | |
233 | ||
234 | # A collection of Restaurants is built as a list of Restaurant objects. | |
235 | ||
236 | # We might decide some day to build it differently, though. Real software | |
237 | # is often revised, possibly to reflect changed circumstances (like adding a | |
238 | # home page for each Restaurant) or to make the program more efficient. | |
239 | # One way of revising is to change the underlying data structure---using a | |
240 | # dictionary instead of a list, for example. We aren't yet ready to make | |
241 | # that kind of design decision, but we can write our code to facilitate it. | |
242 | ||
243 | # When we worked with graphics canvases using tkinter, the information we | |
244 | # had was the names of the functions we could call, the number and type of | |
245 | # each function's arguments, and an English description of what the function | |
246 | # does. We did NOT have the entire function definition. The author of tkinter | |
247 | # promises us that if we call the tkinter functions according to the information | |
248 | # provided, they will work as described, even if the author changes the internal | |
249 | # details. We don't need to know the details of the underlying code; we don't | |
250 | # want to have to think about it. The information the author gives us is called | |
251 | # an API (Application Programming Interface). It's a way of dividing up the | |
252 | # responsibility for a large program. Here we write an API for a Restaurant | |
253 | # Collection. That means we have a series of functions that manipulate | |
254 | # a collection. As long as they always behave the way we've said they will, | |
255 | # we can change the internal details (like using a list or something else). | |
256 | ||
257 | # That's why we describe everything in terms of a "collection"---that doesn't | |
258 | # limit us to one way of building it. | |
259 | ||
260 | TRC = [R1, R2, R3] # Test Restaurant Collection | |
261 | ||
262 | def Collection_new() -> 'list of Restaurants': | |
263 | ''' Create and return a new, empty collection (list) ''' | |
264 | return [ ] | |
265 | assert Collection_new() == [ ] | |
266 | ||
267 | def Collection_add(C: 'list of Restaurant', r: Restaurant) -> 'list of Restaurant': | |
268 | ''' Return the colection with the new restaurant added ''' | |
269 | C.append(r) | |
270 | return C | |
271 | # Alternative: return C + [r] | |
272 | assert Collection_add(Collection_new(), R1) == [R1] | |
273 | assert Collection_add([R2, R3], R1) == [R2, R3, R1] | |
274 | ||
275 | def Collection_to_str(C: 'list of Restaurant') -> str: | |
276 | ''' Return a string representing the whole collection ''' | |
277 | result = "" | |
278 | for r in C: | |
279 | result = result + Restaurant_to_str(r) | |
280 | return result | |
281 | assert Collection_to_str(Collection_new()) == "" | |
282 | assert Collection_to_str(TRC) == \ | |
283 | Restaurant_to_str(R1) + Restaurant_to_str(R2) + Restaurant_to_str(R3) | |
284 | ||
285 | def Collection_search_by_name(C: 'list of Restaurant', looking_for: str) \ | |
286 | -> 'list of Restaurant': | |
287 | ''' Return a collection containing those restaurants in the first parameter | |
288 | that match the name you're looking for. ''' | |
289 | result = [ ] | |
290 | for r in C: | |
291 | if r.name == looking_for: | |
292 | result.append(r) | |
293 | return result | |
294 | assert Collection_search_by_name(TRC, "Taillevent") == [R1] | |
295 | assert Collection_search_by_name(TRC, "McDonald's") == [ ] | |
296 | assert Collection_search_by_name([R2, R3, R2, R1], "Thai Dishes") == [R2, R2] | |
297 | ||
298 | def Collection_remove_by_name(C: 'list of Restaurant', to_remove: str) \ | |
299 | -> 'list of Restaurant': | |
300 | ''' Return collection with the named restaurant(s) deleted. ''' | |
301 | result = [ ] | |
302 | for r in C: | |
303 | if r.name != to_remove: | |
304 | result.append(r) # Save the ones we *don't* want to remove | |
305 | return result | |
306 | ## Many combinations to test | |
307 | # Remove from the empty list [still empty] | |
308 | assert Collection_remove_by_name([], "Taillevent") == [ ] | |
309 | # Remove item not on list at all | |
310 | assert Collection_remove_by_name(TRC, "McDonald's") == TRC | |
311 | # Remmove item from list, result empty | |
312 | assert Collection_remove_by_name([R3], R3.name) == [ ] | |
313 | # Remove first item from list | |
314 | assert Collection_remove_by_name(TRC, "Taillevent") == [R2, R3] | |
315 | # Remove last item on list | |
316 | assert Collection_remove_by_name(TRC, "Thai Touch") == [R1, R2] | |
317 | # Remove from middle of list | |
318 | assert Collection_remove_by_name(TRC, "Thai Dishes") == [R1, R3] | |
319 | # Remove multiple occurrences of name | |
320 | assert Collection_remove_by_name([R3, R2, R3, R1, R3], | |
321 | "Thai Touch") == [R2, R1] | |
322 | ||
323 | def write_file_from_collection(C: 'list of Restaurant') -> None: # Side effect: write a file | |
324 | ''' Write a file called restaurants.txt, tab-delimited | |
325 | ''' | |
326 | outfile = open('restaurants.txt', 'w') | |
327 | for r in C: | |
328 | outfile.write(Restaurant_to_tabbed_string(r) + "\n") | |
329 | outfile.close() | |
330 | return | |
331 | ||
332 | def fill_collection_from_file(filename:str) -> 'list of Restaurant': | |
333 | ''' Read restaurant info from file; return collection. | |
334 | File has one line for each restaurant, with fields delimited by tabs. | |
335 | ''' | |
336 | result = [] | |
337 | infile = open(filename, 'r') | |
338 | for line in infile: | |
339 | field_list = line.split("\t") | |
340 | new_rest = Restaurant(field_list[0], field_list[1], field_list[2], | |
341 | field_list[3], float(field_list[4])) | |
342 | result.append(new_rest) | |
343 | return result | |
344 | ||
345 | ## Now we go back to create the final version of handle_commands() | |
346 | ||
347 | ## At the very bottom, we get everything started: | |
348 | - | restaurants() |
348 | + | restaurants() |
349 | ||
350 | ||
351 | ||
352 | ||
353 | ||
354 | # ICS 31, Thursday 30 November 2017 | |
355 | ||
356 | """ | |
357 | Today: One more basic data structure built in to Python: the SET | |
358 | A set is a collection with no duplicates, where the order doesn't matter. | |
359 | Sets, like any data structure, fit some problems very well. | |
360 | E.g., the set of econ majors. | |
361 | """ | |
362 | s = {1, 2, 3, 4, 5} # same as {1, 2, 5, 3, 4} | |
363 | print(s) | |
364 | empty_dict = {} | |
365 | empty_set = {} | |
366 | print(type(empty_dict)) | |
367 | print(type(empty_set)) | |
368 | empty_set = set() | |
369 | print(type(empty_set)) | |
370 | ||
371 | t = {2, 4, 6, 8, 10} | |
372 | print(s, t) | |
373 | L = {2,3,4,1,2,3,3,5,2,4,2,2,3,4,5,1,2,3,4,1,2,3,1} | |
374 | print(L, L == s) | |
375 | ||
376 | q = {'apple', 23, ('p','q')} | |
377 | print(q) | |
378 | from collections import namedtuple | |
379 | Restaurant = namedtuple('Restaurant', 'name cuisine phone dish price') | |
380 | R = Restaurant('Noma', 'Modern Danish', '334-4433', 'Anchovy Abelskiver', 34.50) | |
381 | r = {2345, R, (1,2,3)} | |
382 | print(r) | |
383 | ||
384 | LL = [2,3,4,1,2,3,3,5,2,4,2,2,3,4,5,1,2,3,4,1,2,3,1] | |
385 | print(LL) | |
386 | unique = set(LL) | |
387 | print(unique) | |
388 | ||
389 | # Bulletproof assertion for remove_duplicats, | |
390 | # recognizing that the order may be shuffled: | |
391 | # assert sorted(duplicates_removed(LL)) == [1,2,3,4,5] | |
392 | ||
393 | ||
394 | ||
395 | ||
396 | # ICS 31, Tuesday 5 December 2017 | |
397 | ||
398 | from collections import namedtuple | |
399 | ||
400 | Restaurant = namedtuple('Restaurant', 'name cuisine phone dish price') | |
401 | # Restaurant attributes: name, kind of food served, phone number, | |
402 | # best dish, price of that dish | |
403 | ||
404 | R1 = Restaurant("Taillevent", "French", "343-3434", "Escargots", 24.50) | |
405 | R2 = Restaurant("La Tour D'Argent", "French", "343-3344", "Ris de Veau", 48.50) | |
406 | R3 = Restaurant("Pascal", "French", "333-4444", "Bouillabaisse", 32.00) | |
407 | R4 = Restaurant("Thai Touch", "Thai", "444-3333", "Mee Krob", 10.95) | |
408 | R5 = Restaurant("Thai Dishes", "Thai", "333-4433", "Paht Woon Sen", 8.50) | |
409 | R6 = Restaurant("Thai Spoon", "Thai", "334-3344", "Mussamun", 9.00) | |
410 | R7 = Restaurant("McDonald's", "Burgers", "333-4443", "Big Mac", 3.95) | |
411 | R8 = Restaurant("Burger King", "Burgers", "444-3344", "Whopper", 3.75) | |
412 | R9 = Restaurant("Wahoo's", "Fish Tacos", "443-4443", "Mahi Mahi Burrito", 7.50) | |
413 | R10 = Restaurant("In-N-Out Burger", "Burgers", "434-3344", "Cheeseburger", 2.50) | |
414 | R11 = Restaurant("The Shack", "Burgers", "333-3334", "Hot Link Burger", 4.50) | |
415 | R12 = Restaurant("Gina's", "Pizza", "334-4433", "Combo Pizza", 12.95) | |
416 | R13 = Restaurant("Peacock, Room", "Indian", "333-4443", "Rogan Josh", 12.50) | |
417 | R14 = Restaurant("Gaylord", "Indian", "333-3433", "Tandoori Chicken", 13.50) | |
418 | R15 = Restaurant("Mr. Chow", "Chinese", "222-3333", "Peking Duck", 24.50) | |
419 | R16 = Restaurant("Chez Panisse", "California", "222-3322", "Grilled Duck Breast", 25.00) | |
420 | R17 = Restaurant("Spago", "California", "333-2222", "Striped Bass", 24.50) | |
421 | R18 = Restaurant("Sriped Bass", "Seafood", "333-2233", "Cedar Plank Salmon", 21.50) | |
422 | R19 = Restaurant("Golden Pagoda", "Chinese", "232-3232", "Egg Foo Young", 8.50) | |
423 | R20 = Restaurant("Langer's", "Delicatessen", "333-2223", "Pastrami Sandwich", 11.50) | |
424 | R21 = Restaurant("Nobu", "Japanese", "335-4433", "Natto Temaki", 5.50) | |
425 | R22 = Restaurant("Nonna", "Italian", "355-4433", "Stracotto", 25.50) | |
426 | R23 = Restaurant("Jitlada", "Thai", "324-4433", "Paht Woon Sen", 15.50) | |
427 | R24 = Restaurant("Nola", "New Orleans", "336-4433", "Jambalaya", 5.50) | |
428 | R25 = Restaurant("Noma", "Modern Danish", "337-4433", "Birch Sap", 35.50) | |
429 | R26 = Restaurant("Addis Ababa", "Ethiopian", "337-4453", "Yesiga Tibs", 10.50) | |
430 | ||
431 | ||
432 | RL = [R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15, R16, | |
433 | R17, R18, R19, R20, R21, R22, R23, R24, R25, R26] | |
434 | ||
435 | """ | |
436 | DUPLICATE CODE IS BAD! | |
437 | Too long, cluttered, error-prone/inconsistent | |
438 | ||
439 | PLs give us a variety of ways to capture commonalities. | |
440 | Sometimes called "refactoring". | |
441 | """ | |
442 | ||
443 | def is_Thai(R: Restaurant) -> bool: | |
444 | ''' Return True if R's cuisine is Thai''' | |
445 | return R.cuisine == "Thai" | |
446 | assert is_Thai(R4) | |
447 | assert not is_Thai(R26) | |
448 | ||
449 | """ | |
450 | This function returns a boolean. We call functions that return booleans PREDICATES. | |
451 | So is_Thai() is a predicate that operates on Restaurants. | |
452 | """ | |
453 | ||
454 | def is_French(R: Restaurant) -> bool: | |
455 | ''' Return True of R's cuisine is French''' | |
456 | return R.cuisine == "French" | |
457 | assert is_French(R1) | |
458 | assert not is_French(R26) | |
459 | ||
460 | ## How do we capture what's in common here? Use a parameter. | |
461 | ||
462 | def matches_cuisine(R: Restaurant, c: str) -> bool: | |
463 | ''' Return True if R's cuisine matches c ''' | |
464 | return R.cuisine == c | |
465 | assert matches_cuisine(R26, 'Ethiopian') | |
466 | assert matches_cuisine(R1, 'French') | |
467 | assert not matches_cuisine(R26, 'Japanese') | |
468 | ||
469 | def is_cheap(R: Restaurant) -> bool: | |
470 | ''' Return True if R's price is $10 or less ''' | |
471 | return R.price <= 10 | |
472 | assert is_cheap(R5) | |
473 | assert is_cheap(Restaurant("","","","",10.00)) | |
474 | assert not is_cheap(R1) | |
475 | ||
476 | def all_Thai(L: 'list of Restaurant') -> 'list of Restaurant': | |
477 | ''' Return a list of those restaurants in L that serve Thai food ''' | |
478 | result = [] | |
479 | for R in L: | |
480 | if is_Thai(R): | |
481 | result.append(R) | |
482 | return result | |
483 | assert all_Thai(RL) == [R4, R5, R6, R23] | |
484 | assert all_Thai([]) == [] | |
485 | assert all_Thai([R19, R20, R21, R22]) == [] | |
486 | assert all_Thai([R19, R20, R23, R21, R22]) == [R23] | |
487 | ||
488 | def all_cheap(L: 'list of Restaurant') -> 'list of Restaurant': | |
489 | ''' Return a list of those restaurants in L that are cheap ''' | |
490 | result = [] | |
491 | for R in L: | |
492 | if is_cheap(R): | |
493 | result.append(R) | |
494 | return result | |
495 | assert all_cheap(RL) == [R5, R6, R7, R8, R9, R10, R11, R19, R21, R24] | |
496 | assert all_cheap([]) == [] | |
497 | assert all_cheap([R1, R2, R3, R4]) == [] | |
498 | assert all_cheap([R5, R6, R7, R8, R12, R13]) == [R5, R6, R7, R8] | |
499 | ||
500 | """ | |
501 | all_Thai and all_cheap are nearly identical, | |
502 | except for the predicate function they use to examine each restaurant. | |
503 | ||
504 | When you find yourself in real life copying, pasting, and tweaking code, | |
505 | look for an opportunity to refactor. | |
506 | """ | |
507 | def is_Peruvian(R: Restaurant) -> bool: | |
508 | ''' Return True of R's cuisine is Peruvian''' | |
509 | return R.cuisine == "Peruvian" | |
510 | assert is_Peruvian(Restaurant("x", "Peruvian", "x", "x", 0)) | |
511 | assert not is_Peruvian(R26) | |
512 | ||
513 | def all_matches(L: 'list of Restaurant', test: 'func restaurant -> bool') -> 'list of Restaurant': | |
514 | ''' Return a list of all the Restaurants in L that pass the test ''' | |
515 | result = [] | |
516 | for R in L: | |
517 | if test(R): | |
518 | result.append(R) | |
519 | return result | |
520 | ||
521 | assert all_matches(RL, is_cheap) == [R5, R6, R7, R8, R9, R10, R11, R19, R21, R24] | |
522 | assert all_matches(RL, is_Thai) == [R4, R5, R6, R23] | |
523 | assert all_matches([], is_Thai) == [] | |
524 | assert all_matches(RL, is_Peruvian) == [] | |
525 | ||
526 | ## With all_matches, we no longer need specific all_Thai, all_cheap, all_whatever. | |
527 | ## We do still need is_cheap, is_French, ... so far. | |
528 | ||
529 | """ | |
530 | This pattern of selecting some items from a list is called FILTERING. | |
531 | ||
532 | Suppose we want the NAMES of all the Thai restaurants | |
533 | (not the whole Restaurant object, which is what all_matches gives us) | |
534 | """ | |
535 | ||
536 | def extract_names(L: 'list of Restaurant') -> 'list of str': | |
537 | ''' Return a list of the names of each restaurant in L ''' | |
538 | result = [] | |
539 | for R in L: | |
540 | result.append(R.name) | |
541 | return result | |
542 | assert extract_names([]) == [] | |
543 | assert extract_names(all_matches(RL, is_Peruvian)) == [] | |
544 | assert extract_names(all_matches(RL, is_French)) == ['Taillevent', "La Tour D'Argent", 'Pascal'] | |
545 | ||
546 | ## Now let's extract (name, phone) tuples | |
547 | ||
548 | def extract_names_and_phones(L: 'list of Restaurant') -> 'list of (name,phone) tuples': | |
549 | ''' Return a list of tuples containing the name and phone of each restaurant in L ''' | |
550 | result = [] | |
551 | for R in L: | |
552 | result.append((R.name, R.phone)) | |
553 | return result | |
554 | assert extract_names_and_phones([]) == [] | |
555 | assert extract_names_and_phones(all_matches(RL, is_Peruvian)) == [] | |
556 | assert extract_names_and_phones(all_matches(RL, is_French)) == \ | |
557 | [('Taillevent', '343-3434'), | |
558 | ("La Tour D'Argent", '343-3344'), | |
559 | ('Pascal','333-4444')] | |
560 | ||
561 | def name_phone(R: Restaurant) -> '(name,phone) tuple': | |
562 | ''' Return a tuple (name, phone) from R ''' | |
563 | return (R.name, R.phone) | |
564 | ||
565 | def transform_each(L: 'list of Restaurant', op: 'func Restaurant -> X') -> 'list of X': | |
566 | ''' Return a list of items produced by applying op to each iteim in L''' | |
567 | result = [] | |
568 | for R in L: | |
569 | result.append(op(R)) | |
570 | return result | |
571 | assert transform_each(all_matches(RL, is_French), name_phone) == \ | |
572 | [('Taillevent', '343-3434'), | |
573 | ("La Tour D'Argent", '343-3344'), | |
574 | ('Pascal','333-4444')] | |
575 | ||
576 | """ | |
577 | The all_matches() function does FILTERING | |
578 | The transform_each() function does MAPPING | |
579 | ||
580 | In fact, Python gives us a predefined function filter() | |
581 | and one called map() to perform these tasks. (They return | |
582 | special objects that we can turn into lists, as we did with | |
583 | the dict methods keys(), values(), and items().) | |
584 | """ | |
585 | print('Cheap restaurants:', list(filter(is_cheap, RL))) | |
586 | print('Names and phones of cheap restaurants:', | |
587 | list(map(name_phone,list(filter(is_cheap, RL))))) | |
588 | # Filter (select) the cheap restaurants, then transform each into a name-phone tuple | |
589 | """ | |
590 | What lets us do this is that Python treats functions as data/objects, | |
591 | passing them to other functions. | |
592 | We call this using HIGHER-ORDER FUNCTIONS, functions that take or return other functions. | |
593 | We say that functions are FIRST-CLASS OBJECTS in Python. | |
594 | ||
595 | We've had to define a lot of little functions to use just once (is_cheap, is_French, ...). | |
596 | Wouldn't it be nice to define these little fuctions on the fly, | |
597 | right where we need them, instead of doing the whole 'def' thing? | |
598 | ||
599 | We can say | |
600 | x = 2 + 2 | |
601 | print(x) | |
602 | but if we're never going to use x again, we can also say | |
603 | print(2+2) | |
604 | We can do this same thing with functions. | |
605 | We do it using LAMBDA EXPRESSIONS. The value of a lambda expression is a function. | |
606 | ||
607 | [Lambda is a letter in the Greek allphabet. It's here because a branch | |
608 | of mathematical logic called the "lambda calculus" operates on functions. | |
609 | The lambda calculus was invented by Alonzo Church. The first programming | |
610 | language to use lambda expressions was LISP, invented by John McCarthy in | |
611 | 1958. Current dialects of LISP are Racket, Scheme, and Common Lisp. | |
612 | """ | |
613 | def double0(n: int) -> int: | |
614 | return 2 * n | |
615 | ||
616 | # Defines the name double0 to mean 'a function that returns 2 * its int argument' | |
617 | ||
618 | # The following is exactly equivalent for the name double: | |
619 | ||
620 | double = lambda n: n*2 | |
621 | ||
622 | print(double0(13)) | |
623 | print(double(13)) | |
624 | ||
625 | print((lambda n: 2*n)(17)) | |
626 | """ | |
627 | The value of (lambda n: 2*n) is "a function that returns 2 * its argument" | |
628 | The value of double is "a function that returns 2 * its argument" | |
629 | The value of double0 is "a function that returns 2 * its argument" | |
630 | They're interchangeable. | |
631 | ||
632 | """ | |
633 | Ethiopian_rests = all_matches(RL, lambda R: R.cuisine=='Ethiopian') | |
634 | print(Ethiopian_rests) | |
635 | # We don't need to define is_Ethiopian if we want to describe its | |
636 | # operation just here in this one place. | |
637 | # We call this "using ANONYMOUS LAMBDA", a lambda expression without a name. |