diff --git a/python/card-games/.exercism/config.json b/python/card-games/.exercism/config.json new file mode 100644 index 0000000..f7d39ca --- /dev/null +++ b/python/card-games/.exercism/config.json @@ -0,0 +1,24 @@ +{ + "authors": [ + "itamargal", + "isaacg", + "bethanyg" + ], + "contributors": [ + "valentin-p", + "pranasziaukas" + ], + "files": { + "solution": [ + "lists.py" + ], + "test": [ + "lists_test.py" + ], + "exemplar": [ + ".meta/exemplar.py" + ] + }, + "icon": "poker", + "blurb": "Learn about lists by tracking hands in card games." +} diff --git a/python/card-games/.exercism/metadata.json b/python/card-games/.exercism/metadata.json new file mode 100644 index 0000000..3e19f7c --- /dev/null +++ b/python/card-games/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"python","exercise":"card-games","id":"47aa83e6f3eb45219f2ef38dc238d4e2","url":"https://exercism.org/tracks/python/exercises/card-games","handle":"Chomp1295","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/python/card-games/HELP.md b/python/card-games/HELP.md new file mode 100644 index 0000000..7ff7fcc --- /dev/null +++ b/python/card-games/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 lists.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/python/card-games/HINTS.md b/python/card-games/HINTS.md new file mode 100644 index 0000000..3c10eca --- /dev/null +++ b/python/card-games/HINTS.md @@ -0,0 +1,47 @@ +# Hints + +## General + +## 1. Tracking Poker Rounds + +- Lists in Python may be [constructed][constructed] in multiple ways. +- This function should [return][return] a `list`. + +## 2. Keeping all Rounds in the Same Place + +- Sequence types such as `list` support [common operations][common sequence operations]. +- This function should [return][return] a `list`. + +## 3. Finding Prior Rounds + +- Sequence types such as `list` support a few [common operations][common sequence operations]. +- This function should [return][return] a `bool`. + +## 4. Averaging Card Values + +- To get the average, this function should count how many items are in the `list` and sum up their values. Then, return the sum divided by the count. + +## 5. Alternate Averages + +- Sequence types such as `list` support a few [common operations][common sequence operations]. +- To access an element, use the square brackets (`[]`) notation. +- Remember that the first element of the `list` is at index 0 from the **left-hand** side. +- In Python, negative indexing starts at -1 from the **right-hand** side. This means that you can find the last element of a `list` by using `[-1]`. +- Think about how you could reuse the code from the functions that you have already implemented. + +## 6. More Averaging Techniques + +- Sequence types such as `list` already support a few [common operations][common sequence operations]. +- Think about reusing the code from the functions that you just implemented. +- The slice syntax supports a _step value_ (`[::]`). + +## 7. Bonus Round Rules + +- Lists are _mutable_. Once a `list` is created, you can modify, delete or add any type of element you wish. +- Python provides a wide range of [ways to modify `lists`][ways to modify `lists`]. + + +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[constructed]: https://docs.python.org/3/library/stdtypes.html#list +[return]: https://www.w3schools.com/python/ref_keyword_return.asp +[ways to modify `lists`]: https://realpython.com/python-lists-tuples/#lists-are-mutable \ No newline at end of file diff --git a/python/card-games/README.md b/python/card-games/README.md new file mode 100644 index 0000000..a8334e3 --- /dev/null +++ b/python/card-games/README.md @@ -0,0 +1,393 @@ +# Card Games + +Welcome to Card Games 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 + +A [`list`][list] is a mutable collection of items in _sequence_. +Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold reference to any (or multiple) data type(s) - including other lists. +Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-based index` from the right. +Lists can be copied in whole or in part via [slice notation][slice notation] or `.copy()`. + +Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`. +List elements can be iterated over using the `for item in ` construct. + `for index, item in enumerate()` can be used when both the element index and the element value are needed. + +Under the hood, `lists` are implemented as [dynamic arrays][dynamic array] -- similar to Java's [`ArrayList`][arraylist] type, and are most often used to store groups of similar data (_strings, numbers, sets etc._) of unknown length. +Lists are an extremely flexible and useful data structure and many built-in methods and operations in Python produce lists as their output. + + +## Construction + +A `list` can be declared as a _literal_ with square `[]` brackets and commas between elements: + + +```python +>>> no_elements = [] + +>>> no_elements +[] + +>>> one_element = ["Guava"] + +>>> one_element +['Guava'] + +>>> elements_separated_with_commas = ["Parrot", "Bird", 334782] + +>>> elements_separated_with_commas +['Parrot', 'Bird', 334782] +``` + +For readability, line breaks can be used when there are many elements or nested data structures within a `list`: + + +```python +>>> lots_of_entries = [ + "Rose", + "Sunflower", + "Poppy", + "Pansy", + "Tulip", + "Fuchsia", + "Cyclamen", + "Lavender" + ] + +>>> lots_of_entries +['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender'] + +# Each data structure is on its own line to help clarify what they are. +>>> nested_data_structures = [ + {"fish": "gold", "monkey": "brown", "parrot": "grey"}, + ("fish", "mammal", "bird"), + ['water', 'jungle', 'sky'] + ] + +>>> nested_data_structures +[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']] +``` + +The `list()` constructor can be used empty or with an _iterable_ as an argument. + Elements in the iterable are cycled through by the constructor and added to the `list` in order: + + +```python +>>> no_elements = list() + +>>> no_elements +[] + +# The tuple is unpacked and each element is added. +>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782)) + +>>> multiple_elements_from_tuple +['Parrot', 'Bird', 334782] + +# The set is unpacked and each element is added. +>>> multiple_elements_from_set = list({2, 3, 5, 7, 11}) + +>>> multiple_elements_from_set +[2, 3, 5, 7, 11] +``` + +Results when using a `list` constructor with a `string` or a `dict` may be surprising: + + +```python +# String elements (Unicode code points) are iterated through and added *individually*. +>>> multiple_elements_string = list("Timbuktu") + +>>> multiple_elements_string +['T', 'i', 'm', 'b', 'u', 'k', 't', 'u'] + +# Unicode separators and positioning code points are also added *individually*. +>>> multiple_code_points_string = list('अभ्यास') + +>>> multiple_code_points_string +['अ', 'भ', '्', 'य', 'ा', 'स'] + +# The iteration default for dictionaries is over the keys, so only key data is inserted into the list. +>>> source_data = {"fish": "gold", "monkey": "brown"} + +>>> multiple_elements_dict_1 = list(source_data) +['fish', 'monkey'] +``` + +Because the `list` constructor will only take _iterables_ (or nothing) as arguments, objects that are _not_ iterable will throw a type error. + Consequently, it is much easier to create a one-item `list` via the literal method. + +```python +# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails. +>>> one_element = list(16) +Traceback (most recent call last): + File "", line 1, in +TypeError: 'int' object is not iterable + +# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward +>>> one_element_from_iterable = list((16,)) + +>>> one_element_from_iterable +[16] +``` + +## Accessing elements + +Items inside lists (_as well as items in other sequence types `str` & `tuple`_) can be accessed via `0-based index` and _bracket notation_. + Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_). + + + + + + +
index from left ⟹






