Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sets Concept Exercise #2485

Merged
merged 34 commits into from
Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bf73c4b
Added cater-waiter UUID and exercise information.
BethanyG Aug 21, 2021
b9f44ee
Added cater-waiter exemplar file and exercise configuration.
BethanyG Aug 21, 2021
53992bc
Added intro, instructions, and hints for cater-waiter.
BethanyG Aug 21, 2021
ade83fc
Added stub, test, and test_data files.
BethanyG Aug 21, 2021
46311f3
Deleted unneeded sets files.
BethanyG Aug 21, 2021
0879531
Renamed after to deprecated_after.
BethanyG Aug 21, 2021
d437e50
Ran prettier.
BethanyG Aug 21, 2021
5a67f43
Added editor field to data py for test runner CI.
BethanyG Aug 25, 2021
c3dda69
Changed editor to not have a default value in the dataclass.
BethanyG Aug 25, 2021
e0d61cb
Reverting to default arugment for dataclass.
BethanyG Aug 25, 2021
ef11fbf
Added editor files into the list of files to copy to the runner.
BethanyG Aug 25, 2021
fad93d7
Added editor files into the list of files to copy to the runner. AGAIN.
BethanyG Aug 25, 2021
55e779d
Updated property for helper_files.
BethanyG Aug 25, 2021
8c8a591
renamed sets_categories to sets_catigories_data, to help with helper …
BethanyG Aug 25, 2021
51521a3
Changed categories to categories_data in exercise config.
BethanyG Aug 25, 2021
70d6577
Tried different strategy for helper_files property.
BethanyG Aug 25, 2021
5011594
Removed helper files from solutions copy, hope this stops the test co…
BethanyG Aug 25, 2021
4710b8e
More attempts at a fix for the copy issue.
BethanyG Aug 25, 2021
9972301
Added None default param for editor file patterns.
BethanyG Aug 25, 2021
aad9997
moved default argument to below positional argument. I hate everythi…
BethanyG Aug 25, 2021
c525fbb
And another edit because I am stupidly stubborn.
BethanyG Aug 25, 2021
3e94010
Added None check for helper_file copy.
BethanyG Aug 26, 2021
4179339
Added dst.
BethanyG Aug 26, 2021
760eb82
Added another missing copy for exercise files.
BethanyG Aug 26, 2021
eb6241d
Typos are fun. Really fun.
BethanyG Aug 26, 2021
8101915
Renamed variables in comprehensions.
BethanyG Aug 26, 2021
cd57fd2
None check.
BethanyG Aug 26, 2021
788ff4f
Test by adding data files to solutiona and test arrays.
BethanyG Aug 26, 2021
f84568f
Revert config changes.
BethanyG Aug 26, 2021
a1412b7
Cleaned up helper_file references and file copy debug statements.
BethanyG Aug 26, 2021
77f7366
Corrected mis-named import statement.
BethanyG Aug 26, 2021
d80fdf1
Cleaned up test data at bottom of file.
BethanyG Aug 26, 2021
ec29da2
restored stub file and docstrings.
BethanyG Aug 26, 2021
17523c6
Removed exemplar import and re-orders sets file import to be near top.
BethanyG Aug 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions bin/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ class ExerciseFiles:

solution: List[str]
test: List[str]
editor: List[str] = None
exemplar: List[str] = None


# practice exercises are different
example: List[str] = None

Expand Down Expand Up @@ -191,6 +193,10 @@ def solution_stub(self):
None,
)

@property
def helper_file(self):
return next(self.path.glob("*_data.py"), None)

@property
def test_file(self):
return next(self.path.glob("*_test.py"), None)
Expand Down Expand Up @@ -275,6 +281,8 @@ class FilePatterns:
test: List[str]
example: List[str]
exemplar: List[str]
editor: List[str] = None



