diff --git a/tisbury-treasure-hunt/.exercism/config.json b/tisbury-treasure-hunt/.exercism/config.json new file mode 100644 index 0000000..6dba773 --- /dev/null +++ b/tisbury-treasure-hunt/.exercism/config.json @@ -0,0 +1,18 @@ +{ + "authors": [ + "BethanyG" + ], + "files": { + "solution": [ + "tuples.py" + ], + "test": [ + "tuples_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "tisbury-treasure-hunt", + "blurb": "Learn about tuples by helping out competitors in the Tisbury Treasure Hunt." +} diff --git a/tisbury-treasure-hunt/.exercism/metadata.json b/tisbury-treasure-hunt/.exercism/metadata.json new file mode 100644 index 0000000..059fc79 --- /dev/null +++ b/tisbury-treasure-hunt/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"tisbury-treasure-hunt","id":"d0695c14209341b39ef09f36acc77dd7","url":"https://exercism.org/tracks/python/exercises/tisbury-treasure-hunt","handle":"myFirstCode","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/tisbury-treasure-hunt/HELP.md b/tisbury-treasure-hunt/HELP.md new file mode 100644 index 0000000..25eaac5 --- /dev/null +++ b/tisbury-treasure-hunt/HELP.md @@ -0,0 +1,130 @@ +# Help + +## Running the tests + +We use [pytest][pytest: Getting Started Guide] as our website test runner. +You will need to install `pytest` on your development machine if you want to run tests for the Python track locally. +You should also install the following `pytest` plugins: + +- [pytest-cache][pytest-cache] +- [pytest-subtests][pytest-subtests] + +Extended information can be found in our website [Python testing guide][Python track tests page]. + + +### Running Tests + +To run the included tests, navigate to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with your path_). +Test files usually end in `_test.py`, and are the same tests that run on the website when a solution is uploaded. + +Linux/MacOS +```bash +$ cd {path/to/exercise-folder-location} +``` + +Windows +```powershell +PS C:\Users\foobar> cd {path\to\exercise-folder-location} +``` + +
+ +Next, run the `pytest` command in your terminal, replacing `{exercise_test.py}` with the name of the test file: + +Linux/MacOS +```bash +$ python3 -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + +Windows +```powershell +PS C:\Users\foobar> py -m pytest -o markers=task {exercise_test.py} +==================== 7 passed in 0.08s ==================== +``` + + +### Common options +- `-o` : override default `pytest.ini` (_you can use this to avoid marker warnings_) +- `-v` : enable verbose output. +- `-x` : stop running tests on first failure. +- `--ff` : run failures from previous test before running other test cases. + +For additional options, use `python3 -m pytest -h` or `py -m pytest -h`. + + +### Fixing warnings + +If you do not use `pytest -o markers=task` when invoking `pytest`, you might receive a `PytestUnknownMarkWarning` for tests that use our new syntax: + +```bash +PytestUnknownMarkWarning: Unknown pytest.mark.task - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/mark.html +``` + +To avoid typing `pytest -o markers=task` for every test you run, you can use a `pytest.ini` configuration file. +We have made one that can be downloaded from the top level of the Python track directory: [pytest.ini][pytest.ini]. + +You can also create your own `pytest.ini` file with the following content: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Placing the `pytest.ini` file in the _root_ or _working_ directory for your Python track exercises will register the marks and stop the warnings. +More information on pytest marks can be found in the `pytest` documentation on [marking test functions][pytest: marking test functions with attributes] and the `pytest` documentation on [working with custom markers][pytest: working with custom markers]. + +Information on customizing pytest configurations can be found in the `pytest` documentation on [configuration file formats][pytest: configuration file formats]. + + +### Extending your IDE or Code Editor + +Many IDEs and code editors have built-in support for using `pytest` and other code quality tools. +Some community-sourced options can be found on our [Python track tools page][Python track tools page]. + +[Pytest: Getting Started Guide]: https://docs.pytest.org/en/latest/getting-started.html +[Python track tools page]: https://exercism.org/docs/tracks/python/tools +[Python track tests page]: https://exercism.org/docs/tracks/python/tests +[pytest-cache]:http://pythonhosted.org/pytest-cache/ +[pytest-subtests]:https://github.com/pytest-dev/pytest-subtests +[pytest.ini]: https://github.com/exercism/python/blob/main/pytest.ini +[pytest: configuration file formats]: https://docs.pytest.org/en/6.2.x/customize.html#configuration-file-formats +[pytest: marking test functions with attributes]: https://docs.pytest.org/en/6.2.x/mark.html#raising-errors-on-unknown-marks +[pytest: working with custom markers]: https://docs.pytest.org/en/6.2.x/example/markers.html#working-with-custom-markers + +## Submitting your solution + +You can submit your solution using the `exercism submit tuples.py` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Python track's documentation](https://exercism.org/docs/tracks/python) +- The [Python track's programming category on the forum](https://forum.exercism.org/c/programming/python) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Below are some resources for getting help if you run into trouble: + +- [The PSF](https://www.python.org) hosts Python downloads, documentation, and community resources. +- [The Exercism Community on Discord](https://exercism.org/r/discord) +- [Python Community on Discord](https://pythondiscord.com/) is a very helpful and active community. +- [/r/learnpython/](https://www.reddit.com/r/learnpython/) is a subreddit designed for Python learners. +- [#python on Libera.chat](https://www.python.org/community/irc/) this is where the core developers for the language hang out and get work done. +- [Python Community Forums](https://discuss.python.org/) +- [Free Code Camp Community Forums](https://forum.freecodecamp.org/) +- [CodeNewbie Community Help Tag](https://community.codenewbie.org/t/help) +- [Pythontutor](http://pythontutor.com/) for stepping through small code snippets visually. + +Additionally, [StackOverflow](http://stackoverflow.com/questions/tagged/python) is a good spot to search for your problem/question to see if it has been answered already. + If not - you can always [ask](https://stackoverflow.com/help/how-to-ask) or [answer](https://stackoverflow.com/help/how-to-answer) someone else's question. \ No newline at end of file diff --git a/tisbury-treasure-hunt/HINTS.md b/tisbury-treasure-hunt/HINTS.md new file mode 100644 index 0000000..ff5aba5 --- /dev/null +++ b/tisbury-treasure-hunt/HINTS.md @@ -0,0 +1,49 @@ +# Hints + +## General + + +- [Tuples][tuples] are immutable [sequence Types][sequence types] that can contain any data type. +- Tuples are [iterable][iterable]. If you need indexes as well as values, use [`enumerate()`][enumerate] +- Elements within tuples can be accessed via [bracket notation][bracket notation], using a zero-based index from the left, or -1 from the right. Other [Common Sequence Operations][common sequence operations] can also be used when working with tuples. + +## 1. Extract coordinates + +- Remember: tuples allow access via _index_, using _brackets_. Indexes start from the left at zero. + +## 2. Format coordinates + +- Check [`class tuple`][class tuple] for more details on tuples. +- Check [`class str`][class str] for more details on strings. + +## 3. Match coordinates + +- What methods could be used here for for [testing membership][testing membership]?. +- Check [`class tuple`][class tuple] for more details on tuples. +- Could you re-use your `convert_coordinate()` function? + +## 4. Combine matched records + +- Remember that tuples support all [common sequence operations][common sequence operations]. +- Could you re-use your `compare_records()` function here? + +## 5. "Clean up" & make a report of all records + +- Remember: tuples are _immutable_, but the contents can be accessed via _index_ using _bracket notation_. +- Tuples don't have to use parentheses unless there is _ambiguity_. +- Python has multiple methods of string formatting. [`str.format()`][str.format] and [`f-strings`][f-strings] are two very common ones. +- There are multiple textual formatting options available via Pythons [`format specification mini-language`][format specification mini-language]. + + +[bracket notation]: https://stackoverflow.com/questions/30250282/whats-the-difference-between-the-square-bracket-and-dot-notations-in-python +[class str]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str +[class tuple]: https://docs.python.org/3/library/stdtypes.html#tuple +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[enumerate]: https://docs.python.org/3/library/functions.html#enumerate +[f-strings]: https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals +[format specification mini-language]: https://docs.python.org/3/library/string.html#format-specification-mini-language +[iterable]: https://docs.python.org/3/glossary.html#term-iterable +[sequence types]: https://docs.python.org/3/library/stdtypes.html#typesseq +[str.format]: https://docs.python.org/3/library/stdtypes.html#str.format +[testing membership]: https://docs.python.org/3/reference/expressions.html#membership-test-operations +[tuples]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences \ No newline at end of file diff --git a/tisbury-treasure-hunt/README.md b/tisbury-treasure-hunt/README.md new file mode 100644 index 0000000..694172b --- /dev/null +++ b/tisbury-treasure-hunt/README.md @@ -0,0 +1,279 @@ +# Tisbury Treasure Hunt + +Welcome to Tisbury Treasure Hunt on Exercism's Python Track. +If you need help running the tests or submitting your code, check out `HELP.md`. +If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :) + +## Introduction + +In Python, a [tuple][tuple] is an _immutable_ collection of items in _sequence_. +Like most collections, `tuples` can hold any (or multiple) data type(s) -- including other `tuples`. +Tuples support all [common sequence operations][common sequence operations], but **do not** support [mutable sequence operations][mutable sequence operations]. + +The elements of a tuple can be iterated over using the `for item in ` construct. +If both element index and value are needed, `for index, item in enumerate()` can be used. +Like any sequence, elements within `tuples` can be accessed via _bracket notation_ using a `0-based index` number from the left or a `-1-based index` number from the right. +Tuples can also be copied in whole or in part using slice notation (_`[::]`_). + + +## Tuple Construction + +Tuples can be formed in multiple ways, using either the `tuple()` class constructor or the `tuple` literal declaration. + +### Using the `tuple()` constructor empty or with an _iterable_: + +```python +>>> no_elements = tuple() +() + +# The constructor *requires* an iterable, so single elements must be passed in a list or another tuple. +>>> one_element = tuple([16]) +(16,) +``` + +Strings are iterable, so using a single `str` as an argument to the `tuple()` constructor can have surprising results: + +```python +# String elements (characters) are iterated through and added to the tuple +>>> multiple_elements_string = tuple("Timbuktu") +('T', 'i', 'm', 'b', 'u', 'k', 't', 'u') +``` + +Single iterables have their elements added one by one: + +```python +>>> multiple_elements_list = tuple(["Parrot", "Bird", 334782]) +("Parrot", "Bird", 334782) + +>>> multiple_elements_set = tuple({2, 3, 5, 7, 11}) +(2,3,5,7,11) +``` + +#### Declaring a tuple as a _literal_ : + +Because the `tuple()` constructor only takes _iterables_ (or nothing) as arguments, it is much easier to create + a one-tuple via the literal method. + +```python +>>> no_elements = () +() + +>>> one_element = ("Guava",) +("Guava",) +``` + +Nested data structures can be included as `tuple` elements, including other `tuples`: + +```python +>>> nested_data_structures = ({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) +({"fish": "gold", "monkey": "brown", "parrot" : "grey"}, ("fish", "mammal", "bird")) + +>>> nested_data_structures_1 = (["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) +(["fish", "gold", "monkey", "brown", "parrot", "grey"], ("fish", "mammal", "bird")) +``` + +## Tuple Concatenation + +Tuples can be concatenated using plus `+` operator, which unpacks each `tuple` creating a new, combined `tuple`. + +```python +>>> new_via_concatenate = ("George", 5) + ("cat", "Tabby") +("George", 5, "cat", "Tabby") + +#likewise, using the multiplication operator * is the equivalent of using + n times +>>> first_group = ("cat", "dog", "elephant") + +>>> multiplied_group = first_group * 3 +('cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant') +``` + +## Accessing Elements Inside a Tuple + +Elements within a `tuple` can be accessed via _bracket notation_ using a `0-based index` number from the left or a `-1-based index` number from the right. + +```python +student_info = ("Alyssa", "grade 3", "female", 8 ) + +#gender is at index 2 or index -2 +>>> student_gender = student_info[2] +'female' + +>>> student_gender = student_info[-2] +'female' + +#name is at index 0 or index -4 +>>> student_name = student_info[0] +Alyssa + +>>> student_name = student_info[-4] +Alyssa +``` + +## Iterating Over a Tuples Elements + +Elements inside a `tuple` can be _iterated over_ in a loop using `for item in ` syntax. +If both indexes and values are needed, `for index, item in enumerate()` can be used. + +```python +>>> student_info = ("Alyssa", "grade 3", "female", 8 ) +>>> for item in student_info: +... print(item) + +... +Alyssa +grade 3 +female +8 + +>>> for index, item in enumerate(student_info): +... print("Index is: " + str(index) + ", value is: " + str(item) +".") + +... +Index is: 0, value is: Alyssa. +Index is: 1, value is: grade 3. +Index is: 2, value is: female. +Index is: 3, value is: 8. +``` + +## Checking Membership in a Tuple + +The `in` operator can be used to check membership in a `tuple`. + +```python +>>> multiple_elements_list = tuple(["Parrot", "Bird", 334782]) +("Parrot", "Bird", 334782) + +>>> "Parrot" in multiple_elements_list +True +``` + +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple + +## Instructions + +Azara and Rui are teammates competing in a pirate-themed treasure hunt. +One has a list of treasures with map coordinates, the other a list of location names with map coordinates. +They've also been given blank maps with a starting place marked YOU ARE HERE. + +
+ + + +
Azara's ListRui's List
+ +| Treasure | Coordinates | +| --------------------------- | ----------- | +| Amethyst Octopus | 1F | +| Angry Monkey Figurine | 5B | +| Antique Glass Fishnet Float | 3D | +| Brass Spyglass | 4B | +| Carved Wooden Elephant | 8C | +| Crystal Crab | 6A | +| Glass Starfish | 6D | +| Model Ship in Large Bottle | 8A | +| Pirate Flag | 7F | +| Robot Parrot | 1C | +| Scrimshawed Whale Tooth | 2A | +| Silver Seahorse | 4E | +| Vintage Pirate Hat | 7E | + + + +| Location Name | Coordinates | Quadrant | +| ------------------------------------- | ----------- | --------- | +| Seaside Cottages | ("1", "C") | Blue | +| Aqua Lagoon (Island of Mystery) | ("1", "F") | Yellow | +| Deserted Docks | ("2", "A") | Blue | +| Spiky Rocks | ("3", "D") | Yellow | +| Abandoned Lighthouse | ("4", "B") | Blue | +| Hidden Spring (Island of Mystery) | ("4", "E") | Yellow | +| Stormy Breakwater | ("5", "B") | Purple | +| Old Schooner | ("6", "A") | Purple | +| Tangled Seaweed Patch | ("6", "D") | Orange | +| Quiet Inlet (Island of Mystery) | ("7", "E") | Orange | +| Windswept Hilltop (Island of Mystery) | ("7", "F") | Orange | +| Harbor Managers Office | ("8", "A") | Purple | +| Foggy Seacave | ("8", "C") | Purple | + +
+ +
+ +But things are a bit disorganized: Azara's coordinates appear to be formatted and sorted differently from Rui's, and they have to keep looking from one list to the other to figure out which treasures go with which locations. +Being budding pythonistas, they have come to you for help in writing a small program (a set of functions, really) to better organize their hunt information. + + +## 1. Extract coordinates + +Implement the `get_coordinate()` function that takes a `(treasure, coordinate)` pair from Azara's list and returns only the extracted map coordinate. + + +```python +>>> get_coordinate(('Scrimshawed Whale Tooth', '2A')) +2A +``` + +## 2. Format coordinates + +Implement the `convert_coordinate()` function that takes a coordinate in the format "2A" and returns a tuple in the format `("2", "A")`. + + +```python +>>> convert_coordinate("2A") +("2", "A") +``` + +## 3. Match coordinates + +Implement the `compare_records()` function that takes a `(treasure, coordinate)` pair and a `(location, coordinate, quadrant)` record and compares coordinates from each. +Return **`True`** if the coordinates "match", and return **`False`** if they do not. +Re-format coordinates as needed for accurate comparison. + + +```python +>>> compare_records(('Brass Spyglass', '4B'), ('Seaside Cottages', ('1', 'C'), 'blue')) +False + +>>> compare_records(('Model Ship in Large Bottle', '8A'), ('Harbor Managers Office', ('8', 'A'), 'purple')) +True +``` + +## 4. Combine matched records + +Implement the `create_record()` function that takes a `(treasure, coordinate)` pair from Azara's list and a `(location, coordinate, quadrant)` record from Rui's list and returns `(treasure, coordinate, location, coordinate, quadrant)` **if the coordinates match**. +If the coordinates _do not_ match, return the string **"not a match"**. +Re-format the coordinate as needed for accurate comparison. + + +```python +>>> create_record(('Brass Spyglass', '4B'), ('Abandoned Lighthouse', ('4', 'B'), 'Blue')) +('Brass Spyglass', '4B', 'Abandoned Lighthouse', ('4', 'B'), 'Blue') + +>>> create_record(('Brass Spyglass', '4B'), ('Seaside Cottages', ('1', 'C'), 'blue')) +"not a match" +``` + +## 5. "Clean up" & make a report of all records + +Clean up the combined records from Azara and Rui so that there's only one set of coordinates per record. Make a report so they can see one list of everything they need to put on their maps. +Implement the `clean_up()` function that takes a tuple of tuples (_everything from both lists_), looping through the _outer_ tuple, dropping the unwanted coordinates from each _inner_ tuple and adding each to a 'report'. +Format and return the 'report' so that there is one cleaned record on each line. + + +```python +>>> clean_up((('Brass Spyglass', '4B', 'Abandoned Lighthouse', ('4', 'B'), 'Blue'), ('Vintage Pirate Hat', '7E', 'Quiet Inlet (Island of Mystery)', ('7', 'E'), 'Orange'), ('Crystal Crab', '6A', 'Old Schooner', ('6', 'A'), 'Purple'))) + +""" +('Brass Spyglass', 'Abandoned Lighthouse', ('4', 'B'), 'Blue')\n +('Vintage Pirate Hat', 'Quiet Inlet (Island of Mystery)', ('7', 'E'), 'Orange')\n +('Crystal Crab', 'Old Schooner', ('6', 'A'), 'Purple')\n +""" +``` + +## Source + +### Created by + +- @BethanyG \ No newline at end of file diff --git a/tisbury-treasure-hunt/tuples.py b/tisbury-treasure-hunt/tuples.py new file mode 100644 index 0000000..0da64ce --- /dev/null +++ b/tisbury-treasure-hunt/tuples.py @@ -0,0 +1,71 @@ +"""Functions to help Azara and Rui locate pirate treasure.""" + + +def get_coordinate(record: tuple) -> str: + """ + Return coordinate value from a tuple containing the treasure name, + and treasure coordinate. + + :param record: tuple - with a (treasure, coordinate) pair. + :return: str - the extracted map coordinate. + """ + return record[-1] + + +def convert_coordinate(coordinate: str) -> tuple: + """ + Split the given coordinate into tuple containing its individual + components. + + :param coordinate: str - a string map coordinate + :return: tuple - the string coordinate split into its individual + components. + """ + return tuple(char for char in coordinate) + + +def compare_records(azara_record: tuple, rui_record: tuple) -> bool: + """ + Compare two record types and determine if their coordinates match. + + :param azara_record: tuple - a (treasure, coordinate) pair. + :param rui_record: tuple - a + (location, tuple(coordinate_1, coordinate_2), + quadrant) + trio. + :return: bool - do the coordinates match? + """ + return azara_record[-1] == "".join(rui_record[1]) + + +def create_record(azara_record: tuple, rui_record: tuple) -> (tuple, str): + """ + Combine the two record types (if possible) and create a combined record + group. + + :param azara_record: tuple - a (treasure, coordinate) pair. + :param rui_record: tuple - a (location, coordinate, quadrant) trio. + :return: tuple or str - the combined record (if compatible), or the string + "not a match" (if incompatible). + """ + if compare_records(azara_record, rui_record): + return azara_record + rui_record + return "not a match" + + +def clean_up(combined_record_group: tuple) -> str: + """ + Clean up a combined record group into a multi-line string of single + records. + + :param combined_record_group: tuple - everything from both participants. + :return: str - everything "cleaned", excess coordinates and information + are removed. + + The return statement should be a multi-lined string with items separated + by newlines. (see HINTS.md for an example). + """ + result: str = "" + for line in combined_record_group: + result += f"{tuple(item for i, item in enumerate(line) if i != 1)}\n" + return result diff --git a/tisbury-treasure-hunt/tuples_test.py b/tisbury-treasure-hunt/tuples_test.py new file mode 100644 index 0000000..a3598dd --- /dev/null +++ b/tisbury-treasure-hunt/tuples_test.py @@ -0,0 +1,162 @@ +# pylint: disable=C0301, C0114, C0115, C0116, R0904 + +import unittest +import pytest +from tuples import (get_coordinate, + convert_coordinate, + compare_records, + create_record, + clean_up) + + +class TisburyTreasureTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_get_coordinate(self): + input_data = [('Scrimshawed Whale Tooth', '2A'), + ('Brass Spyglass', '4B'), + ('Robot Parrot', '1C'), + ('Glass Starfish', '6D'), + ('Vintage Pirate Hat', '7E'), + ('Pirate Flag', '7F'), + ('Crystal Crab', '6A'), + ('Model Ship in Large Bottle', '8A'), + ('Angry Monkey Figurine', '5B'), + ('Carved Wooden Elephant', '8C'), + ('Amethyst Octopus', '1F'), + ('Antique Glass Fishnet Float', '3D'), + ('Silver Seahorse', '4E')] + + result_data = ['2A', '4B', '1C', '6D', '7E', '7F', '6A', '8A', '5B', '8C', '1F', '3D', '4E'] + + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = get_coordinate(item) + error_message = (f'Called get_coordinate({item}). ' + f'The function returned "{actual_result}", but ' + f'the tests expected "{expected}" as the coordinates.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_convert_coordinate(self): + input_data = ['2A', '4B', '1C', '6D', '7E', '7F', + '6A', '8A', '5B', '8C', '1F', '3D', '4E'] + result_data = [('2', 'A'), + ('4', 'B'), + ('1', 'C'), + ('6', 'D'), + ('7', 'E'), + ('7', 'F'), + ('6', 'A'), + ('8', 'A'), + ('5', 'B'), + ('8', 'C'), + ('1', 'F'), + ('3', 'D'), + ('4', 'E')] + + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = convert_coordinate(item) + error_message = (f'Called convert_coordinate({item}). ' + f'The function returned {actual_result}, but the ' + f'tests expected {expected} as the converted coordinate.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_compare_records(self): + input_data = [ + (('Scrimshawed Whale Tooth', '2A'), ('Deserted Docks', ('2', 'A'), 'Blue')), + (('Brass Spyglass', '4B'), ('Abandoned Lighthouse', ('4', 'B'), 'Blue')), + (('Robot Parrot', '1C'), ('Seaside Cottages', ('1', 'C'), 'Blue')), + (('Glass Starfish', '6D'), ('Tangled Seaweed Patch', ('6', 'D'), 'Orange')), + (('Vintage Pirate Hat', '7E'), ('Quiet Inlet (Island of Mystery)', ('7', 'E'), 'Orange')), + (('Amethyst Octopus', '1F'), ('Seaside Cottages', ('1', 'C'), 'Blue')), + (('Angry Monkey Figurine', '5B'), ('Aqua Lagoon (Island of Mystery)', ('1', 'F'), 'Yellow')), + (('Antique Glass Fishnet Float', '3D'), ('Deserted Docks', ('2', 'A'), 'Blue')), + (('Brass Spyglass', '4B'), ('Spiky Rocks', ('3', 'D'), 'Yellow')), + (('Carved Wooden Elephant', '8C'), ('Abandoned Lighthouse', ('4', 'B'), 'Blue')) + ] + result_data = [True, True, True, True, True, False, False, False, False, False] + + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = compare_records(item[0], item[1]) + error_message = (f'Called compare_records({item[0]}, {item[1]}). ' + f'The function returned {actual_result}, but the ' + f'tests expected {expected}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_create_record(self): + input_data = [ + (('Angry Monkey Figurine', '5B'), ('Stormy Breakwater', ('5', 'B'), 'Purple')), + (('Carved Wooden Elephant', '8C'), ('Foggy Seacave', ('8', 'C'), 'Purple')), + (('Amethyst Octopus', '1F'), ('Aqua Lagoon (Island of Mystery)', ('1', 'F'), 'Yellow')), + (('Antique Glass Fishnet Float', '3D'), ('Spiky Rocks', ('3', 'D'), 'Yellow')), + (('Silver Seahorse', '4E'), ('Hidden Spring (Island of Mystery)', ('4', 'E'), 'Yellow')), + (('Amethyst Octopus', '1F'), ('Seaside Cottages', ('1', 'C'), 'Blue')), + (('Angry Monkey Figurine', '5B'), ('Aqua Lagoon (Island of Mystery)', ('1', 'F'), 'Yellow')), + (('Antique Glass Fishnet Float', '3D'), ('Deserted Docks', ('2', 'A'), 'Blue')), + (('Brass Spyglass', '4B'), ('Spiky Rocks', ('3', 'D'), 'Yellow')), + (('Carved Wooden Elephant', '8C'), ('Abandoned Lighthouse', ('4', 'B'), 'Blue')) + ] + result_data = [ + ('Angry Monkey Figurine', '5B', 'Stormy Breakwater', ('5', 'B'), 'Purple'), + ('Carved Wooden Elephant', '8C', 'Foggy Seacave', ('8', 'C'), 'Purple'), + ('Amethyst Octopus', '1F', 'Aqua Lagoon (Island of Mystery)', ('1', 'F'), 'Yellow'), + ('Antique Glass Fishnet Float', '3D', 'Spiky Rocks', ('3', 'D'), 'Yellow'), + ('Silver Seahorse', '4E', 'Hidden Spring (Island of Mystery)', ('4', 'E'), 'Yellow'), + 'not a match', + 'not a match', + 'not a match', + 'not a match', + 'not a match' + ] + + for variant, (item, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', item=item, expected=expected): + actual_result = create_record(item[0], item[1]) + error_message = (f'Called create_record({item[0]},{item[1]}). ' + f'The function returned ' + f'\n{actual_result}, but the tests expected ' + f'\n{expected} for the record.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=5) + def test_clean_up(self): + input_data = ( + ('Scrimshawed Whale Tooth', '2A', 'Deserted Docks', ('2', 'A'), 'Blue'), + ('Brass Spyglass', '4B', 'Abandoned Lighthouse', ('4', 'B'), 'Blue'), + ('Robot Parrot', '1C', 'Seaside Cottages', ('1', 'C'), 'Blue'), + ('Glass Starfish', '6D', 'Tangled Seaweed Patch', ('6', 'D'), 'Orange'), + ('Vintage Pirate Hat', '7E', 'Quiet Inlet (Island of Mystery)', ('7', 'E'), 'Orange'), + ('Pirate Flag', '7F', 'Windswept Hilltop (Island of Mystery)', ('7', 'F'), 'Orange'), + ('Crystal Crab', '6A', 'Old Schooner', ('6', 'A'), 'Purple'), + ('Model Ship in Large Bottle', '8A', 'Harbor Managers Office', ('8', 'A'), 'Purple'), + ('Angry Monkey Figurine', '5B', 'Stormy Breakwater', ('5', 'B'), 'Purple'), + ('Carved Wooden Elephant', '8C', 'Foggy Seacave', ('8', 'C'), 'Purple'), + ('Amethyst Octopus', '1F', 'Aqua Lagoon (Island of Mystery)', ('1', 'F'), 'Yellow'), + ('Antique Glass Fishnet Float', '3D', 'Spiky Rocks', ('3', 'D'), 'Yellow'), + ('Silver Seahorse', '4E', 'Hidden Spring (Island of Mystery)', ('4', 'E'), 'Yellow') + ) + + result_data = """('Scrimshawed Whale Tooth', 'Deserted Docks', ('2', 'A'), 'Blue')\n\ +('Brass Spyglass', 'Abandoned Lighthouse', ('4', 'B'), 'Blue')\n\ +('Robot Parrot', 'Seaside Cottages', ('1', 'C'), 'Blue')\n\ +('Glass Starfish', 'Tangled Seaweed Patch', ('6', 'D'), 'Orange')\n\ +('Vintage Pirate Hat', 'Quiet Inlet (Island of Mystery)', ('7', 'E'), 'Orange')\n\ +('Pirate Flag', 'Windswept Hilltop (Island of Mystery)', ('7', 'F'), 'Orange')\n\ +('Crystal Crab', 'Old Schooner', ('6', 'A'), 'Purple')\n\ +('Model Ship in Large Bottle', 'Harbor Managers Office', ('8', 'A'), 'Purple')\n\ +('Angry Monkey Figurine', 'Stormy Breakwater', ('5', 'B'), 'Purple')\n\ +('Carved Wooden Elephant', 'Foggy Seacave', ('8', 'C'), 'Purple')\n\ +('Amethyst Octopus', 'Aqua Lagoon (Island of Mystery)', ('1', 'F'), 'Yellow')\n\ +('Antique Glass Fishnet Float', 'Spiky Rocks', ('3', 'D'), 'Yellow')\n\ +('Silver Seahorse', 'Hidden Spring (Island of Mystery)', ('4', 'E'), 'Yellow')\n""" + + self.assertEqual(second=clean_up(input_data), first=result_data)