We will start off with a little bit of background information. According to Wikipedia, sushi (寿司) is a dish from Japan that, in one way or another, consists of a bite of rice of a few centimeters thick, with an ingredient on top of it or in it, such as raw fish or other seafood, but also cooked fish, baked egg or vegetables. The two most popular types of sushi, combined with a few subdivisions, are:
maki — one or more tasty things rolled in seaweed and rice
futomaki — seaweed on the outside, usually vegetarian
temaki — cone-shaped role of seaweed filled with rice and tasty things
uramaki — rice on the outside
nigiri — a hand-formed ball of rice, garnished with something tasty
gunkanmaki — with a loose or soft topping that is kept in place with a strike of seaweed
temarizushi — where the topping is only pressed in the rice ball
This kind of hierarchic division magically lends to an implementation of classes and inheritance.
If you ever had dinner in a Japanese sushi-restaurant, you have probably had a good laugh with the funny phrases and many mistakes in the English translation of Japanese descriptions of the dishes. We are going to write a simple program that (in theory) can be used by the owner of a sushi-restaurant to compose a menu, completed with English and Japanese (although not in Japanese characters, sorry for that) descriptions of the dishes. Below we explain how you have to construct this program in steps.
To start off, make a class Ingredient. Two arguments must be given to the initializing method — japanese and english — that correspond with the Japanese and English names of the ingredients. The argument english is optional, and has the value of the argument japanese as a standard value, if it is not given (just like when some names on the menu are not translated, and you have to guess what they are). The value of both arguments must be kept as attribute of the objects of the class Ingredient.
Add two methods to the class Ingredient: __str__(self) and english(self). Both methods must print a string. The __str__ method is automatically called when an object of the class Ingredient is printed or converted to a string. Make sure that the method __str__ print the Japanese name of the ingredient, and the method english the English translation of the ingredient.
We have already made a file based on the data of the website http://www.bento.com/sushivoc.html1 that lists a number of much-used sushi-ingredients. The first column contains the original Japanese name of the ingredient, and the second column contains the English translation, if it is known. There are a number of Japanese terms for which no English translation was given (intentionally), e.g. fugu. For these ingredients, the Japanese name should also be used in the English translation. You can download the file here2.
Write a function index_ingredients to which an opened file object must be given as an argument. This function must index all ingredients of the file in the format of a dictionary, where the Japanese name is used as a key, and the corresponding value is an Ingredient object that can print both the Japanese and the English name of an ingredient. Test the correct functioning of this function before continuing.
Now, make a class Sushi that can be used to represent various sushi-dishes. The class Sushi must have an initialization method to which a list of Ingredient objects must be given.
Then, add a method __str__(self) to the class klasse Sushi. This method must print a string. The string must list the Japanese name of all ingredients in the dish. The contents of the string is formulated in English, so for example 'buri', 'buri and tsubugai' or 'buri, tsubugai and kanpachi' are the correct ways to print respectively one, two and three ingredients. It does not suffice to simply separate the ingredients by commas.
When making a Sushi object, a list of Ingredient objects must be given to the initializing method. To allow the user to make Sushi objects in a simple manner, we ask you to write a function make_sushi. A string that lists the Japanese name of all ingredients in the sushi, must be given to this function as an argument, separated by spaces. The function must first convert this string of names to a list of corresponding Ingredient objects, and must then print a Sushi object that is made on the bases of this list. Of course, when converting the string to the list of Ingredient objects, the dictionary that is printed by the function index_ingredients must be used. Based on the function make_sushi, it is simple to make a Sushi object via a string like 'unagi fugu ika sake'. This allows testing all code you have written up until now in the following manner:
>>> sushi = make_sushi('unagi fugu ika sake')
>>> print(sushi)
unagi, fugu, ika and sake
Pay attention to the fact that the function make_sushi must also be able to make sushi's based on ingredients that do not appear in the dictionary. In this case, Ingredient objects must be made, solely based on the Japanese names of ingredients.
Now, give the class Sushi another method english(self), that prints a description of the sushi-dishes where the names of the ingredients are now translated to English. This method must print a similar string as the one that is printed by the method __str__, but where the ingredients are listed in English and not in Japanese. In this case, however, it is not desirable to call that method __str__ for the implementation of the method english of a Sushi object and translating the ingredients in the printed string one by one. Because a list of Ingredient objects is given when making a Sushi object, it is sufficient to call the method english of the individual Ingredient objects and to draw it up in a correct way with commas and the word and. With this, you are now also able to put the English translation of a sushi-dish on the menu.
>>> sushi = make_sushi('unagi fugu ika sake')
>>> print(sushi.english())
eel, fugu, squid and salmon
Because the methods __str__ and english of a Sushi object display the list of ingredient names in the same way, is it in any case interesting to write a help method that can print a list of ingredient names, no matter what language the ingredients are in.
Now make a class Maki that inherits all its attributes from the class Sushi. Objects of the class Maki behave in the exact same way as the objects of the class Sushi, except that instead of listing the names of the ingredients, now more informative descriptions are given. Here, we want the methods __str__ and english to print a string of the format
[ingredients] rolled in [rice] and [seaweed]
where [ingredients] represents our grammatical list of ingredients, and [rice] and [seaweed] represent two other ingredients that are consistently used in all types of sushi. For these last two ingredients — like it is the case with all other ingredients — it must be assured that the correct language is used on the correct position. These set ingredients, however, are not summed up in the list of ingredients that is given when making sushi. However, they are implied by the type of sushi. You can better make constants for these ingredients that you put in the right place in the class hierarchy. The code fragment below indicates how (but not where) we have defined the set ingredients.
rice = Ingredient('su-meshi', 'sushi rice')
seaweed = Ingredient('nori', 'seaweed')
Now that we already have two kinds of sushi, it is time to revise our way in which these sushi-objects are made. As an initial point we have already written a function make_sushi, that makes a Sushi object based on the string of the Japanese ingredient names. Now, we want to expand this so that the strings 'unagi fugu' and 'unagi fugu sushi' must be see as the description of an general sushi for which a Sushi object must be made. If the last word of the description should be 'maki', however, a Maki object must be made. Because we also want to define other sushi kinds later on, this divide must happen as flexible as possible, by using an implementation that later can easily be expanded. As a general rule, we suppose that a sushi is described by a sequence of Japanese names of ingredients, possibly followed by a specific type of sushi. If no type of sushi is given, a Sushi object must be made, otherwise a sushi-object of the given type must be made.
We can now also reorganize the program code that takes care of make sushi-objects. Until now, sushi-objects were made based on the function make_sushi, that on its turn used both the function index_ingredients (to make ingredients) and a data structure that represents the different types of sushi. When programming object-oriented, it is better to bundle all these aids in a class SushiMaker. We can parameterize the initialization method of this class with an opened file object from which the translation of sushi-ingredients can be read. Standard, this file object can be opened in order to read the file to which was referred above. Then, sushi objects can be made in the following manner
>>> chef = SushiMaker()
>>> sushi = chef.make_sushi('unagi fugu sushi')
>>> print(sushi)
unagi and fugu
>>> print(sushi.english())
eel and fugu
>>> sushi = chef.make_sushi('unagi fugu maki')
>>> print(sushi)
unagi and fugu rolled in su-meshi and nori
>>> print(sushi.english())
eel and fugu rolled in sushi rice and seaweed
Cool! But now we still have to add a number of sushi varieties to our arsenal. Futomaki, temaki and uramaki are all subvarieties of maki, and can be represented by classes that inherit from the class Maki. The way in which the description of every one of these varieties as a string must happen is as follows:
Futomaki: '[ingredients] rolled in [rice] and [seaweed], with [seaweed] facing out'
Temaki: 'cone of [seaweed] filled with [rice] and [ingredients]'
Uramaki: '[ingredients] rolled in [seaweed] and [rice], with [rice] facing out'
For the implementation of the different descriptions, it could be handy to use a format string3. If you define, for example, the following string
>>> description = '{ingredients} rolled in {rice} and {seaweed}, with {seaweed} facing out'
you can do the following
>>> description.format(rice='su-meshi', seaweed='nori', ingredients='unagi fugu')
'unagi fugu rolled in su-meshi and nori, with nori facing out'
This is actually a fairly powerful instrument, because it allows to enclose rice and seaweed in the dictionary, even if they don't occur in the format string! with this information in hand, you can now rewrite the basic class Sushi, so that the description happens on the bases of a property of the object that is used as format string in combination with a dictionary of 'rice', 'seaweed' and 'ingredients'. This way, it is sufficient that every child class defines an own format string and everything works as it should. Making new child classes is then simply done in the following manner
class Futomaki(Maki):
description = '{ingredients} rolled in {rice} and {seaweed}, with {seaweed} facing out'
class Temaki(Maki):
description = 'cone of {seaweed} filled with {rice} and {ingredients}'
Make sure that this works for both descriptions with Japanese and English names of the ingredients, and also make sure that the data structure with the different kinds of sushi in the class SushiMaker is adjusted in a correct way. This way, the following should also work:
>>> chef = SushiMaker()
>>> sushi = chef.make_sushi('unagi ohyo uramaki')
>>> print(sushi)
unagi and ohyo rolled in nori and su-meshi, with su-meshi facing out
>>> print(sushi.english())
eel and halibut rolled in seaweed and sushi rice, with sushi rice facing out
>>> sushi = chef.make_sushi('ikura temaki')
>>> print(sushi)
cone of nori filled with su-meshi and ikura
>>> print(sushi.english())
cone of seaweed filled with sushi rice and salmon roe
We are almost there. The still have to be made a few classes for the last sequence of sushi varieties. Add a class Nigiri that inherits from the class Sushi, and add two classes Gunkanmaki and Temarizushi that inherit from the class Nigiri. Because nigiri normally only has one topping, you must try to benefit from the inheritance to enforce that this condition applies to all nigiri varieties. You do this by providing an adjusted implementation of the method __init__ within the class Nigiri. If this condition is violated, an InvalidSushiError must be raised (you must define this one yourself, seeing as the Python libraries do not quite contain an error of that kind ). Moreover, don't forget to call the method __init__ of the basic class when reimplementing the method __init__. The descriptions of the sushi varieties that belong with these classes are
Nigiri: 'hand-formed [rice] topped with [ingredients]'
Gunkanmaki: '[ingredients] on [rice] wrapped in a strip of [seaweed]'
Temarizushi: '[ingredients] pressed into a ball of [rice]'
As a last test, the following should now also work
>>> chef = SushiMaker()
>>> sushi = chef.make_sushi('fugu ohyo ika unagi')
>>> print(sushi)
fugu, ohyo, ika and unagi
>>> print(sushi.english())
fugu, halibut, squid and eel
>>> sushi = chef.make_sushi('fugu ohyo ika unagi sushi')
>>> print(sushi)
fugu, ohyo, ika and unagi
>>> print(sushi.english())
fugu, halibut, squid and eel
>>> sushi = chef.make_sushi('ika sake gunkanmaki')
Traceback (most recent call last):
InvalidSushiError: Nigiri has only one topping
Traceback (most recent call last):
InvalidSushiError: Nigiri has only one topping