@dataclass
Expand Down
20 changes: 20 additions & 0 deletions bin/test_exercises.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,25 @@ def copy_solution_files(exercise: ExerciseInfo, workdir: Path, exercise_config:
if exercise_config is not None:
solution_files = exercise_config.files.solution
exemplar_files = exercise_config.files.exemplar
helper_files = exercise_config.files.editor
else:
solution_files = []
exemplar_files = []
helper_files = []

if helper_files:
helper_files = [exercise.path / h for h in helper_files]
for helper_file in helper_files:
dst = workdir / helper_file.relative_to(exercise.path)
copy_file(helper_file, dst)

if not solution_files:
solution_files.append(exercise.solution_stub.name)
solution_files = [exercise.path / s for s in solution_files]
if not exemplar_files:
exemplar_files.append(exercise.exemplar_file.relative_to(exercise.path))
exemplar_files = [exercise.path / e for e in exemplar_files]

for solution_file, exemplar_file in zip_longest(solution_files, exemplar_files):
if solution_file is None:
copy_file(exemplar_file, workdir / exemplar_file.name)
Expand All @@ -68,10 +78,20 @@ def copy_solution_files(exercise: ExerciseInfo, workdir: Path, exercise_config:
def copy_test_files(exercise: ExerciseInfo, workdir: Path, exercise_config = None):
if exercise_config is not None:
test_files = exercise_config.files.test
helper_files = exercise_config.files.editor
else:
test_files = []
helper_files = []

if helper_files:
for helper_file_name in helper_files:
helper_file = exercise.path / helper_file_name
helper_file_out = workdir / helper_file_name
copy_file(helper_file, helper_file_out, strip_skips=(exercise.slug not in ALLOW_SKIP))

if not test_files:
test_files.append(exercise.test_file.name)

for test_file_name in test_files:
test_file = exercise.path / test_file_name
test_file_out = workdir / test_file_name
Expand Down
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@
"concepts": ["numbers"],
"prerequisites": ["basics"],
"status": "beta"
},
{
"slug": "cater-waiter",
"name": "Cater Waiter",
"uuid": "922f8d2d-87ec-4681-9654-4e0a36a558d4",
"concepts": ["sets"],
"prerequisites": ["basics", "dicts", "lists", "loops", "tuples"],
"status": "beta"
}
],
"practice": [
Expand Down
63 changes: 63 additions & 0 deletions exercises/concept/cater-waiter/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Hints

## General

- [Sets][sets] are mutable, unordered collections with no duplicate elements.
- Sets can contain any data type, but all elements within a set must be [hashable][hashable].
- Sets are [iterable][iterable].
- Sets are most often used to quickly dedupe other collections or for membership testing.
- Sets also support mathematical operations like `union`, `intersection`, `difference`, and `symmetric difference`

## 1. Clean up Dish Ingredients

- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable.
- Remember: [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(<element_1>, <element_2>)` or via the `tuple()` constructor.

## 2. Cocktails and Mocktails

- A `set` is _disjoint_ from another set if the two sets share no elements.
- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable.
- In Python, [strings:python/strings](https://exercism.lol/tracks/python/concepts/strings) can be concatenated with the `+` sign.

## 3. Categorize Dishes

- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the available meal categories might be useful here.
- If all the elements of `<set_1>` are contained within `<set_2>`, then `<set_1> <= <set_2>`.
- The method equivalent of `<=` is `<set>.issubset(<iterable>)`
- [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can contain any data type, including other tuples. Tuples can be formed using `(<element_1>, <element_2>)` or via the `tuple()` constructor.
- Elements within [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number.
- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable.
- [strings:python/strings](https://exercism.lol/tracks/python/concepts/strings) can be concatenated with the `+` sign.

## 4. Label Allergens and Restricted Foods

- A set _intersection_ are the elements shared between `<set_1>` and `<set_2>`.
- The set method equivalent of `&` is `<set>.intersection(<iterable>)`
- Elements within [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be accessed from the left using a 0-based index number, or from the right using a -1-based index number.
- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable.
- [tuples:python/tuples](https://exercism.lol/tracks/python/concepts/tuples) can be formed using `(<element_1>, <element_2>)` or via the `tuple()` constructor.

## 5. Compile a "Master List" of Ingredients

- A set _union_ is where `<set_1`> and `<set_2>` are combined into a single `set`
- The set method equivalent of `|` is `<set>.union(<iterable>)`
- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the various dishes might be useful here.

## 6. Pull out Appetizers for Passing on Trays

- A set _difference_ is where the elements of `<set_2>` are removed from `<set_1>`, e.g. `<set_1> - <set_2>`.
- The set method equivalent of `-` is `<set>.difference(<iterable>)`
- The `set()` constructor can take any [iterable][iterable] as an argument. [lists:python/lists](https://exercism.lol/tracks/python/concepts/lists) are iterable.
- The [list:python/lists](https://exercism.lol/tracks/python/concepts/lists) constructor can take any [iterable][iterable] as an argument. Sets are iterable.

## 7. Find Ingredients Used in Only One Recipe

- A set _symmetric difference_ is where elements appear in `<set_1>` or `<set_2>`, but not **_both_** sets.
- A set _symmetric difference_ is the same as subtracting the `set` _intersection_ from the `set` _union_, e.g. `(<set_1> | <set_2>) - (<set_1> & <set_2>)`
- A _symmetric difference_ of more than two `sets` will include elements that are repeated more than two times across the input `sets`. To remove these cross-set repeated elements, the _intersections_ between set pairs needs to be subtracted from the symmetric difference.
- Using [loops:python/loops](https://exercism.lol/tracks/python/concepts/loops) to iterate through the various dishes might be useful here.


[hashable]: https://docs.python.org/3.7/glossary.html#term-hashable
[iterable]: https://docs.python.org/3/glossary.html#term-iterable
[sets]: https://docs.python.org/3/tutorial/datastructures.html#sets
142 changes: 142 additions & 0 deletions exercises/concept/cater-waiter/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Instructions

You and your business partners operate a small catering company. You've just agreed to run an event for a local cooking club that features "club favorite" dishes. The club is inexperienced in hosting large events, and needs help with organizing, shopping, prepping and serving. You've decided to write some small Python scripts to speed the whole planning process along.

## 1. Clean up Dish Ingredients

The event recipes were added from various sources and their ingredients appear to have duplicate (_or more_) entries -- you don't want to end up purchasing excess items!
Before the shopping and cooking can commence, each dish's ingredient list needs to be "cleaned".

Implement the `clean_ingredients(<dish_name>, <dish_ingredients>)` function that takes the name of a dish and a `list` of ingredients.
This function should return a `tuple` with the name of the dish as the first item, followed by the de-duped `set` of ingredients.


```python
>>> clean_ingredients('Punjabi-Style Chole', ['onions', 'tomatoes', 'ginger paste', 'garlic paste', 'ginger paste', 'vegetable oil', 'bay leaves', 'cloves', 'cardamom', 'cilantro', 'peppercorns', 'cumin powder', 'chickpeas', 'coriander powder', 'red chili powder', 'ground turmeric', 'garam masala', 'chickpeas', 'ginger', 'cilantro'])

>>> ('Punjabi-Style Chole', {'garam masala', 'bay leaves', 'ground turmeric', 'ginger', 'garlic paste', 'peppercorns', 'ginger paste', 'red chili powder', 'cardamom', 'chickpeas', 'cumin powder', 'vegetable oil', 'tomatoes', 'coriander powder', 'onions', 'cilantro', 'cloves'})
```

## 2. Cocktails and Mocktails

The event is going to include both cocktails and "mocktails" - mixed drinks _without_ the alcohol.
You need to ensure that "mocktail" drinks are truly non-alcoholic and the cocktails do indeed _include_ alcohol.

Implement the `check_drinks(<drink_name>, <drink_ingredients>)` function that takes the name of a drink and a `list` of ingredients.
The function should return the name of the drink followed by "Mocktail" if the drink has no alcoholic ingredients, and drink name followed by "Cocktail" if the drink includes alcohol.
For the purposes of this exercise, cocktails will only include alcohols from the ALCOHOLS constant in `categories.py`:

```python
>>> from categories import ALCOHOLS

>>> check_drinks('Honeydew Cucumber', ['honeydew', 'coconut water', 'mint leaves', 'lime juice', 'salt', 'english cucumber'])
...
'Honeydew Cucumber Mocktail'

>>> check_drinks('Shirley Tonic', ['cinnamon stick', 'scotch', 'whole cloves', 'ginger', 'pomegranate juice', 'sugar', 'club soda'])
...
'Shirley Tonic Cocktail'
```

## 3. Categorize Dishes

The guest list includes diners with different dietary needs, and your staff will need to separate the dishes into Vegan, Vegetarian, Paleo, Keto, and Omnivore.

Implement the `categorize_dish(<dish_name>, <dish_ingredients>)` function that takes a dish name and a `set` of that dish's' ingredients.
The function should return a string with the `dish name: <CATEGORY>` (_which meal category the dish belongs to_).
All dishes will "fit" into one of the categories imported from `categories.py` (VEGAN, VEGETARIAN, PALEO, KETO, or OMNIVORE).

```python
>>> from categories import VEGAN, VEGETARIAN, PALEO, KETO, OMNIVORE


>>> categorize_dish(('Sticky Lemon Tofu', ['tofu', 'soy sauce', 'salt', 'black pepper', 'cornstarch', 'vegetable oil', 'garlic', 'ginger', 'water', 'vegetable stock', 'lemon juice', 'lemon zest', 'sugar']))
...
'Sticky Lemon Tofu: VEGAN'

>>> categorize_dish(('Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole', ['shrimp', 'bacon', 'avocado', 'chickpeas', 'fresh tortillas', 'sea salt', 'guajillo chile', 'slivered almonds', 'olive oil', 'butter', 'black pepper', 'garlic', 'onion']))
...
'Shrimp Bacon and Crispy Chickpea Tacos with Salsa de Guacamole: OMNIVORE'
```

## 4. Label Allergens and Restricted Foods

Some guests have allergies and additional dietary restrictions.
These ingredients need to be tagged/annotated for each dish so that they don't cause issues.

Implement the `tag_special_ingredients(<dish>)` function that takes a `tuple` with the dish name in the first position, and a `list` or `set` of ingredients for that dish in the second position.
Return the dish name followed by the `set` of ingredients that require a special note on the dish description.
Dish ingredients inside a `list` may or may not have duplicates.
For the purposes of this exercise, all allergens or special ingredients that need to be labeled are in the SPECIAL_INGREDIENTS constant imported from `categories.py`.

```python
>>> from categories import SPECIAL_INGREDIENTS

>>> tag_special_ingredients(('Ginger Glazed Tofu Cutlets', ['tofu', 'soy sauce', 'ginger', 'corn starch', 'garlic', 'brown sugar', 'sesame seeds', 'lemon juice']))
...
('Ginger Glazed Tofu Cutlets', {'garlic','soy sauce','tofu'})

>>> tag_special_ingredients(('Arugula and Roasted Pork Salad', ['pork tenderloin', 'arugula', 'pears', 'blue cheese', 'pinenuts', 'balsamic vinegar', 'onions', 'black pepper']))
...
('Arugula and Roasted Pork Salad', {'blue cheese', 'pinenuts', 'pork tenderloin'})
```

## 5. Compile a "Master List" of Ingredients

In preparation for ordering and shopping, you'll need to compile a "master list" of ingredients for everything on the menu (_quantities to be filled in later_).

Implement the `compile_ingredients(<dishes>)` function that takes a `list` of dishes and returns a set of all ingredients in all listed dishes.
Each individual dish is represented by its `set` of ingredients.

```python
dishes = [ {'tofu', 'soy sauce', 'ginger', 'corn starch', 'garlic', 'brown sugar', 'sesame seeds', 'lemon juice'},
{'pork tenderloin', 'arugula', 'pears', 'blue cheese', 'pine nuts',
'balsamic vinegar', 'onions', 'black pepper'},
{'honeydew', 'coconut water', 'mint leaves', 'lime juice', 'salt', 'english cucumber'}]

>>> compile_ingredients(dishes)
...
{'arugula', 'brown sugar', 'honeydew', 'coconut water', 'english cucumber', 'balsamic vinegar', 'mint leaves', 'pears', 'pork tenderloin', 'ginger', 'blue cheese', 'soy sauce', 'sesame seeds', 'black pepper', 'garlic', 'lime juice', 'corn starch', 'pine nuts', 'lemon juice', 'onions', 'salt', 'tofu'}
```

## 6. Pull out Appetizers for Passing on Trays

The hosts have given you a list of dishes they'd like prepped as "bite-sized" appetizers to be served on trays.
You need to pull these from the main list of dishes being prepared as larger servings.

Implement the `separate_appetizers(<dishes>, <appetizers>)` function that takes a `list` of dish names and a `list` of appetizer names.
The function should return the `list` of dish names with appetizer names removed.
Either the `<dishes>` or `<appetizers>` `list` could contain duplicates and may require de-duping.

```python
dishes = ['Avocado Deviled Eggs','Flank Steak with Chimichurri and Asparagus', 'Kingfish Lettuce Cups',
'Grilled Flank Steak with Caesar Salad','Vegetarian Khoresh Bademjan','Avocado Deviled Eggs',
'Barley Risotto','Kingfish Lettuce Cups']

appetizers = ['Kingfish Lettuce Cups','Avocado Deviled Eggs','Satay Steak Skewers',
'Dahi Puri with Black Chickpeas','Avocado Deviled Eggs','Asparagus Puffs',
'Asparagus Puffs']

>>> separate_appetizers(dishes, appetizers)
...
['Vegetarian Khoresh Bademjan', 'Barley Risotto', 'Flank Steak with Chimichurri and Asparagus',
'Grilled Flank Steak with Caesar Salad']
```

## 7. Find Ingredients Used in Only One Recipe

Within in 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 a `list` of dishes and a `<CATEGORY>_INTERSECTIONS` constant for the same category.
Each dish is represented by a `set` of its ingredients.
Each `<CATEGORY>_INTERSECTIONS` is a `set` of ingredients that appear in more than one dish in the category.
Using set operations, your function should return a `set` of "singleton" ingredients (_ingredients appearing in only one dish in the category_).

```python
from categories import example_dishes, EXAMPLE_INTERSECTIONS

>>> singleton_ingredients(example_dishes, EXAMPLE_INTERSECTION)
...
{'vegetable oil', 'vegetable stock', 'barley malt', 'tofu', 'fresh basil', 'lemon', 'ginger', 'honey', 'spaghetti', 'cornstarch', 'yeast', 'red onion', 'breadcrumbs', 'mixed herbs', 'garlic powder', 'celeriac', 'lemon zest', 'sunflower oil', 'mushrooms', 'silken tofu', 'smoked tofu', 'bell pepper', 'cashews', 'oregano', 'tomatoes', 'parsley', 'red pepper flakes', 'rosemary'}
```
Loading