I’m learning Python (and programming in general) by making small programs. Below is a basic shopping program which will return a list of items to buy based on the selected food.
I’d like to improve it and allow user to select several foods at once (e.g. user input would be “1, 2, 3”) and return a list of ingredients based on that.
What kind of approach should I take? I’m using Python 2.7 and here is my existing code:
mm_soup = ['minced meat', 'potatoes', 'frozen vegetable']
sunday_soup = ['chicken with bones', 'noodles', 'soup vegetable']
gulas = ['pork meat', 'food cream', 'potatoes', 'onion', 'frozen peas']
print "What would you like to cook on weekend?"
print "Here are the options:"
print "1. Minced Meat Soup"
print "2. Sunday Soup"
print "3. Gulas"
choose = raw_input("> ")
if choose == '1':
print "Buy", ", ".join(mm_soup) + "."
elif choose == '2':
print "Buy", ", ".join(sunday_soup) + "."
elif choose == '3':
print "Buy", ", ".join(gulas) + "."
else:
print "Hmmm. No such food on the list."
There are some common problems with your code, so let’s begin by fixing those.
You have multiple items you want to present at the user, and you are hard-coding those values. This makes a lot of effort for you as you have to repeat yourself a lot. Look at your choice lines, they all come to basically the same thing. You also repeat yourself by defining what the numbers link to in your description, and in your code. Let’s try to simplify this with a data structure.
Here, we make a list of all the options – a list of tuples, defining the name and set of items for the given food. We use a set here as we don’t need to order the items.
This gives us a good data structure to begin with.
We can then make our question, rather than manually constructing it, we can construct it from our list of options using a loop:
Note the use of the
enumerate()builtin in order to give us the numbers for the options. As you want to start from 1, and Python counts from 0 normally, we pass that in too.This gives us our output, but we can now easily add more items without modifying the existing code. We ask like before, and then instead of a load of
if/elifs, we can simply get the index they give us from the list. We first have to change the string to a number, and then take away one (as Python counts from 0). This gives us:(Using tuple unpacking to ignore the first value, as it’s the name, which we don’t need).
The only issue now is what will happen if the user types in a number out of range or an word instead, for example. You could check it before you convert to an int and use it, but it’s Pythonic to simply try it, and catch the thrown exceptions upon failure. For example:
This makes the entire program much smaller, and also note how easy it is to add new items, you simply add them to the list.
So how do we deal with multiple items? Well, that’s pretty simple now too. We can take the user’s input, split in on commas, and strip the values to remove any spaces, then do the same thing we did before:
This works, printing out multiple buy lines, but it’s not optimal, a better idea would be to produce one bigger shopping list containing all of the needed items.
We can build this by building up a set of all of the items as we loop, then printing out that set.
This is a little inefficient and ugly, however. Python has some built in functionality to build up lists – list comprehensions. We can do this operation like so:
Now we need to print out all the values in the list. Remember that this is a list of sets, so
", ".join()won’t do exactly what we want. We have two options here. We can either use a generator expression to join the sets first, then join the joined strings:Or, we could use
itertools.chain.from_iterable()to return a flattened iterator:This gives us:
Which produces something like:
This is short, easy to expand on, and functions well. There are some other issues you could deal with (how do you want to handle multiple of the same item? You might want to look into
collections.Counterfor that), but it’s the basic idea.