+ +| 0
👇🏾 | 1
👇🏾 | 2
👇🏾 | 3
👇🏾 | 4
👇🏾 | 5
👇🏾 | +|:--------: |:--------: |:--------: |:--------: |:--------: |:--------: | +| P | y | t | h | o | n | +| 👆🏾
-6 | 👆🏾
-5 | 👆🏾
-4 | 👆🏾
-3 | 👆🏾
-2 | 👆🏾
-1 | +





⟸ index from right
+ + +```python +>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"] + +# Oatmeal is at index 0 or index -4. +>>> breakfast_foods[0] +'Oatmeal' + +>>> breakfast_foods[-4] +'Oatmeal' + +# Eggs are at index -2 or 2 +>>> breakfast_foods[-2] +'Eggs' + +>>> breakfast_foods[2] +'Eggs' + +# Toast is at -1 +>>> breakfast_foods[-1] +'Toast' +``` + +A section of the elements inside a `list` can be accessed via _slice notation_ (`[start:stop]`). + A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`. + _Slicing_ returns a copy of the "sliced" items and does not modify the original `list`. + + +A `step` parameter can also be used `[start:stop:step]` to "skip over" or filter the `list` elements (_for example, a `step` of 2 will select every other element in the range_): + + +```python +>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"] + +# If there is no step parameter, the step is assumed to be 1. +>>> middle_colors = colors[2:6] + +>>> middle_colors +['Green', 'Yellow', 'Orange', 'Pink'] + +# If the start or stop parameters are omitted, the slice will +# start at index zero, and will stop at the end of the list. +>>> primary_colors = colors[::3] + +>>> primary_colors +['Red', 'Yellow', 'Blue'] +``` + +## Working with lists + +The usage of the built-in `sum()` function on a list will return the sum of all the numbers in the list: + +```python +>>> number_list = [1, 2, 3, 4] +>>> sum(number_list) +10 +``` + +You can also get the _length_ of a list by using the `len()` function: + +```python +>>> long_list = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] +>>> len(long_list) +10 +``` + +Lists can be also combined in various ways: + +```python +# Using the plus + operator unpacks each list and creates a new list, but it is not efficient. +>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"] + +>>> new_via_concatenate +['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 + +>>> multiplied_group +['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant'] +``` + +Lists supply an _iterator_, and can be looped through/over in the same manner as other _sequence types_. + +```python +# Looping through the list and printing out each element. +>>> colors = ["Orange", "Green", "Grey", "Blue"] + +>>> for item in colors: +... print(item) +... +Orange +Green +Grey +Blue +``` + +_For a more in-depth explanation, of `loops` and `iterators`, complete the `loops` concept._ + +[arraylist]: https://beginnersbook.com/2013/12/java-arraylist/ +[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations +[dict]: https://docs.python.org/3/library/stdtypes.html#dict +[dynamic array]: https://en.wikipedia.org/wiki/Dynamic_array +[list]: https://docs.python.org/3/library/stdtypes.html#list +[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#typesseq-mutable +[sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range +[set]: https://docs.python.org/3/library/stdtypes.html#set +[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings +[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple + +## Instructions + +Elyse is really looking forward to playing some poker (and other card games) during her upcoming trip to Vegas. + Being a big fan of "self-tracking" she wants to put together some small functions that will help her with tracking tasks and has asked for your help thinking them through. + +## 1. Tracking Poker Rounds + +Elyse is especially fond of poker, and wants to track how many rounds she plays - and _which rounds_ those are. + Every round has its own number, and every table shows the round number currently being played. + Elyse chooses a table and sits down to play her first round. She plans on playing three rounds. + +Implement a function `get_rounds()` that takes the current round number and returns a single `list` with that round and the _next two_ that are coming up: + +```python +>>> get_rounds(27) +[27, 28, 29] +``` + +## 2. Keeping all Rounds in the Same Place + +Elyse played a few rounds at the first table, then took a break and played some more rounds at a second table ... but ended up with a different list for each table! + She wants to put the two lists together, so she can track all of the poker rounds in the same place. + +Implement a function `concatenate_rounds(, )` that takes two lists and returns a single `list` consisting of all the rounds in the first `list`, followed by all the rounds in the second `list`: + +```python +>>> concatenate_rounds([27, 28, 29], [35, 36]) +[27, 28, 29, 35, 36] +``` + +## 3. Finding Prior Rounds + +Talking about some of the prior Poker rounds, another player remarks how similarly two of them played out. + Elyse is not sure if she played those rounds or not. + +Implement a function `list_contains_round(, )` that takes two arguments, a list of rounds played and a round number. + The function will return `True` if the round is in the list of rounds played, `False` if not: + +```python +>>> list_contains_round([27, 28, 29, 35, 36], 29) +True + +>>> list_contains_round([27, 28, 29, 35, 36], 30) +False +``` + +## 4. Averaging Card Values + +Elyse wants to try out a new game called Black Joe. + It's similar to Black Jack - where your goal is to have the cards in your hand add up to a target value - but in Black Joe the goal is to get the _average_ of the card values to be 7. + The average can be found by summing up all the card values and then dividing that sum by the number of cards in the hand. + +Implement a function `card_average()` that will return the average value of a hand of Black Joe. + +```python +>>> card_average([5, 6, 7]) +6.0 +``` + +## 5. Alternate Averages + +In Black Joe, speed is important. Elyse is going to try and find a faster way of finding the average. + +She has thought of two ways of getting an _average-like_ number: + +- Take the average of the _first_ and _last_ number in the hand. +- Using the median (middle card) of the hand. + +Implement the function `approx_average_is_average()`, given `hand`, a list containing the values of the cards in your hand. + +Return `True` if either _one_ `or` _both_ of the, above named, strategies result in a number _equal_ to the _actual average_. + +Note: _The length of all hands are odd, to make finding a median easier._ + +```python +>>> approx_average_is_average([1, 2, 3]) +True + +>>> approx_average_is_average([2, 3, 4, 8, 8]) +True + +>>> approx_average_is_average([1, 2, 3, 5, 9]) +False +``` + +## 6. More Averaging Techniques + +Intrigued by the results of her averaging experiment, Elyse is wondering if taking the average of the cards at the _even_ positions versus the average of the cards at the _odd_ positions would give the same results. + Time for another test function! + +Implement a function `average_even_is_average_odd()` that returns a Boolean indicating if the average of the cards at even indexes is the same as the average of the cards at odd indexes. + +```python +>>> average_even_is_average_odd([1, 2, 3]) +True + +>>> average_even_is_average_odd([1, 2, 3, 4]) +False +``` + +## 7. Bonus Round Rules + +Every 11th hand in Black Joe is a bonus hand with a bonus rule: if the last card you draw is a Jack, you double its value. + +Implement a function `maybe_double_last()` that takes a hand and checks if the last card is a Jack (11). + If the last card **is** a Jack (11), double its value before returning the hand. + +```python +>>> hand = [5, 9, 11] +>>> maybe_double_last(hand) +[5, 9, 22] + +>>> hand = [5, 9, 10] +>>> maybe_double_last(hand) +[5, 9, 10] +``` + +## Source + +### Created by + +- @itamargal +- @isaacg +- @bethanyg + +### Contributed to by + +- @valentin-p +- @pranasziaukas \ No newline at end of file diff --git a/python/card-games/lists.py b/python/card-games/lists.py new file mode 100644 index 0000000..a2359f1 --- /dev/null +++ b/python/card-games/lists.py @@ -0,0 +1,81 @@ +"""Functions for tracking poker hands and assorted card tasks. + +Python list documentation: https://docs.python.org/3/tutorial/datastructures.html +""" + + +def get_rounds(number): + """Create a list containing the current and next two round numbers. + + :param number: int - current round number. + :return: list - current round and the two that follow. + """ + + return [number, number + 1, number + 2] + + +def concatenate_rounds(rounds_1, rounds_2): + """Concatenate two lists of round numbers. + + :param rounds_1: list - first rounds played. + :param rounds_2: list - second set of rounds played. + :return: list - all rounds played. + """ + + return rounds_1 + rounds_2 + + +def list_contains_round(rounds, number): + """Check if the list of rounds contains the specified number. + + :param rounds: list - rounds played. + :param number: int - round number. + :return: bool - was the round played? + """ + return number in rounds + + +def card_average(hand): + """Calculate and returns the average card value from the list. + + :param hand: list - cards in hand. + :return: float - average value of the cards in the hand. + """ + return sum(hand) / len(hand) + + +def approx_average_is_average(hand): + """Return if the (average of first and last card values) OR ('middle' card) == calculated average. + + :param hand: list - cards in hand. + :return: bool - does one of the approximate averages equal the `true average`? + """ + + size = len(hand) + actual_avg = card_average(hand) + first_last = (hand[0] + hand[size - 1]) / 2 + median = hand[size // 2] + + return actual_avg in (first_last, median) + + +def average_even_is_average_odd(hand): + """Return if the (average of even indexed card values) == (average of odd indexed card values). + + :param hand: list - cards in hand. + :return: bool - are even and odd averages equal? + """ + return card_average(hand[::2]) == card_average(hand[1::2]) + + +def maybe_double_last(hand): + """Multiply a Jack card value in the last index position by 2. + + :param hand: list - cards in hand. + :return: list - hand with Jacks (if present) value doubled. + """ + + if hand[-1] == 11: + hand[-1] *= 2 + + return hand diff --git a/python/card-games/lists_test.py b/python/card-games/lists_test.py new file mode 100644 index 0000000..e550112 --- /dev/null +++ b/python/card-games/lists_test.py @@ -0,0 +1,137 @@ +import unittest +import pytest + +from lists import ( + get_rounds, + concatenate_rounds, + list_contains_round, + card_average, + approx_average_is_average, + average_even_is_average_odd, + maybe_double_last, +) + + +class CardGamesTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_get_rounds(self): + + input_data = [0, 1, 10, 27, 99, 666] + result_data = [[0, 1, 2], [1, 2, 3], + [10, 11, 12], [27, 28, 29], + [99, 100, 101], [666, 667, 668]] + + for variant, (number, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', number=number, expected=expected): + actual_result = get_rounds(number) + error_message = (f'Called get_rounds({number}). ' + f'The function returned {actual_result}, ' + f'but the tests expected rounds {expected} ' + f'given the current round {number}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=2) + def test_concatenate_rounds(self): + + input_data = [([], []), ([0, 1], []), ([], [1, 2]), + ([1], [2]), ([27, 28, 29], [35, 36]), + ([1, 2, 3], [4, 5, 6])] + + result_data = [[], [0, 1], [1, 2], [1, 2], + [27, 28, 29, 35, 36], + [1, 2, 3, 4, 5, 6]] + + for variant, ((rounds_1, rounds_2), expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', rounds_1=rounds_1, rounds_2=rounds_2, expected=expected): + actual_result = concatenate_rounds(rounds_1, rounds_2) + error_message = (f'Called concatenate_rounds({rounds_1}, {rounds_2}). ' + f'The function returned {actual_result}, but the tests ' + f'expected {expected} as the concatenation ' + f'of {rounds_1} and {rounds_2}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_list_contains_round(self): + + input_data = [([], 1), ([1, 2, 3], 0), + ([27, 28, 29, 35, 36], 30), + ([1], 1), ([1, 2, 3], 1), + ([27, 28, 29, 35, 36], 29)] + result_data = [False, False, False, True, True, True] + + for variant, ((rounds, round_number), expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', rounds=rounds, round_number=round_number, expected=expected): + actual_result = list_contains_round(rounds, round_number) + error_message = (f'Called list_contains_round({rounds}, {round_number}). ' + f'The function returned {actual_result}, but round {round_number} ' + f'{"is" if expected else "is not"} in {rounds}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=4) + def test_card_average(self): + + input_data = [[1], [5, 6, 7], [1, 2, 3, 4], [1, 10, 100]] + result_data = [1.0, 6.0, 2.5, 37.0] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = card_average(hand) + error_message = (f'Called card_average({hand}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} as the average of {hand}.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=5) + def test_approx_average_is_average(self): + + input_data = [[0, 1, 5], [3, 6, 9, 12, 150], [1, 2, 3, 5, 9], + [2, 3, 4, 7, 8], [1, 2, 3], [2, 3, 4], + [2, 3, 4, 8, 8], [1, 2, 4, 5, 8]] + + result_data = [False, False, False, False, True, True, True, True] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=hand, expected=expected): + actual_result = approx_average_is_average(hand) + error_message = (f'Called approx_average_is_average({hand}). ' + f'The function returned {actual_result}, but ' + f'the hand {hand} {"does" if expected else "does not"} ' + f'yield the same approximate average.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=6) + def test_average_even_is_average_odd(self): + + input_data = [[5, 6, 8], [1, 2, 3, 4], [1, 2, 3], [5, 6, 7], [1, 3, 5, 7, 9]] + result_data = [False, False, True, True, True] + + for variant, (input_hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', input_hand=input_hand, expected=expected): + actual_result = average_even_is_average_odd(input_hand) + error_message = (f'Called average_even_is_average_odd({input_hand}). ' + f'The function returned {actual_result}, but ' + f'the hand {"does" if expected else "does not"} ' + f'yield the same odd-even average.') + + self.assertEqual(actual_result, expected, msg=error_message) + + @pytest.mark.task(taskno=7) + def test_maybe_double_last(self): + + input_data = [(1, 2, 11), (5, 9, 11), (5, 9, 10), (1, 2, 3), (1, 11, 8)] + result_data = [[1, 2, 22], [5, 9, 22], [5, 9, 10], [1, 2, 3], [1, 11, 8]] + + for variant, (hand, expected) in enumerate(zip(input_data, result_data), start=1): + with self.subTest(f'variation #{variant}', hand=list(hand), expected=expected): + actual_result = maybe_double_last(list(hand)) + error_message = (f'Called maybe_double_last({list(hand)}). ' + f'The function returned {actual_result}, but ' + f'the tests expected {expected} as the maybe-doubled version of {list(hand)}.') + + self.assertEqual(actual_result, expected, msg=error_message)