From f35d3632fc0a9296c00ec80485b261c7c9678fbe Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 11 Jul 2023 11:55:54 -0700 Subject: [PATCH 1/6] Reworked and shortened concept and introduction docs. --- concepts/sets/about.md | 400 ++++++++++------ concepts/sets/introduction.md | 25 +- exercises/concept/cater-waiter/.docs/hints.md | 4 +- .../cater-waiter/.docs/introduction.md | 438 +++++++++++------- 4 files changed, 548 insertions(+), 319 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 5f272402ee..7eb712a90f 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -1,84 +1,110 @@ # Sets -A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects. -Items within a `set` are distinct and duplicate members are not allowed. -Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable]. -Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor. +A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct -- duplicate items are not allowed. +Like most collections, sets can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Sets also come in an immutable [`frozensets`][type-frozenset] flavor. -Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, and iteration via `for item in `. -_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors. -`sets` are most commonly used to quickly dedupe groups of items. -They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._). +Like other collection types (_dictionaries, lists, tuples_), `sets` support: +- Iteration via `for item in ` +- Membership checking via `in` and `not in`, +- Length calculation through `len()`, and +- Shallow copies through `copy()` -Checking membership in a `set` has only O(1) time complexity versus checking for membership in a `list` or `string`, which has worst-case O(n) time complexity. -Operations such as `.union()`, `.intersection()`, or `.difference()` have an average O(n) time complexity. +`sets` do not support: +- Indexing of any kind +- Ordering via sorting or insertion +- Slicing +- Concatenation via `+` -## Construction +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()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). -A `set` can be declared as a _set literal_ with curly `{}` brackets and commas between elements. + +## Set Construction + +While sets can be created in many different ways, the most straightforward construction methods are declaring a _set literal_, using the `set` class constructor (`set()`), and using a _set comprehension_. + +### Set Literals + +A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements. Duplicates are silently omitted: ```python >>> one_element = {'πŸ˜€'} ->>> one_element {'πŸ˜€'} ->>> multiple_elements = {'Hello!', 'Β‘Hola!', 'ΠŸΡ€ΠΈΠ²Π΅Ρ‚!', 'こんにけは!'} ->>> multiple_elements -{'こんにけは!', 'Β‘Hola!', 'Hello!', 'ΠŸΡ€ΠΈΠ²Π΅Ρ‚!'} +>>> multiple_elements = {'πŸ˜€', 'πŸ˜ƒ', 'πŸ˜„', '😁'} +{'πŸ˜€', 'πŸ˜ƒ', 'πŸ˜„', '😁'} ->>> multiple_duplicates = {'Hello!', 'Β‘Hola!', 'ΠŸΡ€ΠΈΠ²Π΅Ρ‚!', 'こんにけは!', 'Β‘Hola!', 'ΠŸΡ€ΠΈΠ²Π΅Ρ‚!'} ->>> multiple_duplicates -{'こんにけは!', 'Β‘Hola!', 'Hello!', 'ΠŸΡ€ΠΈΠ²Π΅Ρ‚!'} +>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!', + 'Β‘Hola!','ΠŸΡ€ΠΈΠ²Ρ–Ρ‚!', 'こんにけは!', + 'Β‘Hola!','ΠŸΡ€ΠΈΠ²Ρ–Ρ‚!', 'こんにけは!'} +{'こんにけは!', 'Β‘Hola!', 'Hello!', 'ΠŸΡ€ΠΈΠ²Ρ–Ρ‚!'} ``` -Set literals use the same curly braces as `dict` literals, so the `set()` constructor must be used to declare an empty `set`. +Set literals use the same curly braces as `dict` literals, which means you need to use `set()` to create an empty `set`. -The `set()` constructor can also be used with any _iterable_ passed as an argument. -Elements are cycled through by the constructor and added to the `set` individually. -Order is not preserved and duplicates are silently omitted: +### 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 added to the `set` individually. +Element order is not preserved and duplicates are silently omitted: ```python >>> no_elements = set() ->>> no_elements set() -# The tuple is unpacked and each distinct element is added. Duplicates are removed. ->>> multiple_elements_from_tuple = set(("Parrot", "Bird", 334782, "Bird", "Parrot")) ->>> multiple_elements_from_tuple +# The tuple is unpacked & each element is added. +# Duplicates are removed. +>>> elements_from_tuple = set(("Parrot", "Bird", + 334782, "Bird", "Parrot")) {334782, 'Bird', 'Parrot'} -# The list is unpacked and each distinct element is added. ->>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13]) ->>> multiple_elements_from_set +# The list is unpacked & each element is added. +>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5, + 7, 11, 7, 11, 13, 13]) {2, 3, 5, 7, 11, 13} ``` -Results when using a set constructor with a string or dictionary may be surprising: +### Set Comprehensions + +Like `lists` and `dicts`, sets can be created via _comprehension_: ```python -# String elements (Unicode code points) are iterated through and added *individually*. ->>> multiple_elements_string = set("Timbuktu") ->>> multiple_elements_string +# First, a list with duplicates +>>> numbers = [1,2,3,4,5,6,6,5,4,8,9,9,9,2,3,12,18] + +# This set comprehension squares the numbers divisible by 3 +# Duplicates are removed. +>>> calculated = {item**2 for item in numbers if item % 3 == 0} +{9, 36, 81, 144, 324} +``` + +### Gotchas when Creating Sets + +Due to its "unpacking" behavior, using the `set` constructor with a string might be surprising: + +```python +# String elements (Unicode code points) are +# iterated through and added *individually*. +>>> elements_string = set("Timbuktu") {'T', 'b', 'i', 'k', 'm', 't', 'u'} -# Unicode separators and positioning code points are also added *individually*. +# Unicode separators and positioning code points +# are also added *individually*. >>> multiple_code_points_string = set('ΰ€…ΰ€­ΰ₯ΰ€―ΰ€Ύΰ€Έ') ->>> multiple_code_points_string {'ΰ€…', 'ΰ€­', 'ΰ€―', 'ΰ€Έ', 'ΰ€Ύ', 'ΰ₯'} - -# The iteration default for dictionaries is over the keys. ->>> source_data = {"fish": "gold", "monkey": "brown", "duck" : "white", "crow": "black"} ->>> set(source_data) -{'crow', 'duck', 'fish', 'monkey'} ``` -Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_: +Remember: sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_: ```python - ->>> lists_as_elements = {['πŸ˜…','🀣'], ['πŸ˜‚','πŸ™‚','πŸ™ƒ'], ['😜', 'πŸ€ͺ', '😝']} +# Attempting to use a list for a set member throws a TypeError +>>> lists_as_elements = {['πŸ˜…','🀣'], + ['πŸ˜‚','πŸ™‚','πŸ™ƒ'], + ['😜', 'πŸ€ͺ', '😝']} Traceback (most recent call last): @@ -87,8 +113,10 @@ Traceback (most recent call last): TypeError: unhashable type: 'list' -# standard sets are mutable, so they cannot be hashed. ->>> sets_as_elements = {{'πŸ˜…','🀣'}, {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, {'😜', 'πŸ€ͺ', '😝'}} +# Standard sets are mutable, so they cannot be hashed. +>>> sets_as_elements = {{'πŸ˜…','🀣'}, + {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, + {'😜', 'πŸ€ͺ', '😝'}} Traceback (most recent call last): File "", line 1, in @@ -97,23 +125,25 @@ Traceback (most recent call last): TypeError: unhashable type: 'set' ``` -Therefore, to create a `set` of `sets`, the contained sets must be of type `frozenset()` +However, a `set` of `sets` can be created via type `frozenset()`: ```python -# frozensets don't have a literal form +# Frozensets don't have a literal form. >>> set_1 = frozenset({'😜', '😝', 'πŸ€ͺ'}) >>> set_2 = frozenset({'πŸ˜…', '🀣'}) >>> set_3 = frozenset({'πŸ˜‚', 'πŸ™‚', 'πŸ™ƒ'}) >>> frozen_sets_as_elements = {set_1, set_2, set_3} >>> frozen_sets_as_elements -{frozenset({'😜', '😝', 'πŸ€ͺ'}), frozenset({'πŸ˜…', '🀣'}), frozenset({'πŸ˜‚', 'πŸ™‚', 'πŸ™ƒ'})} +{frozenset({'😜', '😝', 'πŸ€ͺ'}), frozenset({'πŸ˜…', '🀣'}), +frozenset({'πŸ˜‚', 'πŸ™‚', 'πŸ™ƒ'})} ``` -## Working with Sets -Elements can be added/removed using `.add()` / `.remove()`. -`remove()` will raise a `KeyError` if the item is not present in the `set`. +## Adding and Removing Set Members + +Elements can be added or removed from a `set` using the methods `.add()` and `.remove()`. +The `.remove()` method will raise a `KeyError` if the item is not present in the `set`: ```python >>> creatures = {'crow', 'duck', 'fish', 'monkey', 'elephant'} @@ -122,7 +152,7 @@ Elements can be added/removed using `.add()` / `.remove()` >>> creatures {'beaver', 'crow', 'elephant', 'fish', 'monkey'} -# Trying to remove an item that is not present will raise a KeyError +# Trying to remove an item that is not present raises a KeyError >>> creatures.remove('bear') Traceback (most recent call last): @@ -132,90 +162,131 @@ Traceback (most recent call last): KeyError: 'bear' ``` -`.discard()` will also remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present. -`.clear()` will remove all items. -`.pop()` will remove and _return_ an **arbitrary** item and raises a `KeyError` if the `set` is empty. +### Additional Strategies for Removing Set Members -## Set Methods +- `.discard()` will remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present. +- `.clear()` will remove all items from the set. +- `.pop()` will remove and _return_ an **arbitrary** item, and raises a `KeyError` if the `set` is empty. -Sets implement methods that generally mimic [mathematical set operations][mathematical-sets]. -Most (_though not all_) of these methods can be performed using either operator(s) or method call(s). -Using operators requires that both inputs be `sets` or `frozensets`, while methods will generally take any iterable as an argument. -### Fast Membership Testing Between Groups +## Set Operations -The `.isdisjoint()` method is used to test if a `set` has **no elements in common** with another set or iterable. -It will accept any `iterable` or `set` as an arugment, returning `True` if they are **disjoint**, `False` otherwise. -Note that for `dcts`, the iteration default is over`.keys()`. +Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. +Most (_not all_) of these methods have an [operator][operator] equivalent. +Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. -```python ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} -# Dictionary of animal names with colors ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'orange and black', - 'cat': 'grey','squirrel': 'black'} +### Membership Testing Between Sets -# List of additonal animals ->>> additional_animals = ['pangolin', 'panda', 'parrot', 'lemur', 'tiger', 'pangolin'] -... +The `.isdisjoint()` method is used to test if a `sets` elements have any overlap with the elements of another. +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**. ->>> mammals.isdisjoint(birds) +```python +# Both mammals and additional_animals are lists. +>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant'] +>>> additional_animals = ['pangolin', 'panda', 'parrot', + 'lemur', 'tiger', 'pangolin'] + +# Animals is a dict. +>>> animals = {'chicken': 'white', + 'sparrow': 'grey', + 'eagle': 'brown and white', + 'albatross': 'grey and white', + 'crow': 'black', + 'elephant': 'grey', + 'dog': 'rust', + 'cow': 'black and white', + 'tiger': 'orange and black', + 'cat': 'grey', + 'squirrel': 'black'} + +# Birds is a set. +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} + +# Mammals and birds don't share any elements. +>>> birds.isdisjoint(mammals) True ->>> mammals.isdisjoint(animals) -False - +# There are also no shared elements between +# additional_animals and birds. >>> birds.isdisjoint(additional_animals) True ->>> set(additional_animals).isdisjoint(animals) +# Animals and mammals have shared elements. +# **Note** The first object needs to be a set or converted to a set +# since .isdisjoint() is a set method. +>>> set(animals).isdisjoint(mammals) False ``` -`.issubset()` | ` <= ` are used to check if every element in `` is also in ``. -`.issuperset()` | ` >= ` are used to check the inverse -- if every element in `` is also in ``. +### Checking for Subsets and Supersets -```python ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'organge and black', - 'cat': 'grey','squirrel': 'black'} +- `.issubset()` is used to check if every element in `` is also in ``. +The operator form is ` <= `: ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} +```python +# Set methods will take any iterable as an argument. +# All members of birds are also members of animals. +>>> birds.issubset(animals) +True -# Methods will take any iterable as an argument ->>> mammals.issubset(animal_colors) +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issubset() is a set method. +>>> set(mammals).issubset(animals) True +# Both objects need to be sets to use a set operator +>>> birds <= set(mammals) +False -# A set is always a loose subset of itself ->>> animals <= animals +# A set is always a loose subset of itself. +>>> set(additional_animals) <= set(additional_animals) True +``` + + `.issuperset()` is the inverse of `.issubset()`. + It is used to check if every element in `` is also in ``. +The operator form is ` >= `: ->>> birds <= animals +```python +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issuperset() is a set method. +>>> set(animals).issuperset(mammals) True ->>> birds <= mammals +# All members of animals do not show up as members of birds. +>>> birds.issuperset(animals) False + +# Both objects need to be sets to use a set operator +>>> birds >= set(mammals) +False + +# A set is always a loose superset of itself. +>>> set(animals) <= set(animals) +True ``` -` < ` and ` > ` are used to test for _proper subsets_: -(`` <= ``) AND (`` != ``) for the `<` operator; (`` >= ``) AND (`` != ``) for the `>` operator. -They have no method equivelent. +### 'Proper' Subsets and Supersets + +` < ` and ` > ` are used to test for _proper subsets_. +A `set` is a proper subset if (`` <= ``) **AND** (`` != ``) for the `<` operator. + +A `set is a proper superset if `(`` >= ``) **AND** (`` != ``) for the `>` operator. +These operators have no method equivalent: ```python ->>> animal_names = {'albatross','cat','chicken','cow','crow','dog', - 'eagle','elephant','sparrow','squirrel','tiger'} +>>> animal_names = {'albatross','cat','chicken','cow','crow','dog', + 'eagle','elephant','sparrow','squirrel','tiger'} ->>> animal_names_also = {'albatross','cat','chicken','cow','crow','dog', - 'eagle','elephant','sparrow','squirrel','tiger'} +>>> animals_also = {'albatross','cat','chicken','cow','crow','dog', + 'eagle','elephant','sparrow','squirrel','tiger'} ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} +>>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} >>> mammals < animal_names True @@ -223,81 +294,114 @@ True >>> animal_names > birds True -# A set is never a *proper subset* of itself ->>> animal_names_also < animal_names +# A set is not a *proper subset* if set == other set. +>>> animals_also < animal_names False - ->>> animals < animals - +# A set is never a *proper subset* of itself +>>> animals_also < animals_also +False ``` -### Set Operations +### Set Unions -`.union(*)` and ` | | | ... | ` return a new `set` with elements from `` and all ``. +`.union(*)` returns a new `set` with elements from `` and all ``. +The operator form of this method is ` | | | ... | `. ```python ->>> perennial_vegetables = {'Asparagus', 'Broccoli', 'Sweet Potatoe', 'Kale'} ->>> annual_vegetables = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} - ->>> more_perennials = ['Radicchio', 'Rhubarb', 'Spinach', 'Watercress'] +>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'} +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} +>>> more_perennials = ['Radicchio', 'Rhubarb', + 'Spinach', 'Watercress'] # Methods will take any iterable as an argument. ->>> perennial_vegetables.union(more_perennials) -{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb','Spinach','Sweet Potatoe','Watercress'} +>>> perennials.union(more_perennials) +{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb', +'Spinach','Sweet Potato','Watercress'} # Operators require sets. ->>> perennial_vegetables | annual_vegetables -{'Asparagus','Broccoli','Corn','Kale','Summer Squash','Sweet Peas','Sweet Potatoe','Zucchini'} - +>>> set(more_perennials) | perennials +{'Asparagus', + 'Broccoli', + 'Kale', + 'Radicchio', + 'Rhubarb', + 'Spinach', + 'Sweet Potato', + 'Watercress'} ``` -`.difference(*)` and ` - - - ...` return a new `set` with elements from the original `` that are not in ``. +### Set Differences + +`.difference(*)` returns a new `set` with elements from the original `` that are not in ``. +The operator version of this method is ` - - - ...`. ```python ->>> berries_and_veggies = {'Asparagus', 'Broccoli', 'Watercress', 'Goji Berries', 'Goose Berries', 'Ramps', - 'Walking Onions', 'Raspberries','Blueberries', 'Blackberries', 'Strawberries', - 'Rhubarb', 'Kale', 'Artichokes', 'Currants', 'Honeyberries'} +>>> berries_and_veggies = {'Asparagus', + 'Broccoli', + 'Watercress', + 'Goji Berries', + 'Goose Berries', + 'Ramps', + 'Walking Onions', + 'Blackberries', + 'Strawberries', + 'Rhubarb', + 'Kale', + 'Artichokes', + 'Currants'} -# Methods will take any iterable as an argument. >>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps', 'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes') ->>> just_berries = berries_and_veggies.difference(veggies) ->>> just_berries -{'Blackberries','Blueberries','Currants','Goji Berries', - 'Goose Berries','Honeyberries','Raspberries','Strawberries'} +# Methods will take any iterable as an argument. +>>> berries = berries_and_veggies.difference(veggies) +{'Blackberries','Currants','Goji Berries', + 'Goose Berries', 'Strawberries'} +# Operators require sets. >>> berries_and_veggies - just_berries -{'Artichokes','Asparagus','Broccoli','Kale','Ramps','Rhubarb','Walking Onions','Watercress'} +{'Artichokes','Asparagus','Broccoli','Kale', +'Ramps','Rhubarb','Walking Onions','Watercress'} ``` -`.intersection(*)` and ` & & & ... ` return a new `set` with elements common to the original `set` and all ``. +### Set Intersections + +`.intersection(*)` returns a new `set` with elements common to the original `set` and all `` (in other words, the `set` where everything [intersects][intersection]). +The operator version of this method is ` & & & ... ` ```python ->>> perennials = {'Annatto','Asafetida','Asparagus','Azalea','Winter Savory', 'Blackberries','Broccoli','Curry Leaf', - 'Fennel','French Sorrel','Fuchsia','Kaffir Lime','Kale','Lavender','Mint','Oranges', - 'Oregano','Ramps','Roses','Tarragon','Watercress','Wild Bergamot'} +>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea', + 'Winter Savory', 'Broccoli','Curry Leaf','Fennel', + 'Kaffir Lime','Kale','Lavender','Mint','Oranges', + 'Oregano', 'Tarragon', 'Wild Bergamot'} ->>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 'Summer Squash', 'Okra', - 'Shallots', 'Basil', 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 'Summer Savory'} +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', + 'Summer Squash', 'Okra','Shallots', 'Basil', + 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', + 'Summer Savory'} ->>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro','Curry Leaf','Fennel','Kaffir Lime', - 'Lavender','Marjoram','Mint','Oregano','Summer Savory' 'Tarragon','Wild Bergamot', - 'Wild Celery','Winter Savory'] +>>> 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. >>> perennial_herbs = perennials.intersection(herbs) ->>> perennial_herbs -{'Mint', 'Annatto', 'Winter Savory', 'Curry Leaf', 'Lavender', 'Fennel', - 'Oregano', 'Kaffir Lime','Asafetida', 'Wild Bergamot', 'Tarragon'} +{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime', + 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'} +# Operators require both groups be sets. >>> annuals & set(herbs) {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} ``` -`.symmetric_difference()` and ` ^ ` return a new `set` that contains elements that are in `` OR ``, but **not in both**. +# Set Symmetric Difference + +`.symmetric_difference()` returns a new `set` that contains elements that are in `` OR ``, but **not in both**. +The operator version of this method is ` ^ `. ```python >>> plants_1 = {'🌲','🍈','🌡', 'πŸ₯‘','🌴', 'πŸ₯­'} @@ -309,6 +413,8 @@ False >>> fruit_and_flowers {'🌸', '🌺', '🍈', 'πŸ₯‘', 'πŸ₯­','🌻' } + +# Operators require both groups be sets. >>> fruit_and_flowers ^ plants_1 {'🌲', '🌸', '🌴', '🌡','🌺', '🌻'} @@ -316,7 +422,9 @@ False { 'πŸ₯‘', '🌴','🌲', '🌡', '🍈', 'πŸ₯­'} ``` -[type-set]: https://docs.python.org/3/library/stdtypes.html#set -[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset -[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable +[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation +[operator]: https://www.computerhope.com/jargon/o/operator.htm +[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset +[type-set]: https://docs.python.org/3/library/stdtypes.html#set +[intersection]: https://www.mathgoodies.com/lessons/sets/intersection diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md index b2eaddc8e6..ee98b39eec 100644 --- a/concepts/sets/introduction.md +++ b/concepts/sets/introduction.md @@ -1,14 +1,25 @@ # Sets -A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects. -Items within a `set` are unique, and no duplicates are allowed. -Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable]. -Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor. +A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct -- duplicate items are not allowed. +Like most collections, sets can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Sets also come in an immutable [`frozensets`][type-frozenset] flavor. -Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, & iteration via `for item in `. -_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors. -`sets` are most commonly used to quickly dedupe groups of items. +Like other collection types (_dictionaries, lists, tuples_), `sets` support: +- Iteration via `for item in ` +- Membership checking via `in` and `not in`, +- Length calculation through `len()`, and +- Shallow copies through `copy()` + +`sets` do not support: +- Indexing of any kind +- Ordering via sorting or insertion +- Slicing +- Concatenation via `+` + +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()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). [type-set]: https://docs.python.org/3/library/stdtypes.html#set [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable diff --git a/exercises/concept/cater-waiter/.docs/hints.md b/exercises/concept/cater-waiter/.docs/hints.md index 056dcceecb..89f51753bc 100644 --- a/exercises/concept/cater-waiter/.docs/hints.md +++ b/exercises/concept/cater-waiter/.docs/hints.md @@ -3,14 +3,14 @@ ## 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 can contain any data type, as long as all elements are [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. +- 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 `(, )` or via the `tuple()` constructor. ## 2. Cocktails and Mocktails diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 905504a63b..5f0ff904e7 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -1,63 +1,92 @@ # Sets -A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects. -Items within a `set` are distinct and duplicate members are not allowed. -Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable]. -Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor. +A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. +Set members must be distinct -- duplicate items are not allowed. +Like most collections, sets can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +Sets also come in an immutable [`frozensets`][type-frozenset] flavor. -Like other collections, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, and iteration via `for item in `. -_Unlike_ sequence type collections (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors. -Sets are most commonly used to quickly dedupe groups of items. -They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._). +Like other collection types (_dictionaries, lists, tuples_), `sets` support: +- Iteration via `for item in ` +- Membership checking via `in` and `not in`, +- Length calculation through `len()`, and +- Shallow copies through `copy()` -Sets are more space-efficient than a keys-only dictionary and faster than a `list` or `array` for membership -- unless you need to keep track of sequenced or duplicated items. +`sets` do not support: +- Indexing of any kind +- Ordering via sorting or insertion +- Slicing +- Concatenation via `+` -## Construction +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()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). -A `set` can be declared as a _set literal_ with curly `{}` brackets and commas between elements. + +## Set Literals + +A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements. +Duplicates are silently omitted: ```python >>> one_element = {'πŸ˜€'} ->>> one_element {'πŸ˜€'} >>> multiple_elements = {'πŸ˜€', 'πŸ˜ƒ', 'πŸ˜„', '😁'} ->>> multiple_elements {'πŸ˜€', 'πŸ˜ƒ', 'πŸ˜„', '😁'} ->>> multiple_duplicates = {'πŸ˜€', 'πŸ˜ƒ', 'πŸ˜„', '😁', 'πŸ˜ƒ', 'πŸ˜„'} ->>> multiple_duplicates -{'πŸ˜€', '😁', 'πŸ˜ƒ', 'πŸ˜„'} +>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!', + 'Β‘Hola!','ΠŸΡ€ΠΈΠ²Ρ–Ρ‚!', 'こんにけは!', + 'Β‘Hola!','ΠŸΡ€ΠΈΠ²Ρ–Ρ‚!', 'こんにけは!'} +{'こんにけは!', 'Β‘Hola!', 'Hello!', 'ΠŸΡ€ΠΈΠ²Ρ–Ρ‚!'} ``` -Set literals use the same curly braces as `dict` literals, so the `set()` constructor must be used to declare an empty `set`. +Set literals use the same curly braces as `dict` literals, which means you need to use `set()` to create an empty `set`. + +## The Set Constructor -The `set()` constructor can also be used with any _iterable_ passed as an argument. -Elements inside the iterable are cycled through by the constructor and added to the `set` individually. -Order is not preserved and duplicates are silently omitted: +`set()` (_the constructor for the `set` class_) can be used with any `iterable` passed as an argument. +Elements of the `iterable` are cycled through added to the `set` individually. +Element order is not preserved and duplicates are silently omitted: ```python >>> no_elements = set() ->>> no_elements set() -# The tuple is unpacked and each distinct element is added. Duplicates are removed. ->>> multiple_elements_from_tuple = set(("Parrot", "Bird", 334782, "Bird", "Parrot")) ->>> multiple_elements_from_tuple +# The tuple is unpacked & each element is added. +# Duplicates are removed. +>>> elements_from_tuple = set(("Parrot", "Bird", + 334782, "Bird", "Parrot")) {334782, 'Bird', 'Parrot'} -# The list is unpacked and each distinct element is added. ->>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13]) ->>> multiple_elements_from_set +# The list is unpacked & each element is added. +>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5, + 7, 11, 7, 11, 13, 13]) {2, 3, 5, 7, 11, 13} ``` -Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_: +### Gotchas when Creating Sets + +Due to its "unpacking" behavior, using `set()` with a string might be surprising: ```python +# String elements (Unicode code points) are +# iterated through and added *individually*. +>>> elements_string = set("Timbuktu") +{'T', 'b', 'i', 'k', 'm', 't', 'u'} + +# Unicode separators and positioning code points +# are also added *individually*. +>>> multiple_code_points_string = set('ΰ€…ΰ€­ΰ₯ΰ€―ΰ€Ύΰ€Έ') +{'ΰ€…', 'ΰ€­', 'ΰ€―', 'ΰ€Έ', 'ΰ€Ύ', 'ΰ₯'} +``` ->>> lists_as_elements = {['πŸ˜…','🀣'], ['πŸ˜‚','πŸ™‚','πŸ™ƒ'], ['😜', 'πŸ€ͺ', '😝']} +Sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_: + +```python +# Attempting to use a list for a set member throws a TypeError +>>> lists_as_elements = {['πŸ˜…','🀣'], + ['πŸ˜‚','πŸ™‚','πŸ™ƒ'], + ['😜', 'πŸ€ͺ', '😝']} Traceback (most recent call last): @@ -67,7 +96,9 @@ Traceback (most recent call last): TypeError: unhashable type: 'list' # Standard sets are mutable, so they cannot be hashed. ->>> sets_as_elements = {{'πŸ˜…','🀣'}, {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, {'😜', 'πŸ€ͺ', '😝'}} +>>> sets_as_elements = {{'πŸ˜…','🀣'}, + {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, + {'😜', 'πŸ€ͺ', '😝'}} Traceback (most recent call last): File "", line 1, in @@ -76,216 +107,295 @@ Traceback (most recent call last): TypeError: unhashable type: 'set' ``` -## Working with Sets -Sets implement methods that generally mimic [mathematical set operations][mathematical-sets]. -Most (_though not all_) of these methods can be performed using either operator(s) or method call(s). -Using operators requires that both inputs be `sets` or `frozensets`, while methods will generally take any iterable as an argument. +## Working with Sets -### Fast Membership Testing +Sets have methods that generally mimic [mathematical set operations][mathematical-sets]. +Most (_not all_) of these methods have an [operator][operator] equivalent. +Methods generally take any `iterable` as an argument, while operators require that both things being compared are `sets` or `frozensets`. -**Subsets**: `.issubset()` / ` <= ` - are used to check if every element in `` is also in ``. +### Disjoint Sets -**Supersets**: `.issuperset()` / ` >= ` - are used to check the inverse -- if every element in `` is also in ``. +The `.isdisjoint()` 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**. +There is no operator equivalent: ```python ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'organge and black', - 'cat': 'grey','squirrel': 'black'} - ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} - -# Methods will take any iterable as an argument ->>> mammals.issubset(animals) -True - -# A set is always a loose subset of itself ->>> birds <= birds +# Mammals and birds don't share any elements. +>>> birds.isdisjoint(mammals) True ->>> birds <= set(animals) +# There are also no shared elements between +# additional_animals and birds. +>>> birds.isdisjoint(additional_animals) True ->>> birds <= mammals +# Animals and mammals have shared elements. +# **Note** The first object needs to be a set or converted to a set +# since .isdisjoint() is a set method. +>>> set(animals).isdisjoint(mammals) False ``` -The `.isdisjoint()` method is used to test if a `set` has **no elements in common** with another set or iterable. -It will accept any `iterable` or `set` as an argument, returning `True` if they are **disjoint**, `False` otherwise. -Note that for `dicts`, the iteration default is over`.keys()`. +### Subsets and Supersets -```python ->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'} ->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} +`.issubset()` is used to check if every element in `` is also in ``. +The operator form is ` <= `: -# Dictionary of animal names with colors ->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white', - 'albatross': 'grey and white','crow': 'black','elephant': 'grey', - 'dog': 'rust','cow': 'black and white','tiger': 'orange and black', - 'cat': 'grey','squirrel': 'black'} -# List of additional animals ->>> additional_animals = ['pangolin', 'panda', 'parrot', 'lemur', 'tiger', 'pangolin'] -... +```python +# Both mammals and additional_animals are lists. +>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant'] +>>> additional_animals = ['pangolin', 'panda', 'parrot', + 'lemur', 'tiger', 'pangolin'] + +# Animals is a dict. +>>> animals = {'chicken': 'white', + 'sparrow': 'grey', + 'eagle': 'brown and white', + 'albatross': 'grey and white', + 'crow': 'black', + 'elephant': 'grey', + 'dog': 'rust', + 'cow': 'black and white', + 'tiger': 'orange and black', + 'cat': 'grey', + 'squirrel': 'black'} + +# Birds is a set. +>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'} + +# Set methods will take any iterable as an argument. +# All members of birds are also members of animals. +>>> birds.issubset(animals) +True ->>> mammals.isdisjoint(birds) +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issubset() is a set method. +>>> set(mammals).issubset(animals) True ->>> mammals.isdisjoint(animals) +# Both objects need to be sets to use a set operator +>>> birds <= set(mammals) False ->>> birds.isdisjoint(additional_animals) +# A set is always a loose subset of itself. +>>> set(additional_animals) <= set(additional_animals) True +``` ->>> set(additional_animals).isdisjoint(animals) + `.issuperset()` is the inverse of `.issubset()`. +It is used to check if every element in `` is also in ``. +The operator form is ` >= `: + + +```python +# All members of mammals also appear in animals. +# **Note** The first object needs to be a set or converted to a set +# since .issuperset() is a set method. +>>> set(animals).issuperset(mammals) +True + +# All members of animals do not show up as members of birds. +>>> birds.issuperset(animals) False + +# Both objects need to be sets to use a set operator +>>> birds >= set(mammals) +False + +# A set is always a loose superset of itself. +>>> set(animals) <= set(animals) +True ``` -### Operations Between Sets -**Union**: `.union(*)` and ` | | | ... | ` return a new `set` with elements from `` and all ``. +### Set Intersections + +`.intersection(*)` returns a new `set` with elements common to the original `set` and all `` (_in other words, the `set` where everything [intersects][intersection]_). +The operator version of this method is ` & & & ... `: + ```python ->>> perennial_vegetables = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'} ->>> annual_vegetables = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} +>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea', + 'Winter Savory', 'Broccoli','Curry Leaf','Fennel', + 'Kaffir Lime','Kale','Lavender','Mint','Oranges', + 'Oregano', 'Tarragon', 'Wild Bergamot'} ->>> more_perennials = ['Radicchio', 'Rhubarb', 'Spinach', 'Watercress'] +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', + 'Summer Squash', 'Okra','Shallots', 'Basil', + 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', + 'Summer Savory'} -# Methods will take any iterable as an argument. ->>> perennial_vegetables.union(more_perennials) -{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb','Spinach','Sweet Potato','Watercress'} +>>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro', + 'Curry Leaf','Fennel','Kaffir Lime','Lavender', + 'Marjoram','Mint','Oregano','Summer Savory' + 'Tarragon','Wild Bergamot','Wild Celery', + 'Winter Savory'] -# Operators require sets. ->>> perennial_vegetables | annual_vegetables -{'Asparagus','Broccoli','Corn','Kale','Summer Squash','Sweet Peas','Sweet Potato','Zucchini'} +# Methods will take any iterable as an argument. +>>> perennial_herbs = perennials.intersection(herbs) +{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime', + 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'} + +# Operators require both groups be sets. +>>> annuals & set(herbs) + {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} ``` -**Difference**: `.difference(*)` and ` - - - ...` return a new `set` with elements from the original `` that are not in ``. + +### Set Unions + +`.union(*)` returns a new `set` with elements from `` and all ``. +The operator form of this method is ` | | | ... | `: + ```python ->>> berries_and_veggies = {'Asparagus', 'Broccoli', 'Watercress', 'Goji Berries', 'Goose Berries', 'Ramps', - 'Walking Onions', 'Raspberries','Blueberries', 'Blackberries', 'Strawberries', - 'Rhubarb', 'Kale', 'Artichokes', 'Currants', 'Honeyberries'} +>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'} +>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'} +>>> more_perennials = ['Radicchio', 'Rhubarb', + 'Spinach', 'Watercress'] # Methods will take any iterable as an argument. ->>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps', - 'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes') - ->>> just_berries = berries_and_veggies.difference(veggies) ->>> just_berries -{'Blackberries','Blueberries','Currants','Goji Berries', - 'Goose Berries','Honeyberries','Raspberries','Strawberries'} +>>> perennials.union(more_perennials) +{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb', +'Spinach','Sweet Potato','Watercress'} ->>> berries_and_veggies - just_berries -{'Artichokes','Asparagus','Broccoli','Kale','Ramps','Rhubarb','Walking Onions','Watercress'} +# Operators require sets. +>>> set(more_perennials) | perennials +{'Asparagus', + 'Broccoli', + 'Kale', + 'Radicchio', + 'Rhubarb', + 'Spinach', + 'Sweet Potato', + 'Watercress'} ``` -**Intersection**: `.intersection(*)` and ` & & & ... ` return a new `set` with elements common to the original `set` and all ``. -```python ->>> perennials = {'Annatto','Asafetida','Asparagus','Azalea','Winter Savory', 'Blackberries','Broccoli','Curry Leaf', - 'Fennel','French Sorrel','Fuchsia','Kaffir Lime','Kale','Lavender','Mint','Oranges', - 'Oregano','Ramps','Roses','Tarragon','Watercress','Wild Bergamot'} +### Set Differences ->>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 'Summer Squash', 'Okra', - 'Shallots', 'Basil', 'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 'Summer Savory'} +`.difference(*)` returns a new `set` with elements from the original `` that are not in ``. +The operator version of this method is ` - - - ...`. ->>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro','Curry Leaf','Fennel','Kaffir Lime', - 'Lavender','Marjoram','Mint','Oregano','Summer Savory' 'Tarragon','Wild Bergamot', - 'Wild Celery','Winter Savory'] +```python +>>> berries_and_veggies = {'Asparagus', + 'Broccoli', + 'Watercress', + 'Goji Berries', + 'Goose Berries', + 'Ramps', + 'Walking Onions', + 'Blackberries', + 'Strawberries', + 'Rhubarb', + 'Kale', + 'Artichokes', + 'Currants'} +>>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps', + 'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes') # Methods will take any iterable as an argument. ->>> perennial_herbs = perennials.intersection(herbs) ->>> perennial_herbs -{'Mint', 'Annatto', 'Winter Savory', 'Curry Leaf', 'Lavender', 'Fennel', - 'Oregano', 'Kaffir Lime','Asafetida', 'Wild Bergamot', 'Tarragon'} +>>> berries = berries_and_veggies.difference(veggies) +{'Blackberries','Currants','Goji Berries', + 'Goose Berries', 'Strawberries'} ->>> annuals & set(herbs) - {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} +# Operators require sets. +>>> berries_and_veggies - just_berries +{'Artichokes','Asparagus','Broccoli','Kale', +'Ramps','Rhubarb','Walking Onions','Watercress'} ``` -**Symmetric Difference**: `.symmetric_difference()` and ` ^ ` return a new `set` that contains elements that are in `` OR ``, but **not in both**. -```python ->>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour', - 'flour','lemon','parsley','salt','soy sauce','sunflower oil','water'} +# Set Symmetric Difference ->>> two = {'black pepper','cornstarch','garlic','ginger','lemon juice','lemon zest', - 'salt','soy sauce','sugar','tofu','vegetable oil','vegetable stock','water'} +`.symmetric_difference()` returns a new `set` that contains elements that are in `` OR ``, **but not in both**. +The operator version of this method is ` ^ `: ->>> two_as_list = ['black pepper','cornstarch','garlic','ginger','lemon juice','lemon zest', - 'salt','soy sauce','sugar','tofu','vegetable oil','vegetable stock','water'] ->>> one ^ two -... -{'breadcrumbs','celeriac','chickpea flour','cornstarch','flour','garlic','ginger', 'lemon', -'lemon juice','lemon zest','parsley','sugar','sunflower oil','tofu','vegetable oil','vegetable stock'} +```python +>>> plants_1 = {'🌲','🍈','🌡', 'πŸ₯‘','🌴', 'πŸ₯­'} +>>> plants_2 = ('🌸','🌴', '🌺', '🌲', '🌻', '🌡') ->>> (one | two) - (one & two) -... -{'breadcrumbs','celeriac','chickpea flour','cornstarch','flour','garlic','ginger', 'lemon', -'lemon juice','lemon zest','parsley','sugar','sunflower oil','tofu','vegetable oil','vegetable stock'} ->>> one ^ two == (one | two) - (one & two) -... -True +# Methods will take any iterable as an argument. +>>> fruit_and_flowers = plants_1.symmetric_difference(plants_2) +>>> fruit_and_flowers +{'🌸', '🌺', '🍈', 'πŸ₯‘', 'πŸ₯­','🌻' } -# Methods will take any iterable as an argument. ->>> one.symmetric_difference(two_as_list) -... -{'breadcrumbs','celeriac','chickpea flour','cornstarch','flour','garlic','ginger', 'lemon', -'lemon juice','lemon zest','parsley','sugar','sunflower oil','tofu','vegetable oil','vegetable stock'} +# Operators require both groups be sets. +>>> fruit_and_flowers ^ plants_1 +{'🌲', '🌸', '🌴', '🌡','🌺', '🌻'} + +>>> fruit_and_flowers ^ plants_2 +{ 'πŸ₯‘', '🌴','🌲', '🌡', '🍈', 'πŸ₯­'} ``` -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. +~~~~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: + ```python >>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour', - 'flour','lemon','parsley','salt','soy sauce','sunflower oil','water'} + 'flour','lemon','parsley','salt','soy sauce', + 'sunflower oil','water'} ->>> two = {'black pepper','cornstarch','garlic','ginger','lemon juice','lemon zest', - 'salt','soy sauce','sugar','tofu','vegetable oil','vegetable stock','water'} +>>> two = {'black pepper','cornstarch','garlic','ginger', + 'lemon juice','lemon zest','salt','soy sauce','sugar', + 'tofu','vegetable oil','vegetable stock','water'} ->>> three = {'black pepper','garlic','lemon juice','mixed herbs','nutritional yeast', - 'olive oil','salt','silken tofu','smoked tofu','soy sauce','spaghetti','turmeric'} +>>> three = {'black pepper','garlic','lemon juice','mixed herbs', + 'nutritional yeast', 'olive oil','salt','silken tofu', + 'smoked tofu','soy sauce','spaghetti','turmeric'} ->>> four = {'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'} +>>> four = {'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'} ->>> intersections = (one & two | one & three | one & four | two & three | two & four | three & four) ->>> intersections - ... - {'black pepper','flour','garlic','lemon juice','nutritional yeast', 'olive oil','salt','soy sauce', 'sugar','water'} - ->>> one ^ two ^ three ^ four +>>> intersections = (one & two | one & three | one & four | + two & three | two & four | three & four) ... -{'barley malt','bell pepper','black pepper','breadcrumbs','cashews','celeriac','chickpea flour','cornstarch', - 'fresh basil','garlic','garlic powder','ginger','honey','lemon','lemon zest','mixed herbs','mushrooms', - 'oregano','parsley','red onion','red pepper flakes','rosemary','silken tofu','smoked tofu','soy sauce', - 'spaghetti','sunflower oil','tofu','tomatoes','turmeric','vegetable oil','vegetable stock','water','yeast'} +{'black pepper','flour','garlic','lemon juice','nutritional yeast', +'olive oil','salt','soy sauce', 'sugar','water'} + +# The ^ operation will include some of the items in intersections, +# which means it is not a "clean" symmetric difference - there +# are overlapping members. +>>> (one ^ two ^ three ^ four) & intersections +{'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. >>> (one ^ two ^ three ^ four) - intersections ... -{'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'} +{'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 -[type-set]: https://docs.python.org/3/library/stdtypes.html#set -[type-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset -[mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation +~~~~ + [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable +[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-frozenset]: https://docs.python.org/3/library/stdtypes.html#frozenset +[type-set]: https://docs.python.org/3/library/stdtypes.html#set From 1b5ed66e77affd8bd1fe8c2fcae2922fdaf5aefd Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 11 Jul 2023 11:57:43 -0700 Subject: [PATCH 2/6] Added exercism note about symmetric difference to about. --- concepts/sets/about.md | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 7eb712a90f..5bc473708d 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -422,6 +422,59 @@ The operator version of this method is ` ^ `. { 'πŸ₯‘', '🌴','🌲', '🌡', '🍈', 'πŸ₯­'} ``` +~~~~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: + + +```python +>>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour', + 'flour','lemon','parsley','salt','soy sauce', + 'sunflower oil','water'} + +>>> two = {'black pepper','cornstarch','garlic','ginger', + 'lemon juice','lemon zest','salt','soy sauce','sugar', + 'tofu','vegetable oil','vegetable stock','water'} + +>>> three = {'black pepper','garlic','lemon juice','mixed herbs', + 'nutritional yeast', 'olive oil','salt','silken tofu', + 'smoked tofu','soy sauce','spaghetti','turmeric'} + +>>> four = {'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'} + +>>> intersections = (one & two | one & three | one & four | + two & three | two & four | three & four) +... +{'black pepper','flour','garlic','lemon juice','nutritional yeast', +'olive oil','salt','soy sauce', 'sugar','water'} + +# The ^ operation will include some of the items in intersections, +# which means it is not a "clean" symmetric difference - there +# are overlapping members. +>>> (one ^ two ^ three ^ four) & intersections +{'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. +>>> (one ^ two ^ three ^ four) - intersections +... +{'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 +~~~~ + [hashable]: https://docs.python.org/3.7/glossary.html#term-hashable [mathematical-sets]: https://en.wikipedia.org/wiki/Set_theory#Basic_concepts_and_notation [operator]: https://www.computerhope.com/jargon/o/operator.htm From 118d6dbab3fe4d8ce79c15b48991d89175f44e66 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 11 Jul 2023 22:47:34 -0700 Subject: [PATCH 3/6] Final intro touchups. --- concepts/sets/about.md | 10 ++++++++-- concepts/sets/introduction.md | 5 ++++- exercises/concept/cater-waiter/.docs/introduction.md | 10 ++++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index 5bc473708d..c2c7ead30b 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -2,9 +2,11 @@ A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. Set members must be distinct -- duplicate items are not allowed. -Like most collections, sets can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. +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, lists, tuples_), `sets` support: - Iteration via `for item in ` @@ -18,6 +20,7 @@ Like other collection types (_dictionaries, lists, tuples_), `sets` support: - Slicing - Concatenation via `+` + 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()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). @@ -49,10 +52,12 @@ Set literals use the same curly braces as `dict` literals, which means you need ### 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 added to the `set` individually. +Elements of the `iterable` are cycled through and added to the `set` individually. Element order is not preserved and duplicates are silently omitted: + ```python +# To create an empty set, the constructor must be used. >>> no_elements = set() set() @@ -63,6 +68,7 @@ set() {334782, 'Bird', 'Parrot'} # The list 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]) {2, 3, 5, 7, 11, 13} diff --git a/concepts/sets/introduction.md b/concepts/sets/introduction.md index ee98b39eec..4c264f0903 100644 --- a/concepts/sets/introduction.md +++ b/concepts/sets/introduction.md @@ -2,9 +2,11 @@ A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. Set members must be distinct -- duplicate items are not allowed. -Like most collections, sets can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. +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, lists, tuples_), `sets` support: - Iteration via `for item in ` @@ -18,6 +20,7 @@ Like other collection types (_dictionaries, lists, tuples_), `sets` support: - Slicing - Concatenation via `+` + 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()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 5f0ff904e7..44df1e7045 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -2,9 +2,11 @@ A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects. Set members must be distinct -- duplicate items are not allowed. -Like most collections, sets can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. +They can hold multiple different data types and even nested structures like a `tuple` of `tuples` -- as long as all elements can be _hashed_. Sets also come in an immutable [`frozensets`][type-frozenset] flavor. +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, lists, tuples_), `sets` support: - Iteration via `for item in ` @@ -18,6 +20,7 @@ Like other collection types (_dictionaries, lists, tuples_), `sets` support: - Slicing - Concatenation via `+` + 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()`, `.intersection()`, or `.difference()` also have constant time complexity (on average). @@ -42,13 +45,15 @@ Duplicates are silently omitted: Set literals use the same curly braces as `dict` literals, which means you need to 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 added to the `set` individually. +Elements of the `iterable` are cycled through and added to the `set` individually. Element order is not preserved and duplicates are silently omitted: ```python +# To create an empty set, the constructor must be used. >>> no_elements = set() set() @@ -59,6 +64,7 @@ set() {334782, 'Bird', 'Parrot'} # The list 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]) {2, 3, 5, 7, 11, 13} From 025d900daebf527de344a4cd9fbdbe5032e57a73 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Tue, 11 Jul 2023 22:59:07 -0700 Subject: [PATCH 4/6] Further touchups to terminal examples. Removed iPython refs. --- concepts/sets/about.md | 26 +++++++------------ .../cater-waiter/.docs/introduction.md | 16 +++++------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index c2c7ead30b..d35d12d026 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -113,21 +113,17 @@ Remember: sets can hold different datatypes and _nested_ datatypes, but all `set ['😜', 'πŸ€ͺ', '😝']} Traceback (most recent call last): - - File "", line 1, in - lists_as_elements = {['πŸ˜…','🀣'], ['πŸ˜‚','πŸ™‚','πŸ™ƒ'], ['😜', 'πŸ€ͺ', '😝']} - + File "", line 1, in TypeError: unhashable type: 'list' + # Standard sets are mutable, so they cannot be hashed. >>> sets_as_elements = {{'πŸ˜…','🀣'}, {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, {'😜', 'πŸ€ͺ', '😝'}} -Traceback (most recent call last): - - File "", line 1, in - sets_as_elements = {{'πŸ˜…','🀣'}, {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, {'😜', 'πŸ€ͺ', '😝'}} +Traceback (most recent call last): + File "", line 1, in TypeError: unhashable type: 'set' ``` @@ -161,11 +157,8 @@ The `.remove()` method will raise a `KeyError` if the item is not present # Trying to remove an item that is not present raises a KeyError >>> creatures.remove('bear') Traceback (most recent call last): - - File "", line 1, in - creatures.remove('bear') - -KeyError: 'bear' + File "", line 1, in + KeyError: 'bear' ``` ### Additional Strategies for Removing Set Members @@ -228,7 +221,7 @@ False ### Checking for Subsets and Supersets -- `.issubset()` is used to check if every element in `` is also in ``. +`.issubset()` is used to check if every element in `` is also in ``. The operator form is ` <= `: ```python @@ -252,10 +245,11 @@ False True ``` - `.issuperset()` is the inverse of `.issubset()`. - It is used to check if every element in `` is also in ``. +`.issuperset()` is the inverse of `.issubset()`. +It is used to check if every element in `` is also in ``. The operator form is ` >= `: + ```python # All members of mammals also appear in animals. # **Note** The first object needs to be a set or converted to a set diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md index 44df1e7045..235ae86937 100644 --- a/exercises/concept/cater-waiter/.docs/introduction.md +++ b/exercises/concept/cater-waiter/.docs/introduction.md @@ -95,21 +95,17 @@ Sets can hold different datatypes and _nested_ datatypes, but all `set` elements ['😜', 'πŸ€ͺ', '😝']} Traceback (most recent call last): - - File "", line 1, in - lists_as_elements = {['πŸ˜…','🀣'], ['πŸ˜‚','πŸ™‚','πŸ™ƒ'], ['😜', 'πŸ€ͺ', '😝']} - + File "", line 1, in TypeError: unhashable type: 'list' + # Standard sets are mutable, so they cannot be hashed. >>> sets_as_elements = {{'πŸ˜…','🀣'}, {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, {'😜', 'πŸ€ͺ', '😝'}} -Traceback (most recent call last): - - File "", line 1, in - sets_as_elements = {{'πŸ˜…','🀣'}, {'πŸ˜‚','πŸ™‚','πŸ™ƒ'}, {'😜', 'πŸ€ͺ', '😝'}} +Traceback (most recent call last): + File "", line 1, in TypeError: unhashable type: 'set' ``` @@ -195,7 +191,7 @@ False True ``` - `.issuperset()` is the inverse of `.issubset()`. +`.issuperset()` is the inverse of `.issubset()`. It is used to check if every element in `` is also in ``. The operator form is ` >= `: @@ -224,7 +220,7 @@ True ### Set Intersections `.intersection(*)` returns a new `set` with elements common to the original `set` and all `` (_in other words, the `set` where everything [intersects][intersection]_). -The operator version of this method is ` & & & ... `: +The operator version of this method is ` & & & ... `: ```python From 327372197601f5108695ae1c9d8770637d20a8bb Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 30 Sep 2023 18:09:14 -0700 Subject: [PATCH 5/6] Update concepts/sets/about.md --- concepts/sets/about.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/sets/about.md b/concepts/sets/about.md index d35d12d026..0b8fc842f1 100644 --- a/concepts/sets/about.md +++ b/concepts/sets/about.md @@ -398,7 +398,7 @@ The operator version of this method is ` & & & . {'Basil', 'Chervil', 'Marjoram', 'Cilantro'} ``` -# Set Symmetric Difference +### Set Symmetric Differences `.symmetric_difference()` returns a new `set` that contains elements that are in `` OR ``, but **not in both**. The operator version of this method is ` ^ `. From 5ca2a8f669b7b8ece2d957077f3331be1c718bee Mon Sep 17 00:00:00 2001 From: BethanyG Date: Sat, 7 Oct 2023 15:24:33 -0700 Subject: [PATCH 6/6] Slight wording change --- exercises/concept/cater-waiter/sets_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exercises/concept/cater-waiter/sets_test.py b/exercises/concept/cater-waiter/sets_test.py index de35ecad1d..ec93507ae6 100644 --- a/exercises/concept/cater-waiter/sets_test.py +++ b/exercises/concept/cater-waiter/sets_test.py @@ -48,8 +48,8 @@ def test_clean_ingredients(self): with self.subTest(f"variation #{variant}", inputs="recipes with duplicated ingredients", result="recipe ingredients de-duped"): - error_msg = (f"Expected a cleaned ingredient list for {item[0]}, " - "but the ingredients aren't cleaned as expected.") + error_msg = (f"Expected the ingredient list for {item[0]} to be de-duplicated, " + "but the ingredients were not cleaned as expected.") self.assertEqual(clean_ingredients(item[0], item[1]), (result[1], result[2]), msg=error_msg)