Compare commits

...

2 commits

Author SHA1 Message Date
bbd7c562bd
Python: completed Meltdown Mitigation 2024-09-26 17:51:05 -04:00
d336d146db
Python: completed Card Games 2024-09-26 17:49:18 -04:00
14 changed files with 1349 additions and 0 deletions

View file

@ -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."
}

View file

@ -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}

130
python/card-games/HELP.md Normal file
View file

@ -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}
```
<br>
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.

View file

@ -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 (`<list>[]`) 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 `<list>[-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_ (`<list>[<start>:<stop>:<step>]`).
## 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

393
python/card-games/README.md Normal file
View file

@ -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 `<list>.copy()`.
Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `<list>.index()`, `<list>.append()` and `<list>.reverse()`.
List elements can be iterated over using the `for item in <list>` construct.
`for index, item in enumerate(<list>)` 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 "<stdin>", line 1, in <module>
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_).
<table>
<tr>
<td style="vertical-align: top"> index from left ⟹<br><br><br><br><br><br><br></td><td style="vertical-align: middle">
| 0<br>👇🏾 | 1<br>👇🏾 | 2<br>👇🏾 | 3<br>👇🏾 | 4<br>👇🏾 | 5<br>👇🏾 |
|:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |
| P | y | t | h | o | n |
| 👆🏾<br>-6 | 👆🏾<br>-5 | 👆🏾<br>-4 | 👆🏾<br>-3 | 👆🏾<br>-2 | 👆🏾<br>-1 |
</td><td style="vertical-align: bottom"><br><br><br><br><br>⟸ index from right</td>
</tr>
</table>
```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_ (`<list>[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(<round_number>)` 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(<rounds_1>, <rounds_2>)` 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(<rounds>, <round_number>)` 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(<hand>)` 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(<hand>)`, 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(<hand>)` 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(<hand>)` 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

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,22 @@
{
"authors": [
"sachsom95",
"BethanyG"
],
"contributors": [
"kbuc"
],
"files": {
"solution": [
"conditionals.py"
],
"test": [
"conditionals_test.py"
],
"exemplar": [
".meta/exemplar.py"
]
},
"icon": "circular-buffer",
"blurb": "Learn about conditionals and avoid a meltdown by developing a simple control system for a Nuclear Reactor."
}

View file

@ -0,0 +1 @@
{"track":"python","exercise":"meltdown-mitigation","id":"b1c401666a194150aa374afe91089205","url":"https://exercism.org/tracks/python/exercises/meltdown-mitigation","handle":"Chomp1295","is_requester":true,"auto_approve":false}

View file

@ -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}
```
<br>
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 conditionals.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.

View file

@ -0,0 +1,50 @@
# Hints
## General
- The Python Docs on [Control Flow Tools][control flow tools] and the Real Python tutorial on [conditionals][real python conditionals] are great places to start.
- The Python Docs on [Boolean Operations][boolean operations] can be a great refresher on `bools`, as can the Real Python tutorial on [booleans][python booleans].
- The Python Docs on [Comparisons][comparisons] and [comparisons examples][python comparisons examples] can be a great refresher for comparisons.
## 1. Check for criticality
- Comparison operators ([comparisons][comparisons review]) and boolean operations ([concept:python/bools]()) can be combined and used with conditionals.
- Conditional expressions must evaluate to `True` or `False`.
- `else` can be used for a code block that will execute when all conditional tests return `False`.
```python
>>> item = 'blue'
>>> item_2 = 'green'
>>> if len(item) >= 3 and len(item_2) < 5:
print('Both pass the test!')
elif len(item) >= 3 or len(item_2) < 5:
print('One passes the test!')
else:
print('None pass the test!')
...
One passes the test!
```
## 2. Determine the Power output range
- Comparison operators can be combined and used with conditionals.
- Any number of `elif` statements can be used as decision "branches".
- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools.
- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable.
## 3. Fail Safe Mechanism
- Comparison operators can be combined and used with conditionals.
- Any number of `elif` statements can be used as decision "branches".
- Each "branch" can have a separate `return`, although it might be considered "bad form" by linting tools.
- If the linter complains, consider assigning the output of a branch to a common variable, and then `return`ing that variable.
[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[comparisons review]: https://www.learnpython.dev/02-introduction-to-python/090-boolean-logic/20-comparisons/
[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html
[python booleans]: https://realpython.com/python-boolean/
[python comparisons examples]: https://www.tutorialspoint.com/python/comparison_operators_example.htm
[real python conditionals]: https://realpython.com/python-conditional-statements/

View file

@ -0,0 +1,172 @@
# Meltdown Mitigation
Welcome to Meltdown Mitigation 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, [`if`][if statement], `elif` (_a contraction of 'else and if'_) and `else` statements are used to [control the flow][control flow tools] of execution and make decisions in a program.
Unlike many other programming languages, Python versions 3.9 and below do not offer a formal case-switch statement, instead using multiple `elif` statements to serve a similar purpose.
Python 3.10 introduces a variant case-switch statement called `structural pattern matching`, which will be covered separately in another concept.
Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` type directly, or by evaluating as ["truthy" or "falsy"][truth value testing].
```python
x = 10
y = 5
# The comparison '>' returns the bool 'True',
# so the statement is printed.
if x > y:
print("x is greater than y")
...
>>> x is greater than y
```
When paired with `if`, an optional `else` code block will execute when the original `if` condition evaluates to `False`:
```python
x = 5
y = 10
# The comparison '>' here returns the bool 'False',
# so the 'else' block is executed instead of the 'if' block.
if x > y:
print("x is greater than y")
else:
print("y is greater than x")
...
>>> y is greater than x
```
`elif` allows for multiple evaluations/branches.
```python
x = 5
y = 10
z = 20
# The 'elif' statement allows for the checking of more conditions.
if x > y:
print("x is greater than y and z")
elif y > z:
print("y is greater than x and z")
else:
print("z is greater than x and y")
...
>>> z is greater than x and y
```
[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
```python
>>> def classic_fizzbuzz(number):
if number % 3 == 0 and number % 5 == 0:
say = 'FizzBuzz!'
elif number % 5 == 0:
say = 'Buzz!'
elif number % 3 == 0:
say = 'Fizz!'
else:
say = str(number)
return say
>>> classic_fizzbuzz(15)
'FizzBuzz!'
>>> classic_fizzbuzz(13)
'13'
```
[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
## Instructions
In this exercise, we'll develop a simple control system for a nuclear reactor.
For a reactor to produce the power it must be in a state of _criticality_.
If the reactor is in a state less than criticality, it can become damaged.
If the reactor state goes beyond criticality, it can overload and result in a meltdown.
We want to mitigate the chances of meltdown and correctly manage reactor state.
The following three tasks are all related to writing code for maintaining ideal reactor state.
## 1. Check for criticality
The first thing a control system has to do is check if the reactor is balanced in criticality.
A reactor is said to be critical if it satisfies the following conditions:
- The temperature is less than 800 K.
- The number of neutrons emitted per second is greater than 500.
- The product of temperature and neutrons emitted per second is less than 500000.
Implement the function `is_criticality_balanced()` that takes `temperature` measured in kelvin and `neutrons_emitted` as parameters, and returns `True` if the criticality conditions are met, `False` if not.
```python
>>> is_criticality_balanced(750, 600)
True
```
## 2. Determine the Power output range
Once the reactor has started producing power its efficiency needs to be determined.
Efficiency can be grouped into 4 bands:
1. `green` -> efficiency of 80% or more,
2. `orange` -> efficiency of less than 80% but at least 60%,
3. `red` -> efficiency below 60%, but still 30% or more,
4. `black` -> less than 30% efficient.
The percentage value can be calculated as `(generated_power/theoretical_max_power)*100`
where `generated_power` = `voltage` * `current`.
Note that the percentage value is usually not an integer number, so make sure to consider the
proper use of the `<` and `<=` comparisons.
Implement the function `reactor_efficiency(<voltage>, <current>, <theoretical_max_power>)`, with three parameters: `voltage`,
`current`, and `theoretical_max_power`.
This function should return the efficiency band of the reactor : 'green', 'orange', 'red', or 'black'.
```python
>>> reactor_efficiency(200,50,15000)
'orange'
```
## 3. Fail Safe Mechanism
Your final task involves creating a fail-safe mechanism to avoid overload and meltdown.
This mechanism will determine if the reactor is below, at, or above the ideal criticality threshold.
Criticality can then be increased, decreased, or stopped by inserting (or removing) control rods into the reactor.
Implement the function called `fail_safe()`, which takes 3 parameters: `temperature` measured in kelvin,
`neutrons_produced_per_second`, and `threshold`, and outputs a status code for the reactor.
- If `temperature * neutrons_produced_per_second` < 90% of `threshold`, output a status code of 'LOW'
indicating that control rods must be removed to produce power.
- If the value `temperature * neutrons_produced_per_second` is within 10% of the `threshold` (so either 0-10% less than the threshold, at the threshold, or 0-10% greater than the threshold), the reactor is in _criticality_ and the status code of 'NORMAL' should be output, indicating that the reactor is in optimum condition and control rods are in an ideal position.
- If `temperature * neutrons_produced_per_second` is not in the above-stated ranges, the reactor is
going into meltdown and a status code of 'DANGER' must be passed to immediately shut down the reactor.
```python
>>> fail_safe(temperature=1000, neutrons_produced_per_second=30, threshold=5000)
'DANGER'
```
## Source
### Created by
- @sachsom95
- @BethanyG
### Contributed to by
- @kbuc

View file

@ -0,0 +1,79 @@
"""Functions to prevent a nuclear meltdown."""
def is_criticality_balanced(temperature, neutrons_emitted):
"""Verify criticality is balanced.
:param temperature: int or float - temperature value in kelvin.
:param neutrons_emitted: int or float - number of neutrons emitted per second.
:return: bool - is criticality balanced?
A reactor is said to be critical if it satisfies the following conditions:
- The temperature is less than 800 K.
- The number of neutrons emitted per second is greater than 500.
- The product of temperature and neutrons emitted per second is less than 500000.
"""
if (
temperature < 800
and neutrons_emitted > 500
and temperature * neutrons_emitted < 500000
):
return True
return False
def reactor_efficiency(voltage, current, theoretical_max_power):
"""Assess reactor efficiency zone.
:param voltage: int or float - voltage value.
:param current: int or float - current value.
:param theoretical_max_power: int or float - power that corresponds to a 100% efficiency.
:return: str - one of ('green', 'orange', 'red', or 'black').
Efficiency can be grouped into 4 bands:
1. green -> efficiency of 80% or more,
2. orange -> efficiency of less than 80% but at least 60%,
3. red -> efficiency below 60%, but still 30% or more,
4. black -> less than 30% efficient.
The percentage value is calculated as
(generated power/ theoretical max power)*100
where generated power = voltage * current
"""
efficiency = ((voltage * current) / theoretical_max_power) * 100
if efficiency >= 80:
return "green"
if efficiency >= 60:
return "orange"
if efficiency >= 30:
return "red"
return "black"
def fail_safe(temperature, neutrons_produced_per_second, threshold):
"""Assess and return status code for the reactor.
:param temperature: int or float - value of the temperature in kelvin.
:param neutrons_produced_per_second: int or float - neutron flux.
:param threshold: int or float - threshold for category.
:return: str - one of ('LOW', 'NORMAL', 'DANGER').
1. 'LOW' -> `temperature * neutrons per second` < 90% of `threshold`
2. 'NORMAL' -> `temperature * neutrons per second` +/- 10% of `threshold`
3. 'DANGER' -> `temperature * neutrons per second` is not in the above-stated ranges
"""
if temperature * neutrons_produced_per_second < threshold * 0.9:
return "LOW"
if temperature * neutrons_produced_per_second <= threshold * 1.1:
return "NORMAL"
if temperature * neutrons_produced_per_second > threshold * 1.1:
return "DANGER"

View file

@ -0,0 +1,82 @@
import unittest
import pytest
from conditionals import (is_criticality_balanced,
reactor_efficiency,
fail_safe)
class MeltdownMitigationTest(unittest.TestCase):
"""Test cases for Meltdown mitigation exercise.
"""
@pytest.mark.task(taskno=1)
def test_is_criticality_balanced(self):
"""Testing border cases around typical points.
T, n == (800, 500), (625, 800), (500, 1000), etc.
"""
test_data = ((750, 650, True), (799, 501, True), (500, 600, True),
(1000, 800, False), (800, 500, False), (800, 500.01, False),
(799.99, 500, False), (500.01, 999.99, False), (625, 800, False),
(625.99, 800, False), (625.01, 799.99, False), (799.99, 500.01, True),
(624.99, 799.99, True), (500, 1000, False), (500.01, 1000, False),
(499.99, 1000, True))
for variant, data in enumerate(test_data, start=1):
temp, neutrons_emitted, expected = data
with self.subTest(f'variation #{variant}', temp=temp, neutrons_emitted=neutrons_emitted, expected=expected):
# pylint: disable=assignment-from-no-return
actual_result = is_criticality_balanced(temp, neutrons_emitted)
failure_message = (f'Called is_criticality_balanced({temp}, {neutrons_emitted}). '
f' The function returned {actual_result}, '
f'but the test expected {expected} as the return value.')
self.assertEqual(actual_result, expected, failure_message)
@pytest.mark.task(taskno=2)
def test_reactor_efficiency(self):
voltage = 10
theoretical_max_power = 10000
# The numbers are chosen so that current == 10 x percentage
test_data = ((1000, 'green'), (999, 'green'), (800, 'green'),
(799, 'orange'), (700, 'orange'), (600, 'orange'),
(599, 'red'), (560, 'red'), (400, 'red'), (300, 'red'),
(299, 'black'), (200, 'black'), (0, 'black'))
for variant, data in enumerate(test_data, start=1):
current, expected = data
with self.subTest(f'variation #{variant}', voltage=voltage, current=current,
theoretical_max_power=theoretical_max_power, expected=expected):
# pylint: disable=assignment-from-no-return
actual_result = reactor_efficiency(voltage, current, theoretical_max_power)
failure_message =(f'Called reactor_efficiency({voltage}, {current}, {theoretical_max_power}). '
f'The function returned {actual_result}, '
f'but the test expected {expected} as the return value.')
self.assertEqual(actual_result, expected, failure_message)
@pytest.mark.task(taskno=3)
def test_fail_safe(self):
temp = 10
threshold = 10000
test_data = ((399, 'LOW'), (300, 'LOW'), (1, 'LOW'),
(0, 'LOW'), (901, 'NORMAL'), (1000, 'NORMAL'),
(1099, 'NORMAL'), (899, 'LOW'), (700, 'LOW'),
(400, 'LOW'), (1101, 'DANGER'), (1200, 'DANGER'))
for variant, (neutrons_per_second, expected) in enumerate(test_data, start=1):
with self.subTest(f'variation #{variant}', temp=temp, neutrons_per_second=neutrons_per_second,
threshold=threshold, expected=expected):
# pylint: disable=assignment-from-no-return
actual_result = fail_safe(temp, neutrons_per_second, threshold)
failure_message = (f'Called fail_safe({temp}, {neutrons_per_second}, {threshold}). '
f'The function returned {actual_result}, '
f'but the test expected {expected} as the return value.')
self.assertEqual(actual_result, expected, failure_message)