I want to be able to use the generators to assign a table and a seat number with meal selections to each guest. I need to create a generator function that will take in as input the guest's dictionary and use the connected generator seating/food selection generators I created in the earlier lines of code. This function I need to write should yield a tuple of the guest name and the next value from the connected generator.
I perceive that a correct answer would look something like this: (Tim, "Chicken, "Table 1", f"Seat 1) and then for the next guest
(Tonya, "Chicken", "Table 1", f"Seat 2)
My specific question as to what is wrong with my code is why my for a loop at the very end of the code is not iterable. How can I make it iterable? Did I approach this problem the correct way?
In terms of Codecademy's instructions it seems that they want me to understand how to use generators and combine other generators with yield from statements. They have provided the following hint:
The next() function can be called within the yield statement similar to the following:
yield (name, next(another_generator))
I have tried to create a function guest_assignment() which would utilize a for loop targeting the keys in the guest's dictionary. It would then yield the name and use the next() function to place a guest at the next table. Instead, I got a TypeError: function object is not iterable.
As for research steps I have looked on Codecademy's forums, I have looked at prior stack overflow examples, and I have looked at the type error but I do not know if I need to make the guest_assignment iterable or if I have made a mistake in making my generators or somewhere along the way.
Error Message:
Traceback (most recent call last):
File "script.py", line 72, in <module>
print(tuple(seating_chart))
File "script.py", line 68, in combined_generators
yield from combined_tables
TypeError: 'function' object is not iterable
Provided Code:
Tim,22
Tonya,45
Mary,12
Ann,32
Beth,20
Sam,5
Manny,76
Kenton,15
Kenny,27
Dixie,46
Mallory,32
Julian,4
Edward,71
Rose,65
script.py
guests = {}
def read_guestlist(file_name):
text_file = open(file_name,'r')
while True:
line_data = text_file.readline().strip().split(",")
if len(line_data) < 2:
# If no more lines, close file
text_file.close()
break
name = line_data[0]
age = int(line_data[1])
guests[name] = age
guest_addition = yield name # modified code to create variable
if guest_addition is not None: # we know a guest addition has been passed in.
split_guest_addition = guest_addition.split(",")
# we need to split the ["Jane, 35"] data first and store it in a variable.
new_guest_name = split_guest_addition[0]
# we take the newly created variable and store the 0 index into new guest name
new_guest_age = int(split_guest_addition[1])
# we take the newly creted variable and store the 1 index into the new guest age variable
# we want to place jane in the guest dictionary. how did they do this look at line 12
guests[new_guest_name] = new_guest_age # added new guest here
#input the name of the dictionary we want to update.
# new_guest name is the "Jane" or the guest name that we want to add. it is the key that we want to input into the dictionary. and it updates the dictionary even though it is not being set on the right hand side of the equals sign. the new_guest_age value is the value side of the dictionary. we update the key and the value on the same line of code!
guest_list_generator = read_guestlist("guest_list.txt")
guest_index = 0
for guest in guest_list_generator:
# print(guest)
guest_index += 1
if guest_index >= 10:
break
guest_list_generator.send("Jane,35")
for guest in guest_list_generator:
print(guest) # Jane does not need to be on the list in this case per Mark.
guest_list_21_and_over = (name for name,age in guests.items() if age >= 21)
print(list(guest_list_21_and_over))
def table_one():
for iteration in range(1,6):
yield ("Chicken", "Table 1", f"Seat {iteration}")
print("\n")
def table_two():
for iteration in range(1,6):
yield("Beef", "Table 2", f"Seat {iteration}")
print("\n")
def table_three():
for iteration in range(1,6):
yield("Fish", "Table 3", f"Seat {iteration}")
print("\n")
def combined_tables():
yield from table_one()
yield from table_two()
yield from table_three()
all_tables = combined_tables()
def guest_assignment():
for name in guests.keys():
yield name, next(combined_tables())
def combined_generators():
yield from combined_tables
yield from guest_assignment
seating_chart = combined_generators()
print(tuple(seating_chart))
First of all, you don't need generators to do what you want to do.
E.g. this code does exactly what you gave as example:
def read(path, sep=","):
res = []
with open(path) as f:
for line in f:
if sep in line:
name, num = line.split(sep)
res.append((name, int(num)))
return res
def assign_seats(guests, menus=["Chicken", "Beef", "Fish"], n_tables=5, n_seats=3):
res = []
for menu in menus:
for table_num in range(1, n_tables+1):
res.append((menu, f"Table {table_num}", f"Seat {table_num}"))
return ((name, menu, table, seat) for ((name, age), (menu, table, seat)) in zip(guests, res))
list(assign_seats(res))
It returns:
[('Tim', 'Chicken', 'Table 1', 'Seat 1'),
('Tonya', 'Chicken', 'Table 2', 'Seat 2'),
('Mary', 'Chicken', 'Table 3', 'Seat 3'),
('Ann', 'Chicken', 'Table 4', 'Seat 4'),
('Beth', 'Chicken', 'Table 5', 'Seat 5'),
('Sam', 'Beef', 'Table 1', 'Seat 1'),
('Manny', 'Beef', 'Table 2', 'Seat 2'),
('Kenton', 'Beef', 'Table 3', 'Seat 3'),
('Kenny', 'Beef', 'Table 4', 'Seat 4'),
('Dixie', 'Beef', 'Table 5', 'Seat 5'),
('Mallory', 'Fish', 'Table 1', 'Seat 1'),
('Julian', 'Fish', 'Table 2', 'Seat 2'),
('Edward', 'Fish', 'Table 3', 'Seat 3'),
('Rose', 'Fish', 'Table 4', 'Seat 4')]
To the code:
You use a generator to have flexible assignment between table, seat, and menu with the guests.
But a zip()
combined with destructuring would do it.
A generator however would have the thinnest memory footprint. But for such a small amount of guests - and be it 1000, it would not mean any problem for any normal personal computer to treat them as lists.
To reading the file:
Using with open(filepath) as f:
and then looping over the lines is much more pythonic than using a while loop.
In practical life, one would even do just:
import pandas as pd
df = pd.read_csv("list.txt", header=None)
df.columns = ["name", "age"]
# df would contain:
name age
0 Tim 22
1 Tonya 45
2 Mary 12
3 Ann 32
4 Beth 20
5 Sam 5
6 Manny 76
7 Kenton 15
8 Kenny 27
9 Dixie 46
10 Mallory 32
11 Julian 4
12 Edward 71
13 Rose 65
And df.values
would make a list of list out of it.
(Okay, a numpy array, but one can use it like a list of list).
As a function this would be:
import pandas as pd
def read(filepath):
df = pd.read_csv(filepath)
df.columns = ["name", "age"]
return df.to_numpy()
There is a logical error in your script: Table 1 always has Seat 1, Table 2 always has Seat 2 etc. I don't think this is what you intend. So actually each table should have 3 seats (3 menus) or should be filled up to k seats.
I think your way to use generators is not very nice, because you have to hardcode nearly every seat. So your code is not scalable. If you decide to add a new dish to your menu, you have to re-write your entire program. That is you should rather use nested for-loops like I did.
Perhaps you don't even need nested for-loops. Perhaps you want the program just to map the guests - to a certain amount of tables with seats. Perhaps you want them to be distributed randomly. In this case, it is better to first construct all seats with tables and menus - and assigning every seat an index number. Then taking the guests list, and assign randomly an index to each guest.
This could be done like this:
# use one the read functions above.
guests = read("list.txt")
# let's say there are 5 tables with each 3 seats:
def get_table_seats(n_tables, n_seats):
return [(i_table, i_seat) for i_table in range(1, n_tables+1) for i_seat in range(1, n_seats+1)]
table_seats = get_table_seats(n_tables=5, n_seats=3)
# table_seats contain now all combinations of table and seat numbers:
[(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 2),
(2, 3),
(3, 1),
(3, 2),
(3, 3),
(4, 1),
(4, 2),
(4, 3),
(5, 1),
(5, 2),
(5, 3)]
# now, we write a function which assigns randomly menus
# (we could say seat 1, 2, 3 get each of the menus)
# but we could also give each seat an index using `enumerate()`
def index_table_seat(table_seat):
return [(i, i_table, i_seat) for i, (i_table, i_seat) in enumerate(table_seat)]
i_table_seat = index_table_seat(table_seats)
# which holds:
[(0, 1, 1),
(1, 1, 2),
(2, 1, 3),
(3, 2, 1),
(4, 2, 2),
(5, 2, 3),
(6, 3, 1),
(7, 3, 2),
(8, 3, 3),
(9, 4, 1),
(10, 4, 2),
(11, 4, 3),
(12, 5, 1),
(13, 5, 2),
(14, 5, 3)]
indexes = [i for i, table, seat in i_table_seat]
# in a normal world, guests would choose their menu
# so the menu would be already assigned in the guests list.
# but okay, let's pretend we assign every index a menu
# and the randomize the order.
# let's use your generator idea and take a list of menus
# and make them cycling:
def cycle(lst):
while True:
for el in lst:
yield el
menu = ["Chicken", "Beef", "Fish"]
menu_ = cycle(menu)
# from now on with `next(menu_)` you can cycle through the menu list
# ad infinitum.
import random
def assign_randomized_menu(indexes, menu):
menu_ = cycle(menu)
# menu_ behaves like an endless list in zip()
# we use random sample to randomize order of indexes
return {index: menu for index, menu in zip(random.sample(indexes, k=len(indexes)), menu_)}
# we use a dictionary, to be able to recall for every index its menu
# now, we assign every guest randomized to an index:
def assign_index_to_guest(indexes, guests):
return {index: guest for index, guest in zip(random.sample(indexes, k=len(indexes)), guests)}
index2guest = assign_index_to_guest(indexes, guests)
index2menu = assign_randomized_menu(indexes, menu)
def assign_randomized(guests, i_table_seat, menu):
indexes = [i for i, table, seat in i_table_seat]
index2guest = assign_index_to_guest(indexes, guests)
index2menu = assign_randomized_menu(indexes, menu)
return [(i, index2guest.get(i, [None, None])[0], index2menu[i], table, seat) for i, table, seat in i_table_seat]
# we could make it return a dictionary
# and make a data frame out of it
def assign_randomized(guests, i_table_seat, menu):
indexes = [i for i, table, seat in i_table_seat]
index2guest = assign_index_to_guest(indexes, guests)
index2menu = assign_randomized_menu(indexes, menu)
return [{'index': i, 'name': index2guest.get(i, [None, None])[0], 'menu': index2menu[i], 'table': table, 'seat': seat} for i, table, seat in i_table_seat]
pd.DataFrame(assign_randomized(guests, i_table_seat, menu))
# to make it reproducible, you can give the random machine a seed:
random.seed(1)
pd.DataFrame(assign_randomized(guests, i_table_seat, menu))
# then the result will always be:
index name menu table seat
0 0 Edward Fish 1 1
1 1 Ann Chicken 1 2
2 2 Tim Fish 1 3
3 3 Kenny Beef 2 1
4 4 Beth Beef 2 2
5 5 Dixie Fish 2 3
6 6 Mallory Fish 3 1
7 7 Manny Chicken 3 2
8 8 Kenton Beef 3 3
9 9 Tonya Beef 4 1
10 10 Rose Fish 4 2
11 11 Sam Chicken 4 3
12 12 Mary Chicken 5 1
13 13 Julian Beef 5 2
14 14 None Chicken 5 3