In a previous issue, we created a Random Dessert Generator in Python to spit out new, exciting desserts from a mishmash of flavours. Now, we’re going to take that code and turn it into something sleeker and fancier, like the kind of code you might see in professional settings.
Introducing Random Dessert Generator 2.0 — the Deluxe Edition!
WHAT IS “BETTER” CODE?
There’s no single answer. Sometimes “better” means “simpler”, because simple code is easier to debug, especially when you have multiple programmers. Alternatively, “better” could mean “faster”. People are naturally impatient, after all — we don’t like waiting for webpages to load, and we don’t like lag in our video games!
Another area for improvement is size. If you’re working with phone apps, or coding on the microchip of a smart appliance, then you’ll want your code to take up minimal space in memory.
Finally, “better” can mean “easier to modify”. Software is constantly evolving, and in order to release new versions, companies need code that doesn’t break every time they add or remove a feature. Choosing which improvements to make all depends on the goals of your software. Often, you have to sacrifice one goal (like speed) for another (like flexibility).
THE OLD DESSERT GENERATOR
from random import choice flavour = ["cherry", "chocolate", "vanilla", "caramel"] bonus = ["swirl", "volcano", "mousse", "sprinkles"] dessert = ["ice cream", "doughnut", "pie", "cookies"] new_dessert = choice(flavour) + " " + choice(bonus) + " " + choice(dessert) print(new_dessert)
A HIGH-LEVEL CHANGE
“High-level” changes are all about structure. Every program is essentially a series of tasks: create a character, explore the map, find the treasure. When you’re designing a program, you want to order and group those tasks together in smart ways that accomplish your software goals.
Our Dessert Generator does three things: (1) it creates lists of words, (2) it chooses random elements and strings them together into a delicious dessert, and (3) it displays the result onscreen. This process is simple and efficient, but what if we want to add a second flavour? First, we’ll need to add the new list. Second, we’ll have to update the line that cobbles together the dessert.
from random import choice flavour = ["cherry", "chocolate", "vanilla", "caramel"] flavour2 = ["cream", "coconut", "pineapple"] bonus = ["swirl", "volcano", "mousse", "sprinkles"] dessert = ["ice cream", "doughnut", "pie", "cookies"] new_dessert = choice(flavour) + " " + choice(flavour2) + " " + choice(bonus) + " " + choice(dessert) print(new_dessert)
Updating two lines of code seems like a small change — and it is! The problem is that one change is affecting two different tasks. In a large program, tasks are often squirrelled away in separate files, and this “small” change wouldn’t be so small. Is there a way to restructure our code to prevent this? What if we try:
from random import choice flavour = ["cherry", "chocolate", "vanilla", "caramel"] flavour2 = ["cream", "coconut", "pineapple"] bonus = ["swirl", "volcano", "mousse", "sprinkles"] dessert = ["ice cream", "doughnut", "pie", "cookies"] all_lists = [flavour, flavour2, bonus, dessert] new_dessert = "" for l in all_lists: new_dessert += choice(l) + " " print(new_dessert)
This new code uses a “list of lists” to deliver all the dessert inputs in a tidy little bag. As an analogy, imagine packing for a family trip. Each person has their own suitcase, and there might be a communal “food bag” with snacks, or a “beach bag” with towels and sunscreen. This system of bags is much more organized than throwing everything into the car in a huge pile. The car then acts as the “master list” which groups all the bags together and makes sure none are left behind.
In the new code, our “for loop” goes through each list (represented by the character “l”) one at a time. For each one, we randomly choose an input, add it to the new dessert, and move on — just like going through each bag in the car, removing one item, and moving on.
The main benefit of this system is flexibility. You can put as many lists as you want in the master list — two, five, one hundred, three thousand — and the code for “task 2” will work regardless. And if you want to change the program to a “goofy nickname generator” or a “random movie title generator” it’ll be easier to reuse code.
A LOW-LEVEL CHANGE
“Low-level” changes involve nitty-gritty, language-specific hacks for speed and efficiency. Many of these are tricks learned through time and experience, instead of techniques you figure out by pure logic.
For example, in our current code we use the “+=“ operator to string our dessert together. This happens to be pretty inefficient, because of the way the operating system stores variables in memory. A faster hack is to use the “.join()” string function to glue strings together.
There are two pieces of info you need to call the “join” function. The first is the delimiter between words: are you using a space, a hyphen, an underscore? The next is the list of words to join. The syntax then looks like this:
[delimiter].join( [list] )
How do we go from our “list of lists” to a single list that contains a random item from each “bag”? The syntax is a tad strange:
choice(l) for l in all_lists
It may help if you start reading in the middle of line: “for each list in the master list, take choice(l)”. The choice function, you may recall, selects a random element from the list. All together, these changes give you:
from random import choice flavour = ["cherry", "chocolate", "vanilla", "caramel"] flavour2 = ["cream", "coconut", "pineapple"] bonus = ["swirl", "volcano", "mousse", "sprinkles"] dessert = ["ice cream", "doughnut", "pie", "cookies"] all_lists = [flavour, flavour2, bonus, dessert] new_dessert = " ".join(choice(l) for l in all_lists) print(new_dessert)
Another possible tweak is to get rid of the “new_dessert” variable, and jump straight into printing the joined string:
from random import choice flavour = ["cherry", "chocolate", "vanilla", "caramel"] flavour2 = ["cream", "coconut", "pineapple"] bonus = ["swirl", "volcano", "mousse", "sprinkles"] dessert = ["ice cream", "doughnut", "pie", "cookies"] all_lists = [flavour, flavour2, bonus, dessert] print(" ".join(choice(l) for l in all_lists))
See any potential problems with this change? While we’re saving memory space by cutting out a variable, we’ve combined tasks 2 & 3: creating the dessert and displaying it. If we ever want to change the way we display the dessert (say, by using graphics and colours) then we’ll have to “decouple” the tasks before we can add our new feature.
CONCLUSION
These small improvements won’t make much of a difference in a program like the Random Dessert Generator. But if you take a large, complex piece of software like Google or Facebook, then thinking about high-level structure and low-level optimization is key. Besides, who doesn’t like having better code?
Learn More
Python String join() Method
https://www.tutorialspoint.com/python/string_join.htm
Why is .join() faster than += in Python
https://www.codecademy.com/articles/mvc
why you should use python generators
https://realpython.com/introduction-to-python-generators/