View difference between Paste ID: 94aLeRAG and qm7FfbH3
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.