I am a newbie, so please forgive the simplicity of the question. In this exercise, I found task 7 description practically incomprehensible. I was unable to begin because I didn’t really understand what we were being asked to do. Can somebody here perhaps state the problem in another way? Which dishes are in the dish parameter? Is the second parameter a list of all the categories or just one? The description is confusing to me. Thanks in advance
Hi @jshames ![]()
This task based only on the instructions is a tad obtuse. It’s meant to have you practice set symmetric difference.
If you take a look at the hints for the exercise and/or the introduction to the concept, things might be a little clearer.
The task is asking you to find unique ingredients across multiple sets. So – not a de-duping of one set (because making a set de-dupes things), but a “sort of” de-duping of ALL (or multiple) sets combined together, where you surface ingredients that only appear once for all recipes.
You can also take a look at the test data and the category data for more details, although you should be warned that both of those files are long and might be hard to read.
The upshot? symmetric difference between two sets can be used to surface items that appear in one set but not both sets.
It can also be used across multiple sets, but has to be applied in a specific pattern or loop.
Let me know if that makes sense - and if you have additional questions or issues. ![]()
Just for context, this is the description of task #7 of the “Cater Waiter” exercise.
Within each category (Vegan, Vegetarian, Paleo, Keto, Omnivore), you’re going to pull out ingredients that appear in only one dish. These “singleton” ingredients will be assigned a special shopper to ensure they’re not forgotten in the rush to get everything else done.
Implement the
singleton_ingredients(<dishes>, <INTERSECTIONS>)function that takes alistof dishes and a<CATEGORY>_INTERSECTIONSconstant for the same category. Each dish is represented by asetof its ingredients. Each<CATEGORY>_INTERSECTIONSis asetof ingredients that appear in more than one dish in the category. Using set operations, your function should return asetof “singleton” ingredients (ingredients appearing in only one dish in the category).from sets_categories_data import example_dishes, EXAMPLE_INTERSECTION >>> singleton_ingredients(example_dishes, EXAMPLE_INTERSECTION) ... {'garlic powder', 'sunflower oil', 'mixed herbs', 'cornstarch', 'celeriac', 'honey', 'mushrooms', 'bell pepper', 'rosemary', 'parsley', 'lemon', 'yeast', 'vegetable oil', 'vegetable stock', 'silken tofu', 'tofu', 'cashews', 'lemon zest', 'smoked tofu', 'spaghetti', 'ginger', 'breadcrumbs', 'tomatoes', 'barley malt', 'red pepper flakes', 'oregano', 'red onion', 'fresh basil'}
Your task is to implement the function singleton_ingredients.
This function takes two arguments:
- a
listof dishes, where each element in this list is asetof ingredients. - a
setof all ingredients that appear in more than one dish
The functions should return a set of ingredients that appear only in one dish.
For example:
I have a list of dishes:
- Spicy Omelette (ingredients: eggs, pepper, salt, olive oil, tabasco)
- Feta Pesto (ingredients: pasta, feta, pignola, basil, olive oil)
- Spaghetti Cacio e Pepe (ingredients: pasta, olive oil, pepper, salt)
I also have a set of all ingredients that appear in multiple recipes:
pepper, salt, olive oil, pasta
I want to get all ingredients that appear only in a single recipe.
For these three recipes that’s eggs, tabasco, feta, pignola, basil
You can solve this exercise by reusing one of the functions you wrote earlier, and by using a “set operation” as explained in the instructions.
[edit: While I was still typing Bethany gave a much better answer]
@siebenschlaefer – I disagree. I think your answer is excellent. Both of us are getting at the same point from different (and valid!) directions. ![]()
So, the second parameter is a single list containing the shared ingredients among the dishes in the first parameter. Is that right?
The second parameter <INTERSECTION> is a set, not list, which is why you need to use the symmetric difference set operator. Don’t be fooled by the tuple or the zip() in the tests, or the test data ![]()
Here is the function signature from the stub file:
def singleton_ingredients(dishes, intersection):
"""Determine which `dishes` have a singleton ingredient (an ingredient that only appears once across dishes).
:param dishes: list - of ingredient sets.
:param intersection: constant - can be one of `<CATEGORY>_INTERSECTION` constants imported from `sets_categories_data.py`.
:return: set - containing singleton ingredients.
Each dish is represented by a `set` of its ingredients.
Each `<CATEGORY>_INTERSECTION` is an `intersection` of all dishes in the category. `<CATEGORY>` can be any one of:
(VEGAN, VEGETARIAN, PALEO, KETO, or OMNIVORE).
The function should return a `set` of ingredients that only appear in a single dish.
"""
So the first parameter is a list of sets. The second is a set representing the intersection of all sets within the first parameter.
As an aside: Because of the size of the data for this exercise, we made a decision to pull test data out into different files and use zip() to reduce clutter. The original tests were effectively breaking the UI and causing readability problems. But abstracting out the data can make it hard to suss out what exactly is going on.
Thank you, I think I understand now. For some reason I was stuck on the language in the description, you helped clarify things for me. The logic of the coding is fairly straightforward.
Same as @jshames , I found the singleton_ingredients task confusing at first.
The full instructions do say:
Each
<CATEGORY>_INTERSECTIONSis a set of ingredients that appear in more than one dish in the category.
That wording makes the intended logic much clearer. But the starter docstring in sets.py only says:
intersection (constant): Can be one of
<CATEGORY>_INTERSECTIONSconstants imported fromsets_categories_data.py.
Because the parameter is named intersection, I initially interpreted it as the normal mathematical intersection of all dishes, meaning ingredients common to every dish. The docstring tells me which constant to pass, but not what that constant represents.
Also, unlike some other exercises, the example does not show the actual values of the parameters being passed in. It imports example_dishes and EXAMPLE_INTERSECTION, then shows only the returned set. Since learners cannot see what EXAMPLE_INTERSECTION contains from the example alone, the meaning of the second argument is harder to infer.
Would updating the starter docstring help save learners time by keeping it consistent with the clearer wording from the instructions?
For example:
intersection (set): One of the
<CATEGORY>_INTERSECTIONSconstants fromsets_categories_data.py, containing ingredients that appear in more than one dish in that category.
If this clarification sounds useful, I’d be happy to create a PR for it. I’d like to contribute back to this amazing community, so please let me know if this would be welcome.
Hi @tdoan123,
Welcome to the Exercism forums.
Thanks for chiming in on this thread with your suggestions.
If you are working online, both the test data and the INTERSECTION data are available in their own tabs in the left-hand side of the UI:
If you are working offline, the relevant files should download with the exercise stub and tests into the same directory structure.
The docstrings for this exercise were updated yesterday, and are as clear as I can make them without spelling out the answer.
That is correct - intersection is the “ingredients in common” for the category.
It is a bit long-winded, but the This section in the introduction explains why this parameter is named intersection, and how that applies to the symmetric difference of more than two sets. There’s also a link to a rather mathy Wiki article.
The upshot is that you can’t take the symmetric difference of two + sets without including some intersections. So the intersections need to be cleaned up.
Hope this helps clarify things. Let us know if you have additional questions or issues.
