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

Add cater waiter concept exercise #818

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
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 config.json
Original file line number Diff line number Diff line change
@@ -57,6 +57,14 @@
"basics"
],
"status": "wip"
},
{
"slug": "cater-waiter",
"name": "cater-waiter",
"uuid": "40c83f3e-cf87-4f00-9205-8c002b419a8d",
"concepts": [],
"prerequisites": [],
"status": "wip"
}
],
"practice": [
61 changes: 61 additions & 0 deletions exercises/concept/cater-waiter/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Hints

## General

- [Sets][sets] are mutable, unordered collections with no duplicate elements.
- Sets can contain any data type.
- 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. Vectors are iterable.
- Remember: Tuples can be formed using `(<element_1>, <element_2>)`.

## 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. Vectors are iterable.
- Strings can be concatenated with the `*` sign and interpolation can be done via `$()`.

## 3. Categorize Dishes

- Using 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 `issubset(<set>, <iterable>)`
- Tuples can contain any data type, including other tuples. Tuples can be formed using `(<element_1>, <element_2>)`.
- Elements within Tuples can be accessed from the left using a 1-based index number, or from the right using an `end`-based index number (e.g. `<tuple>[end]`).
- The `Set()` constructor can take any [iterable][iterable] as an argument. Vectors are iterable.
- Strings can be concatenated with the `*` sign and interpolation can be done via `$()`.

## 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 `intersect(<set>, <iterable>)` or `∩(<set>, <iterable>)`.
- Elements within Tuples can be accessed from the left using a 1-based index number, or from the right using an `end`-based index number (e.g. `<tuple>[end]`).
- The `Set()` constructor can take any [iterable][iterable] as an argument. Vectors are iterable.
- Tuples can be formed using `(<element_1>, <element_2>)`.

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

- A set _union_ is where elements of `<set_1`> and `<set_2>` are combined into a single `set`
- The set method equivalent of `∪` is `union(<set>, <iterable>)` or `∪(<set>, <iterable>)`
- Using 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. `setdiff(<set_1>, <set_2>)`.
- The `Set()` constructor can take any [iterable][iterable] as an argument. Vectors are iterable.
- The Vector 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. `setdiff(<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 looops to iterate through the various dishes might be useful here.


[iterable]: https://docs.julialang.org/en/v1/base/collections/#Iterable-Collections
[sets]: https://docs.julialang.org/en/v1/base/collections/#Base.Set
143 changes: 143 additions & 0 deletions exercises/concept/cater-waiter/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# 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 `vector` 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.


```julia-repl
julia> 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", Set(["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 `vector` 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 `sets_categories_data.jl`:

```julia-repl
julia> include("sets_categories_data.jl")

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

julia> 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 found in `sets_categories_data.jl` (VEGAN, VEGETARIAN, PALEO, KETO, or OMNIVORE).

```julia-repl
julia> include("sets_categories_data.jl")


julia> categorize_dish("Sticky Lemon Tofu", Set(["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", Set(["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 `vector` 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 `vector` 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 found in `sets_categories_data.py`.

```julia-reple
julia> include("sets_categories_data.jl")

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

>>> tag_special_ingredients(("Arugula and Roasted Pork Salad", ["pork tenderloin", "arugula", "pears", "blue cheese", "pine nuts", "balsamic vinegar", "onions", "black pepper"
]))
...
("Arugula and Roasted Pork Salad", Set(["pork tenderloin", "blue cheese", "pine nuts", "onions"]))
```

## 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 `vector` of dishes and returns a set of all ingredients in all listed dishes.
Each individual dish is represented by its `set` of ingredients.

```julia-repl
dishes = [Set(["tofu", "soy sauce", "ginger", "corn starch", "garlic", "brown sugar", "sesame seeds", "lemon juice"]),
Set(["pork tenderloin", "arugula", "pears", "blue cheese", "pine nuts",
"balsamic vinegar", "onions", "black pepper"]),
Set(["honeydew", "coconut water", "mint leaves", "lime juice", "salt", "english cucumber"])]

julia> compile_ingredients(dishes)
...
Set(["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 `vector` of dish names and a `vector` of appetizer names.
The function should return a `vector` with the list of dish names with appetizer names removed.
Either the `<dishes>` or `<appetizers>` `vector` could contain duplicates and may require de-duping.

```julia-repl
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"]

julia> 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 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 `vector` 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_).

```julia-repl
julia> include("sets_categories_data.jl")

julia> singleton_ingredients(example_dishes, EXAMPLE_INTERSECTION)
...
Set(["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"])
```
389 changes: 389 additions & 0 deletions exercises/concept/cater-waiter/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,389 @@
# Sets


A [set][type-set] is a _mutable_ and _unordered_ collection of objects.
Set members must be distinct — duplicate items are not allowed.
They can hold multiple different data types and even nested structures like a `tuple` of `tuples` or `vector` of `vectors`.

Sets are most commonly used to quickly remove duplicates from other data structures or item groupings.
They are also used for efficient comparisons when sequencing and duplicate tracking are not needed.

Like other collection types (_dictionaries, vectors, tuples_), `sets` support:
- Iteration via `for item in <set>`
- Membership checking via `in` or ``, and `not in` or ``,
- Length calculation through `length()`, and
- Shallow copies through `copy()`

`sets` do not support:
- Indexing of any kind
- Ordering via sorting or insertion
- Slicing


Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases.
Methods such as `union(<set>, <iterable>)`, `intersect(<set>, <iterable>)`, or `setdiff(<set>, <iterable>)` also have constant time complexity (on average).


## Set Literals

A `set` can be directly entered as a _set literal_ with the `Set(<iterable>)` constructor.
Duplicates are silently omitted:

```julia-repl
julia> one_element = Set('😀')
Set{Char} with 1 element:
'😀'
julia> multiple_elements = Set(['😀', '😃', '😄', '😁'])
Set{Char} with 4 elements:
'😁'
'😃'
'😀'
'😄'
julia> multiple_duplicates = Set(["Hello!", "Hello!", "Hello!",
"¡Hola!","Привіт!", "こんにちは!",
"¡Hola!","Привіт!", "こんにちは!"])
Set{String} with 4 elements:
"こんにちは!"
"¡Hola!"
"Привіт!"
"Hello!"
```

You can also use `Set()` to create an empty `set`.


## The Set Constructor

`Set()` (_the constructor for the `set` class_) can be used with any `iterable` passed as an argument.
Elements of the `iterable` are cycled through and added to the `set` individually.
Element order is not preserved and duplicates are silently omitted:

```julia-repl
# To create an empty set, the constructor is used without input.
julia> no_elements = Set()
Set{Any}()
# The tuple is unpacked & each element is added.
# Duplicates are removed.
>>> elements_from_tuple = Set(("Parrot", "Bird",
334782, "Bird", "Parrot"))
Set(["Parrot", 334782, "Bird"])
# The vector is unpacked & each element is added.
# Duplicates are removed.
>>> elements_from_list = Set([2, 3, 2, 3, 3, 3, 5,
7, 11, 7, 11, 13, 13])
Set([5, 7, 13, 2, 11, 3])
```

### Gotchas when Creating Sets

Due to its "unpacking" behavior, using `Set()` with a string might be surprising:

```julia-repl
# String elements (Unicode code points) are
# iterated through and added *individually*.
>>> elements_string = Set("Timbuktu")
Set(['i', 'u', 'k', 't', 'm', 'T', 'b'])
# Unicode separators and positioning code points
# are also added *individually*.
>>> multiple_code_points_string = Set("अभ्यास")
Set(['अ', 'भ', 'य', 'ा', '्', 'स'])
```

Unlike some other languagues, Julia allows mutable collections to be hashed. This can cause some unwanted behavior if one mutates a element of a set since the original hash value will no longer map to the item in that hash slot.

```julia-repl
julia> array1 = ['😅','🤣'];
julia> set1 = Set([array1, ['😂','🙂','🙃'], ['😜', '🤪', '😝']]);
julia> ['😅','🤣'] ∈ set1
true
julia> array1 ∈ set1
true
julia> pop!(array1);
julia> array1
['😅']
julia> set1
Set([['😅'], ['😂', '🙂', '🙃'], ['😜', '🤪', '😝']])
julia> ['😅'] ∈ set1
false
julia> ['😅','🤣'] ∈ set1
false
julia> array1 ∈ set1
false
```

## Working with Sets

Sets have methods that generally mimic [mathematical set operations][mathematical-sets].
Most (_not all_) of these methods have an [operator][operator] equivalent.
Methods and operators generally take any `iterable` as an argument.


### Disjoint Sets

The `isdisjoint(<set>, <other_collection>)` method is used to test if a `sets` elements have any overlap with the elements of another `set`.
The method will accept any `iterable` or `set` as an argument.
It will return `True` if the two sets have **no elements in common**, `False` if elements are **shared**.


```julia-repl
# Both mammals and additional_animals are vectors.
julia> mammals = ["squirrel","dog","cat","cow", "tiger", "elephant"];
julia> additional_animals = ["pangolin", "panda", "parrot",
"lemur", "tiger", "pangolin"];
# Animals is a dict.
julia> animals = Dict("chicken" => "white",
"sparrow" => "gray",
"eagle" => "brown and white",
"albatross" => "gray and white",
"crow" => "black",
"elephant" => "gray",
"dog" => "rust",
"cow" => "black and white",
"tiger" => "orange and black",
"cat" => "gray",
"squirrel" => "black");
# Birds is a set.
julia> birds = Set(["crow","sparrow","eagle","chicken", "albatross"]);
# Mammals and birds don't share any elements.
julia> isdisjoint(birds, mammals)
true
# There are also no shared elements between
# additional_animals and birds.
julia> isdisjoint(birds, additional_animals)
true
# Animals and mammals have shared elements.
# **Note** The objects need to be comparable, so the dictionary `animals` can't be compared
# directly to vector `mammals`, since the former is make up of pairs, while the latter is
# made up of strings. We can use the keys or values of the dictionary though.
julia> isdisjoint(keys(animals), mammals)
false
```


### Subsets

`issubset(<set>, <other_collection>)` is used to check if every element in `<set>` is also in `<other_collection>`.
The operator form is `<set> ⊆ <other_set>`:


```julia-repl
# Both mammals and additional_animals are vectors.
julia> mammals = ["squirrel","dog","cat","cow","tiger","elephant"];
julia> additional_animals = ["pangolin", "panda", "parrot",
"lemur", "tiger", "pangolin"];
# Animals is a dict.
julia> animals = Dict("chicken" => "white",
"sparrow" => "gray",
"eagle" => "brown and white",
"albatross" => "gray and white",
"crow" => "black",
"elephant" => "gray",
"dog" => "rust",
"cow" => "black and white",
"tiger" => "orange and black",
"cat" => "gray",
"squirrel" => "black");
# Birds is a set.
julia> birds = Set(["crow","sparrow","eagle","chicken","albatross"]);
# Set methods will take any iterable as an argument.
# All members of birds are also members of animals.
julia> issubset(birds, animals)
true
# All members of mammals also appear in animals.
julia> issubset(mammals, animals)
true
# We can also use the set operator
julia> birds ⊆ mammals
false
# A set is always a loose subset of itself.
>>> set(additional_animals) ⊆ set(additional_animals)
true
```

There is also an operator `<set> ⊊ <other collection>` which checks if `<set>` is a subset of `<other collection>` without being equal to it (i.e. .a "proper subset")

### Set Intersections

`intersect(<set>, <other iterables>...)` returns a new `set` with elements common to the original `set` and all `<others>` (_in other words, the `set` where everything [intersects][intersection]_).
The operator version of this method is `<set> ∩ <other set> ∩ <other set 2> ∩ ... ∩ <other set n>`:

There is also an `intersect!(<set>, <other iterables>...)` form which overwrites `<set>` with the result of the intersection.

```julia-repl
julia> perennials = Set(["Annatto","Asafetida","Asparagus","Azalea",
"Winter Savory", "Broccoli","Curry Leaf","Fennel",
"Kaffir Lime","Kale","Lavender","Mint","Oranges",
"Oregano", "Tarragon", "Wild Bergamot"]);
julia> annuals = Set(["Corn", "Zucchini", "Sweet Peas", "Marjoram",
"Summer Squash", "Okra","Shallots", "Basil",
"Cilantro", "Cumin", "Sunflower", "Chervil",
"Summer Savory"]);
julia> herbs = ["Annatto","Asafetida","Basil","Chervil","Cilantro",
"Curry Leaf","Fennel","Kaffir Lime","Lavender",
"Marjoram","Mint","Oregano","Summer Savory",
"Tarragon","Wild Bergamot","Wild Celery",
"Winter Savory"];
# Methods will take any iterable as an argument.
julia> perennial_herbs = intersect(perennials, herbs)
Set(["Annatto", "Asafetida", "Curry Leaf", "Fennel", "Kaffir Lime",
"Lavender", "Mint", "Oregano", "Wild Bergamot","Winter Savory"])
# Operators work with any collection.
julia> annuals ∩ herbs
Set(["Basil", "Chervil", "Marjoram", "Cilantro"])
```


### Set Unions

`union(<set>, <other iterables>...)` returns a new `set` with elements from `<set>` and all `<other iterables>`.
The operator form of this method is `<set> ∪ <other set 1> ∪ <other set 2> ∪ ... ∪ <other set n>`:

There is also a `union!(<set>, <other iterables>...)` form which overwrites `<set>` with the result of the union.

```julia-repl
julia> perennials = Set(["Asparagus", "Broccoli", "Sweet Potato", "Kale"]);
julia> annuals = Set(["Corn", "Zucchini", "Sweet Peas", "Summer Squash"]);
julia> more_perennials = ["Radicchio", "Rhubarb", "Spinach", "Watercress"];
# Methods will take any iterable as an argument.
>>> union(perenials, more_perennials)
Set(["Asparagus","Broccoli","Kale","Radicchio","Rhubarb",
"Spinach","Sweet Potato","Watercress"])
```


### Set Differences

`setdiff(<set>, <other iterables>...)` returns a new `set` with elements from the original `<set>` that are not in `<others>`.
There is also a `setdiff!(<set>, <other iterables>...)` form which overwrites `<set>` with the result of the set difference.

```julia-repl
julia> berries_and_veggies = Set(["Asparagus",
"Broccoli",
"Watercress",
"Goji Berries",
"Goose Berries",
"Ramps",
"Walking Onions",
"Blackberries",
"Strawberries",
"Rhubarb",
"Kale",
"Artichokes",
"Currants"]);
julia> veggies = ("Asparagus", "Broccoli", "Watercress", "Ramps",
"Walking Onions", "Rhubarb", "Kale", "Artichokes")
# Methods will take any iterable as an argument.
julia> berries = setdiff(berries_and_veggies, veggies)
Set(["Blackberries","Currants","Goji Berries",
"Goose Berries", "Strawberries"])
```


# Set Symmetric Difference

`symdiff(<set>, <other iterable>...)` returns a new `set` that contains elements that are in `<set>` OR `<other>`, **but not in both**.

There is also a `symdiff!(<set>, <other iterable>...)` which overwrites `<set>` with the result of the symmetric difference.


```julia-repl
julia> plants_1 = Set(['🌲','🍈','🌵', '🥑','🌴', '🥭']);
julia> plants_2 = ('🌸','🌴', '🌺', '🌲', '🌻', '🌵');
# Methods will take any iterable as an argument.
>>> fruit_and_flowers = symdiff(plants_1, plants_2)
>>> fruit_and_flowers
Set(['🌸', '🌺', '🍈', '🥑', '🥭','🌻' ])
```

~~~~exercism/note
A symmetric difference of more than two sets will result in a `set` that includes both the elements unique to each `set` AND elements shared between more than two sets in the series (_details in the Wikipedia article on [symmetric difference][symmetric_difference]_).
To obtain only items unique to each `set` in the series, intersections between all 2-set combinations need to be aggregated in a separate step, and removed:
```julia-repl
julia> one = Set(["black pepper","breadcrumbs","celeriac","chickpea flour",
"flour","lemon","parsley","salt","soy sauce",
"sunflower oil","water"]);
julia> two = Set(["black pepper","cornstarch","garlic","ginger",
"lemon juice","lemon zest","salt","soy sauce","sugar",
"tofu","vegetable oil","vegetable stock","water"]);
julia> three = Set(["black pepper","garlic","lemon juice","mixed herbs",
"nutritional yeast", "olive oil","salt","silken tofu",
"smoked tofu","soy sauce","spaghetti","turmeric"]);
julia> four = Set(["barley malt","bell pepper","cashews","flour",
"fresh basil","garlic","garlic powder", "honey",
"mushrooms","nutritional yeast","olive oil","oregano",
"red onion", "red pepper flakes","rosemary","salt",
"sugar","tomatoes","water","yeast"]);
julia> intersections = (one ∩ two ∪ one ∩ three ∪ one ∩ four ∪
two ∩ three ∪ two ∩ four ∪ three ∩ four)
...
Set(["black pepper","flour","garlic","lemon juice","nutritional yeast",
"olive oil","salt","soy sauce", "sugar","water"])
# The symdiff method will include some of the items in intersections,
# which means it is not a "clean" symmetric difference - there
# are overlapping members.
julia> symdiff(one, two, three, four) ∩ intersections
Set(["black pepper", "garlic", "soy sauce", "water"])
# Overlapping members need to be removed in a separate step
# when there are more than two sets that need symmetric difference.
julia> setdiff(symdiff(one, two, three, four), intersections)
...
Set(["barley malt","bell pepper","breadcrumbs", "cashews","celeriac",
"chickpea flour","cornstarch","fresh basil", "garlic powder",
"ginger","honey","lemon","lemon zest","mixed herbs","mushrooms",
"oregano","parsley","red onion","red pepper flakes","rosemary",
"silken tofu","smoked tofu","spaghetti","sunflower oil", "tofu",
"tomatoes","turmeric","vegetable oil","vegetable stock","yeast"])
```
[symmetric_difference]: https://en.wikipedia.org/wiki/Symmetric_difference
~~~~

[intersection]: https://www.mathgoodies.com/lessons/sets/intersection
[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation
[operator]: https://www.computerhope.com/jargon/o/operator.htm
[type-set]: https://docs.julialang.org/en/v1/base/collections/#Base.Set
18 changes: 18 additions & 0 deletions exercises/concept/cater-waiter/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"authors": [
"depial"
],
"files": {
"solution": [
"cater-waiter.jl"
],
"test": [
"runtests.jl"
],
"exemplar": [
".meta/exemplar.jl"
]
},
"icon": "meetup",
"blurb": "Learn about sets by managing the menus and ingredients for your catering company's event."
}
778 changes: 778 additions & 0 deletions exercises/concept/cater-waiter/.meta/exemplar.jl

Large diffs are not rendered by default.

97 changes: 97 additions & 0 deletions exercises/concept/cater-waiter/cater-waiter.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Functions for compiling dishes and ingredients for a catering company."""

include("sets_categories_data.jl")

"""Remove duplicates from `dish_ingredients`.
:param dish_name: String - containing the dish name.
:param dish_ingredients: Vector - dish ingredients.
:return: tuple - containing (dish_name, ingredient set).
This function should return a `Tuple` with the name of the dish as the first item,
followed by the de-duped `Set` of ingredients as the second item.
"""
function clean_ingredients(dish_name, dish_ingredients)

end

"""Append "Cocktail" (alcohol) or "Mocktail" (no alcohol) to `drink_name`, based on `drink_ingredients`.
:param drink_name: String - name of the drink.
:param drink_ingredients: Vector - ingredients in the drink.
:return: String - drink_name appended with "Mocktail" or "Cocktail".
The function should return the name of the drink followed by "Mocktail" (non-alcoholic) and drink
name followed by "Cocktail" (includes alcohol).
"""
function check_drinks(drink_name, drink_ingredients)

end

"""Categorize `dish_name` based on `dish_ingredients`.
:param dish_name: String - dish to be categorized.
:param dish_ingredients: Set - ingredients for the dish.
:return: String - the dish name appended with ": <CATEGORY>".
This function should return a string with the `dish name: <CATEGORY>` (which meal category the dish belongs to).
`<CATEGORY>` can be any one of (VEGAN, VEGETARIAN, PALEO, KETO, or OMNIVORE).
All dishes will "fit" into one of the categories imported from `sets_categories_data.py`
"""
function categorize_dish(dish_name, dish_ingredients)

end

"""Compare `dish` ingredients to `SPECIAL_INGREDIENTS`.
:param dish: Tuple - of (dish name, list of dish ingredients).
:return: Tuple - containing (dish name, dish special ingredients).
Return the dish name followed by the `Set` of ingredients that require a special note on the dish description.
For the purposes of this exercise, all allergens or special ingredients that need to be tracked are in the
SPECIAL_INGREDIENTS constant imported from `sets_categories_data.py`.
"""
function tag_special_ingredients(dish)

end

"""Create a master list of ingredients.
:param dishes: Vector - of dish ingredient sets.
:return: Set - of ingredients compiled from `dishes`.
This function should return a `Set` of all ingredients from all listed dishes.
"""
function compile_ingredients(dishes)

end

"""Determine which `dishes` are designated `appetizers` and remove them.
:param dishes: Vector - of dish names.
:param appetizers: Vector - of appetizer names.
:return: Vector - of dish names that do not appear on appetizer list.
The function should return the vector of dish names with appetizer names removed.
Either vector could contain duplicates and may require de-duping.
"""
function separate_appetizers(dishes, appetizers)

end

"""Determine which `dishes` have a singleton ingredient (an ingredient that only appears once across dishes).
:param dishes: Vector - of ingredient sets.
:param intersection: constant - can be one of `<CATEGORY>_INTERSECTIONS` constants imported from `sets_categories_data.py`.
:return: Set - containing singleton ingredients.
Each dish is represented by a `Set` of its ingredients.
Each `<CATEGORY>_INTERSECTIONS` 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.
"""
function singleton_ingredients(dishes, intersection)

end
63 changes: 63 additions & 0 deletions exercises/concept/cater-waiter/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using Test

include("cater-waiter.jl")

@testset verbose=true "tests" begin
@testset "clean ingredients" begin
test_data = zip(recipes_with_duplicates[1:3:end], recipes_without_duplicates[1:3:end])

for (item, result) in test_data
@test clean_ingredients(item[1], item[2]) == (result[2], result[3])
end
end

@testset "check_drinks" begin
test_data = zip(all_drinks[1:2:end], drink_names[1:2:end])

for (item, result) in test_data
@test check_drinks(item[1], item[2]) == result
end
end

@testset "categorize dish" begin
test_data = zip(sort(recipes_without_duplicates, rev=true)[1:3:end], dishes_categorized[1:3:end])

for (item, result) in test_data
@test categorize_dish(item[2], item[3]) == result
end
end

@testset "tag special ingredients" begin
test_data = zip(dishes_to_special_label[1:3:end], dishes_labeled[1:3:end])

for (item, result) in test_data
@test tag_special_ingredients(item) == result
end
end

@testset "compile ingredients" begin
test_data = zip(ingredients_only, [VEGAN, VEGETARIAN, PALEO, KETO, OMNIVORE])

for (item, result) in test_data
@test compile_ingredients(item) == result
end
end

@testset "separate appetizers" begin
test_data = zip(dishes_and_appetizers, dishes_cleaned)

for (item, result) in test_data
separateappetizers = separate_appetizers(item[1], item[2])
@test isa(separateappetizers, Vector)
@test sort(separateappetizers) == sort(result)
end
end

@testset "singleton ingredients" begin
test_data = zip(dishes_and_overlap, singletons)

for (item, result) in test_data
@test singleton_ingredients(item[1], item[2]) == result
end
end
end
677 changes: 677 additions & 0 deletions exercises/concept/cater-waiter/sets_categories_data.jl

Large diffs are not rendered by default.

454 changes: 454 additions & 0 deletions exercises/concept/cater-waiter/sets_test_data.jl

Large diffs are not rendered by default.