Python: completed Currency Exchange, Ghost Gobble, and Guido's Lasagna

This commit is contained in:
Andrew Scott 2024-09-20 17:06:15 -04:00
parent d0147655f7
commit 1e8c06ae40
Signed by: a
GPG key ID: 7CD5A5977E4931C1
21 changed files with 1754 additions and 0 deletions

View file

@ -0,0 +1,28 @@
{
"authors": [
"Ticktakto",
"Yabby1997",
"limm-jk",
"OMEGA-Y",
"wnstj2007",
"J08K"
],
"contributors": [
"BethanyG",
"kytrinyx",
"pranasziaukas"
],
"files": {
"solution": [
"exchange.py"
],
"test": [
"exchange_test.py"
],
"exemplar": [
".meta/exemplar.py"
]
},
"icon": "hyperia-forex",
"blurb": "Learn about numbers by solving Chandler's currency exchange conundrums."
}

View file

@ -0,0 +1 @@
{"track":"python","exercise":"currency-exchange","id":"733ea7bd3e2d4d00935c24cd7c70c5cc","url":"https://exercism.org/tracks/python/exercises/currency-exchange","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 exchange.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,43 @@
# Hints
## General
- [The Python Numbers Tutorial][python-numbers-tutorial] and [Python numeric types][python-numeric-types] can be a great introduction.
## 1. Estimate value after exchange
- You can use the [division operator][division-operator] to get the value of exchanged currency.
## 2. Calculate currency left after an exchange
- You can use the [subtraction operator][subtraction-operator] to get the amount of change.
## 3. Calculate value of bills
- You can use the [multiplication operator][multiplication-operator] to get the value of bills.
## 4. Calculate number of bills
- You need to divide `amount` into `denomination`.
- You need to use type casting to `int` to get the exact number of bills.
- To remove decimal places from a `float`, you can convert it to `int`.
**Note:** The `//` operator also does floor division. But, if the operand has `float`, the result is still `float`.
## 5. Calculate leftover after exchanging into bills
- You need to find the remainder of `amount` that does not equal a whole `denomination`.
- The Modulo operator `%` can help find the remainder.
## 6. Calculate value after exchange
- You need to calculate `spread` percent of `exchange_rate` using multiplication operator and add it to `exchange_rate` to get the exchanged currency.
- The actual rate needs to be computed. Remember to add exchange _rate_ and exchange _fee_.
- You can get exchanged money affected by commission by using divide operation and type casting to `int`.
[division-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
[multiplication-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers
[python-numbers-tutorial]: https://docs.python.org/3/tutorial/introduction.html#numbers
[python-numeric-types]: https://docs.python.org/3.9/library/stdtypes.html#numeric-types-int-float-complex
[subtraction-operator]: https://docs.python.org/3/tutorial/introduction.html#numbers

View file

@ -0,0 +1,198 @@
# Currency Exchange
Welcome to Currency Exchange 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
## Numbers
There are three different kinds of built-in numbers in Python : `ints`, `floats`, and `complex`. However, in this exercise you'll be dealing only with `ints` and `floats`.
### ints
`ints` are whole numbers. e.g. `1234`, `-10`, `20201278`.
Integers in Python have [arbitrary precision][arbitrary-precision] -- the number of digits is limited only by the available memory of the host system.
### floats
`floats` are numbers containing a decimal point. e.g. `0.0`,`3.14`,`-9.01`.
Floating point numbers are usually implemented in Python using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system and other implementation details. This can create some surprises when working with floats, but is "good enough" for most situations.
You can see more details and discussions in the following resources:
- [Python numeric type documentation][numeric-type-docs]
- [The Python Tutorial][floating point math]
- [Documentation for `int()` built in][`int()` built in]
- [Documentation for `float()` built in][`float()` built in]
- [0.30000000000000004.com][0.30000000000000004.com]
## Arithmetic
Python fully supports arithmetic between `ints` and `floats`. It will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). When division with `/`, `//` returns the quotient and `%` returns the remainder.
Python considers `ints` narrower than `floats`. So, using a float in an expression ensures the result will be a float too. However, when doing division, the result will always be a float, even if only integers are used.
```python
# The int is widened to a float here, and a float type is returned.
>>> 3 + 4.0
7.0
>>> 3 * 4.0
12.0
>>> 3 - 2.0
1.0
# Division always returns a float.
>>> 6 / 2
3.0
>>> 7 / 4
1.75
# Calculating remainders.
>>> 7 % 4
3
>>> 2 % 4
2
>>> 12.75 % 3
0.75
```
If an int result is needed, you can use `//` to truncate the result.
```python
>>> 6 // 2
3
>>> 7 // 4
1
```
To convert a float to an integer, you can use `int()`. Also, to convert an integer to a float, you can use `float()`.
```python
>>> int(6 / 2)
3
>>> float(1 + 2)
3.0
```
[0.30000000000000004.com]: https://0.30000000000000004.com/
[`float()` built in]: https://docs.python.org/3/library/functions.html#float
[`int()` built in]: https://docs.python.org/3/library/functions.html#int
[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#:~:text=In%20computer%20science%2C%20arbitrary%2Dprecision,memory%20of%20the%20host%20system.
[floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html
[numeric-type-docs]: https://docs.python.org/3/library/stdtypes.html#typesnumeric
## Instructions
Your friend Chandler plans to visit exotic countries all around the world. Sadly, Chandler's math skills aren't good. He's pretty worried about being scammed by currency exchanges during his trip - and he wants you to make a currency calculator for him. Here are his specifications for the app:
## 1. Estimate value after exchange
Create the `exchange_money()` function, taking 2 parameters:
1. `budget` : The amount of money you are planning to exchange.
2. `exchange_rate` : The amount of domestic currency equal to one unit of foreign currency.
This function should return the value of the exchanged currency.
**Note:** If your currency is USD and you want to exchange USD for EUR with an exchange rate of `1.20`, then `1.20 USD == 1 EUR`.
```python
>>> exchange_money(127.5, 1.2)
106.25
```
## 2. Calculate currency left after an exchange
Create the `get_change()` function, taking 2 parameters:
1. `budget` : Amount of money before exchange.
2. `exchanging_value` : Amount of money that is *taken* from the budget to be exchanged.
This function should return the amount of money that *is left* from the budget.
```python
>>> get_change(127.5, 120)
7.5
```
## 3. Calculate value of bills
Create the `get_value_of_bills()` function, taking 2 parameters:
1. `denomination` : The value of a single bill.
2. `number_of_bills` : The total number of bills.
This exchanging booth only deals in cash of certain increments.
The total you receive must be divisible by the value of one "bill" or unit, which can leave behind a fraction or remainder.
Your function should return only the total value of the bills (_excluding fractional amounts_) the booth would give back.
Unfortunately, the booth gets to keep the remainder/change as an added bonus.
```python
>>> get_value_of_bills(5, 128)
640
```
## 4. Calculate number of bills
Create the `get_number_of_bills()` function, taking `amount` and `denomination`.
This function should return the _number of currency bills_ that you can receive within the given _amount_.
In other words: How many _whole bills_ of currency fit into the starting amount?
Remember -- you can only receive _whole bills_, not fractions of bills, so remember to divide accordingly.
Effectively, you are rounding _down_ to the nearest whole bill/denomination.
```python
>>> get_number_of_bills(127.5, 5)
25
```
## 5. Calculate leftover after exchanging into bills
Create the `get_leftover_of_bills()` function, taking `amount` and `denomination`.
This function should return the _leftover amount_ that cannot be returned from your starting _amount_ given the denomination of bills.
It is very important to know exactly how much the booth gets to keep.
```python
>>> get_leftover_of_bills(127.5, 20)
7.5
```
## 6. Calculate value after exchange
Create the `exchangeable_value()` function, taking `budget`, `exchange_rate`, `spread`, and `denomination`.
Parameter `spread` is the *percentage taken* as an exchange fee, written as an integer.
It needs to be converted to decimal by dividing it by 100.
If `1.00 EUR == 1.20 USD` and the *spread* is `10`, the actual exchange rate will be: `1.00 EUR == 1.32 USD` because 10% of 1.20 is 0.12, and this additional fee is added to the exchange.
This function should return the maximum value of the new currency after calculating the *exchange rate* plus the *spread*.
Remember that the currency *denomination* is a whole number, and cannot be sub-divided.
**Note:** Returned value should be `int` type.
```python
>>> exchangeable_value(127.25, 1.20, 10, 20)
80
>>> exchangeable_value(127.25, 1.20, 10, 5)
95
```
## Source
### Created by
- @Ticktakto
- @Yabby1997
- @limm-jk
- @OMEGA-Y
- @wnstj2007
- @J08K
### Contributed to by
- @BethanyG
- @kytrinyx
- @pranasziaukas

View file

@ -0,0 +1,79 @@
"""Functions for calculating steps in exchanging currency.
Python numbers documentation: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
Overview of exchanging currency when travelling: https://www.compareremit.com/money-transfer-tips/guide-to-exchanging-currency-for-overseas-travel/
"""
def exchange_money(budget, exchange_rate):
"""
:param budget: float - amount of money you are planning to exchange.
:param exchange_rate: float - unit value of the foreign currency.
:return: float - exchanged value of the foreign currency you can receive.
"""
return budget / exchange_rate
def get_change(budget, exchanging_value):
"""
:param budget: float - amount of money you own.
:param exchanging_value: float - amount of your money you want to exchange now.
:return: float - amount left of your starting currency after exchanging.
"""
return budget - exchanging_value
def get_value_of_bills(denomination, number_of_bills):
"""
:param denomination: int - the value of a bill.
:param number_of_bills: int - total number of bills.
:return: int - calculated value of the bills.
"""
return int(denomination * number_of_bills)
def get_number_of_bills(amount, denomination):
"""
:param amount: float - the total starting value.
:param denomination: int - the value of a single bill.
:return: int - number of bills that can be obtained from the amount.
"""
return amount // denomination
def get_leftover_of_bills(amount, denomination):
"""
:param amount: float - the total starting value.
:param denomination: int - the value of a single bill.
:return: float - the amount that is "leftover", given the current denomination.
"""
return amount % denomination
def exchangeable_value(budget, exchange_rate, spread, denomination):
"""
:param budget: float - the amount of your money you are planning to exchange.
:param exchange_rate: float - the unit value of the foreign currency.
:param spread: int - percentage that is taken as an exchange fee.
:param denomination: int - the value of a single bill.
:return: int - maximum value you can get.
"""
exchange_rate += exchange_rate * (spread / 100)
return (
get_number_of_bills(exchange_money(budget, exchange_rate), denomination)
* denomination
)

View file

@ -0,0 +1,141 @@
import unittest
import pytest
from exchange import (
exchange_money,
get_change,
get_value_of_bills,
get_number_of_bills,
get_leftover_of_bills,
exchangeable_value)
class CurrencyExchangeTest(unittest.TestCase):
@pytest.mark.task(taskno=1)
def test_exchange_money(self):
test_data = [(100000, 0.8), (700000, 10.0)]
result_data = [125000, 70000]
for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1):
budget, exchange_rate = params
with self.subTest(f"variation #{variant}",
budget=budget,
exchange_rate=exchange_rate,
expected=expected):
actual_result = exchange_money(*params)
error_message = (f'Called exchange_money{budget, exchange_rate}. '
f'The function returned {actual_result}, but '
f'The tests expected {expected} when exchanging'
f' {budget} at a rate of {exchange_rate}.')
self.assertAlmostEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=2)
def test_get_change(self):
test_data = [(463000, 5000), (1250, 120), (15000, 1380)]
result_data = [458000, 1130, 13620]
for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1):
budget, exchanging_value = params
with self.subTest(f"variation #{variant}",
budget=budget,
exchanging_value=exchanging_value,
expected=expected):
actual_result = get_change(*params)
error_message = (f'Called get_change{budget, exchanging_value}. '
f'The function returned {actual_result}, but '
f'The tests expected {expected} left in your budget.')
self.assertAlmostEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=3)
def test_get_value_of_bills(self):
test_data = [(10000, 128), (50, 360), (200, 200)]
result_data = [1280000, 18000, 40000]
for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1):
denomination, number_of_bills = params
with self.subTest(f"variation #{variant}",
denomination=denomination,
number_of_bills=number_of_bills,
expected=expected):
actual_result = get_value_of_bills(*params)
error_message = (f'Called get_value_of_bills{denomination, number_of_bills}. '
f'The function returned {actual_result}, but '
f'The tests expected {expected} for the bills value.')
self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=4)
def test_get_number_of_bills(self):
test_data = [(163270, 50000), (54361, 1000)]
result_data = [3, 54]
for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1):
amount, denomination = params
with self.subTest(f"variation #{variant}",
amount=amount,
denomination=denomination,
expected=expected):
actual_result = get_number_of_bills(amount, denomination)
error_message = (f'Called get_number_of_bills{amount, denomination}. '
f'The function returned {actual_result} bills, but '
f'The tests expected {expected} bills.')
self.assertEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=5)
def test_get_leftover_of_bills(self):
test_data = [(10.1, 10), (654321.0, 5), (3.14, 2)]
result_data = [0.1, 1.0, 1.14]
for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1):
amount, denomination = params
with self.subTest(f"variation #{variant}",
amount=amount,
denomination=denomination,
expected=expected):
actual_result = get_leftover_of_bills(*params)
error_message = (f'Called get_leftover_of_bills{amount, denomination}. '
f'The function returned {actual_result}, but '
f'The tests expected {expected} as the leftover amount.')
self.assertAlmostEqual(actual_result, expected, msg=error_message)
@pytest.mark.task(taskno=6)
def test_exchangeable_value(self):
test_data = [(100000, 10.61, 10, 1),
(1500, 0.84, 25, 40),
(470000, 1050, 30, 10000000000),
(470000, 0.00000009, 30, 700),
(425.33, 0.0009, 30, 700)]
result_data = [8568, 1400, 0, 4017094016600, 363300]
for variant, (params, expected) in enumerate(zip(test_data, result_data), start=1):
budget, exchange_rate, spread, denomination = params
with self.subTest(f"variation #{variant}",
budget=budget,
exchange_rate=exchange_rate,
spread=spread,
denomination=denomination,
expected=expected):
actual_result = exchangeable_value(budget, exchange_rate, spread, denomination)
error_message = (f'Called exchangeable_value{budget, exchange_rate, spread, denomination}. '
f'The function returned {actual_result}, but '
f'The tests expected {expected} as the maximum '
f'value of the new currency .')
self.assertEqual(actual_result, expected, msg=error_message)

View file

@ -0,0 +1,26 @@
{
"authors": [
"neenjaw"
],
"contributors": [
"cmccandless",
"BethanyG"
],
"files": {
"solution": [
"arcade_game.py"
],
"test": [
"arcade_game_test.py"
],
"exemplar": [
".meta/exemplar.py"
]
},
"language_versions": ">=3.5",
"forked_from": [
"elixir/pacman-rules"
],
"icon": "matching-brackets",
"blurb": "Learn about bools by setting up the rules for the Ghost Gobble arcade game."
}

View file

@ -0,0 +1 @@
{"track":"python","exercise":"ghost-gobble-arcade-game","id":"65cc0be008194bdb839d13c86960bbbb","url":"https://exercism.org/tracks/python/exercises/ghost-gobble-arcade-game","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 arcade_game.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,26 @@
# Hints
## General
- For an overview, this section of the Python documentation: [Truth Value Testing][stdlib-bools] might help.
- Don't worry about how the arguments are _derived_, focus on combining the arguments to return the intended result.
## 1. Define if Pac-Man can eat a ghost
- You can use the [Boolean][boolean] [operators][Boolean-operators] to combine arguments for a result.
## 2. Define if Pac-Man scores
- You can use the [Boolean][boolean] [operators][Boolean-operators] to combine arguments for a result.
## 3. Define if Pac-Man loses
- You can use the [boolean][Boolean] [operators][Boolean-operators] to combine arguments for a result.
## 4. Define if Pac-Man wins
- You can use the [Boolean][boolean] [operators][Boolean-operators] to combine arguments for a result.
[Boolean-operators]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
[boolean]: https://docs.python.org/3/library/stdtypes.html#truth
[stdlib-bools]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing

View file

@ -0,0 +1,96 @@
# Ghost Gobble Arcade Game
Welcome to Ghost Gobble Arcade Game 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
Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`.
There are only two values in this type: `True` and `False`.
These values can be bound to a variable:
```python
>>> true_variable = True
>>> false_variable = False
```
We can evaluate Boolean expressions using the `and`, `or`, and `not` operators:
```python
>>> true_variable = True and True
>>> false_variable = True and False
>>> true_variable = False or True
>>> false_variable = False or False
>>> true_variable = not False
>>> false_variable = not True
```
[bools]: https://docs.python.org/3/library/stdtypes.html#typebool
## Instructions
In this exercise, you need to implement some rules from [Pac-Man][Pac-Man], the classic 1980s-era arcade-game.
You have four rules to implement, all related to the game states.
> _Do not worry about how the arguments are derived, just focus on combining the arguments to return the intended result._
## 1. Define if Pac-Man eats a ghost
Define the `eat_ghost()` function that takes two parameters (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man is able to eat a ghost.
The function should return `True` only if Pac-Man has a power pellet active and is touching a ghost.
```python
>>> eat_ghost(False, True)
...
False
```
## 2. Define if Pac-Man scores
Define the `score()` function that takes two parameters (_if Pac-Man is touching a power pellet_ and _if Pac-Man is touching a dot_) and returns a Boolean value if Pac-Man scored.
The function should return `True` if Pac-Man is touching a power pellet or a dot.
```python
>>> score(True, True)
...
True
```
## 3. Define if Pac-Man loses
Define the `lose()` function that takes two parameters (_if Pac-Man has a power pellet active_ and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man loses.
The function should return `True` if Pac-Man is touching a ghost and does not have a power pellet active.
```python
>>> lose(False, True)
...
True
```
## 4. Define if Pac-Man wins
Define the `win()` function that takes three parameters (_if Pac-Man has eaten all of the dots_, _if Pac-Man has a power pellet active_, and _if Pac-Man is touching a ghost_) and returns a Boolean value if Pac-Man wins.
The function should return `True` if Pac-Man has eaten all of the dots and has not lost based on the parameters defined in part 3.
```python
>>> win(False, True, False)
...
False
```
[Pac-Man]: https://en.wikipedia.org/wiki/Pac-Man
## Source
### Created by
- @neenjaw
### Contributed to by
- @cmccandless
- @BethanyG

View file

@ -0,0 +1,46 @@
"""Functions for implementing the rules of the classic arcade game Pac-Man."""
def eat_ghost(power_pellet_active, touching_ghost):
"""Verify that Pac-Man can eat a ghost if he is empowered by a power pellet.
:param power_pellet_active: bool - does the player have an active power pellet?
:param touching_ghost: bool - is the player touching a ghost?
:return: bool - can a ghost be eaten?
"""
return power_pellet_active and touching_ghost
def score(touching_power_pellet, touching_dot):
"""Verify that Pac-Man has scored when a power pellet or dot has been eaten.
:param touching_power_pellet: bool - is the player touching a power pellet?
:param touching_dot: bool - is the player touching a dot?
:return: bool - has the player scored or not?
"""
return touching_power_pellet or touching_dot
def lose(power_pellet_active, touching_ghost):
"""Trigger the game loop to end (GAME OVER) when Pac-Man touches a ghost without his power pellet.
:param power_pellet_active: bool - does the player have an active power pellet?
:param touching_ghost: bool - is the player touching a ghost?
:return: bool - has the player lost the game?
"""
return not power_pellet_active and touching_ghost
def win(has_eaten_all_dots, power_pellet_active, touching_ghost):
"""Trigger the victory event when all dots have been eaten.
:param has_eaten_all_dots: bool - has the player "eaten" all the dots?
:param power_pellet_active: bool - does the player have an active power pellet?
:param touching_ghost: bool - is the player touching a ghost?
:return: bool - has the player won the game?
"""
return has_eaten_all_dots and not lose(power_pellet_active, touching_ghost)

View file

@ -0,0 +1,144 @@
import unittest
import pytest
from arcade_game import eat_ghost, score, lose, win
class GhostGobbleGameTest(unittest.TestCase):
@pytest.mark.task(taskno=1)
def test_ghost_gets_eaten(self):
actual_result = eat_ghost(True, True)
error_message = ('Called eat_ghost(True, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the ghost gets eaten (True).')
self.assertIs(actual_result, True, msg=error_message)
@pytest.mark.task(taskno=1)
def test_ghost_does_not_get_eaten_because_no_power_pellet_active(self):
actual_result = eat_ghost(False, True)
error_message = ('Called eat_ghost(False, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'ghost **does not** get eaten because '
'no power pellet was active.')
self.assertIs(actual_result, False, msg=error_message)
@pytest.mark.task(taskno=1)
def test_ghost_does_not_get_eaten_because_not_touching_ghost(self):
actual_result = eat_ghost(True, False)
error_message = ('Called eat_ghost(True, False).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'ghost **does not** get eaten because '
'the player was not touching the ghost.')
self.assertIs(actual_result, False, msg=error_message)
@pytest.mark.task(taskno=2)
def test_score_when_eating_dot(self):
actual_result = score(False, True)
error_message = ('Called score(False, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player scores because they were touching a dot.')
self.assertIs(actual_result, True, msg=error_message)
@pytest.mark.task(taskno=2)
def test_score_when_eating_power_pellet(self):
actual_result = score(True, False)
error_message = ('Called score(True, False).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player scores because they '
'were touching a power pellet.')
self.assertIs(actual_result,True,msg=error_message)
@pytest.mark.task(taskno=2)
def test_no_score_when_nothing_eaten(self):
actual_result = score(False, False)
error_message = ('Called score(False, False).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player **does not** score because they '
'were not touching anything.')
self.assertIs(actual_result, False,msg=error_message)
@pytest.mark.task(taskno=3)
def test_lose_if_touching_a_ghost_without_a_power_pellet_active(self):
actual_result = lose(False, True)
error_message = ('Called lose(False, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player loses because they touched a '
'ghost without a power pellet activated.')
self.assertIs(
actual_result, True, msg=error_message)
@pytest.mark.task(taskno=3)
def test_dont_lose_if_touching_a_ghost_with_a_power_pellet_active(self):
actual_result = lose(True, True)
error_message = ('Called lose(True, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player **does not** lose because when they touched a '
'ghost, a power pellet was active.')
self.assertIs(actual_result, False, msg=error_message)
@pytest.mark.task(taskno=3)
def test_dont_lose_if_not_touching_a_ghost(self):
actual_result = lose(True, False)
error_message = ('Called lose(True, False).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player **does not** lose because they were '
'not touching a ghost.')
self.assertIs(actual_result, False, msg=error_message)
@pytest.mark.task(taskno=4)
def test_win_if_all_dots_eaten(self):
actual_result = win(True, False, False)
error_message = ('Called win(True, False, False).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player wins because all the dots were eaten.')
self.assertIs(actual_result, True, msg=error_message)
@pytest.mark.task(taskno=4)
def test_dont_win_if_all_dots_eaten_but_touching_a_ghost(self):
actual_result = win(True, False, True)
error_message = ('Called win(True, False, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the '
'player **does not** win, because '
'the player was touching a ghost.')
self.assertIs(actual_result, False, msg=error_message)
@pytest.mark.task(taskno=4)
def test_win_if_all_dots_eaten_and_touching_a_ghost_with_a_power_pellet_active(self):
actual_result = win(True, True, True)
error_message = ('Called win(True, True, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the player wins, '
f'because a power pellet was active when they '
f'touched a ghost.')
self.assertIs(actual_result, True, msg=error_message)
@pytest.mark.task(taskno=4)
def test_dont_win_if_not_all_dots_eaten(self):
actual_result = win(False, True, True)
error_message = ('Called win(False, True, True).'
f'The function returned {actual_result}, but the '
f'tests expected that the player **does not** win, '
f'because the player did not eat all of the dots.')
self.assertIs(actual_result, False, msg=error_message)

View file

@ -0,0 +1,22 @@
{
"authors": [
"BethanyG"
],
"files": {
"solution": [
"lasagna.py"
],
"test": [
"lasagna_test.py"
],
"exemplar": [
".meta/exemplar.py"
]
},
"forked_from": [
"csharp/lucians-luscious-lasagna",
"ruby/lasagna"
],
"icon": "lasagna",
"blurb": "Learn the basics of Python by cooking Guido's Gorgeous Lasagna."
}

View file

@ -0,0 +1 @@
{"track":"python","exercise":"guidos-gorgeous-lasagna","id":"978bc06ad2404c3daee057f3c151eb5d","url":"https://exercism.org/tracks/python/exercises/guidos-gorgeous-lasagna","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 lasagna.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,48 @@
# Hints
## General
- [The Python Tutorial][the python tutorial] can be a great introduction.
- [PEP 8][pep8] is the Python code style guide.
- [PEP 257][PEP257] details Python docstring conventions.
- [Numbers][numbers] in Python can be integers, floats, or complex.
## 1. Define expected bake time in minutes
- You need to [name][naming] a constant, and [assign][assignment] it an [integer][numbers] value.
## 2. Calculate remaining bake time in minutes
- You need to define a [function][defining functions] with a single parameter representing the time elapsed so far.
- Use the [mathematical operator for subtraction][numbers] to subtract values.
- This function should [return a value][return].
## 3. Calculate preparation time in minutes
- You need to define a [function][defining functions] with a single parameter representing the number of layers.
- Use the [mathematical operator for multiplication][numbers] to multiply values.
- You could define an extra _constant_ for the time in minutes per layer rather than using a "magic number" in your code.
- This function should [return a value][return].
## 4. Calculate total elapsed cooking time (prep + bake) in minutes
- You need to define a [function][defining functions] with two parameters.
- Remember: you can always _call_ a function you've defined previously.
- You can use the [mathematical operator for addition][python as a calculator] to sum values.
- This function should [return a value][return].
## 5. Update the recipe with notes
- Clearly [commenting][comments] and [documenting][docstrings] your code according to [PEP257][pep257] is always recommended.
[assignment]: https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment-stmt
[comments]: https://realpython.com/python-comments-guide/
[defining functions]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions
[docstrings]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings
[naming]: https://realpython.com/python-variables/
[numbers]: https://docs.python.org/3/tutorial/introduction.html#numbers
[pep257]: https://www.python.org/dev/peps/pep-0257/
[python as a calculator]: https://docs.python.org/3/tutorial/introduction.html#using-python-as-a-calculator
[return]: https://docs.python.org/3/reference/simple_stmts.html#return
[the python tutorial]: https://docs.python.org/3/tutorial/introduction.html

View file

@ -0,0 +1,333 @@
# Guido's Gorgeous Lasagna
Welcome to Guido's Gorgeous Lasagna 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
Python is a [dynamic and strongly][dynamic typing in python] typed programming language.
It employs both [duck typing][duck typing] and [gradual typing][gradual typing] via [type hints][type hints].
While Python supports many different programming _styles_, internally **everything in Python is an [object][everythings an object]**.
This includes numbers, strings, lists, and even functions.
We'll dig more into what all of that means as we continue through the track.
This first exercise introduces 4 major Python language features:
1. Name Assignment (_variables and constants_),
2. Functions (_the `def` keyword and the `return` keyword_),
3. Comments, and
4. Docstrings.
~~~~exercism/note
In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names.
On the Python track, [variables][variables] are always written in [`snake_case`][snake case], and constants in `SCREAMING_SNAKE_CASE`.
[variables]: https://realpython.com/python-variables/
[snake case]: https://en.wikipedia.org/wiki/Snake_case
~~~~
## Name Assignment (Variables & Constants)
Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: `<name> = <value>`.
A name can be reassigned (or re-bound) to different values (different object types) over its lifetime.
```python
>>> my_first_variable = 1 # my_first_variable bound to an integer object of value one.
>>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2.
>>> print(type(my_first_variable))
<class 'int'>
>>> print(my_first_variable)
2
>>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value.
>>> print(type(my_first_variable))
<class 'str'>
>>> print(my_first_variable)
"Now, I'm a string." # Strings can be declared using single or double quote marks.
```
### Constants
Constants are names meant to be assigned only once in a program.
They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program.
Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated.
## Functions
The `def` keyword begins a [function definition][function definition].
Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon.
Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_.
```python
# The body of a function is indented by 2 spaces, & prints the sum of the numbers.
def add_two_numbers(number_one, number_two):
total = number_one + number_two
print(total)
>>> add_two_numbers(3, 4)
7
# Inconsistent indentation in your code blocks will raise an error.
>>> def add_three_numbers_misformatted(number_one, number_two, number_three):
... result = number_one + number_two + number_three # This was indented by 4 spaces.
... print(result) #this was only indented by 3 spaces
...
...
File "<stdin>", line 3
print(result)
^
IndentationError: unindent does not match any outer indentation level
```
Functions _explicitly_ return a value or object via the [`return`][return] keyword:
```python
# Function definition on first line, explicit return used on final line.
def add_two_numbers(number_one, number_two):
return number_one + number_two
# Calling the function in the Python terminal returns the sum of the numbers.
>>> add_two_numbers(3, 4)
7
# Assigning the function call to a variable and printing it
# will also return the value.
>>> sum_with_return = add_two_numbers(5, 6)
>>> print(sum_with_return)
11
```
Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object.
The details of `None` will be covered in a later exercise.
For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
```python
# This function does not have an explicit return.
def add_two_numbers(number_one, number_two):
result = number_one + number_two
# Calling the function in the Python terminal appears
# to not return anything at all.
>>> add_two_numbers(5, 7)
>>>
# Using print() with the function call shows that
# the function is actually returning the **None** object.
>>> print(add_two_numbers(5, 7))
None
# Assigning the function call to a variable and printing
# the variable will also show None.
>>> sum_without_return = add_two_numbers(5, 6)
>>> print(sum_without_return)
None
```
### Calling Functions
Functions are [_called_][calls] or invoked using their name followed by `()`.
Dot (`.`) notation is used for calling functions defined inside a class or module.
```python
>>> def number_to_the_power_of(number_one, number_two):
return number_one ** number_two
...
>>> number_to_the_power_of(3,3) # Invoking the function with the arguments 3 and 3.
27
# A mismatch between the number of parameters and the number of arguments will raise an error.
>>> number_to_the_power_of(4,)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two'
# Calling methods or functions in classes and modules.
>>> start_text = "my silly sentence for examples."
>>> str.upper(start_text) # Calling the upper() method for the built-in str class.
"MY SILLY SENTENCE FOR EXAMPLES."
# Importing the math module
import math
>>> math.pow(2,4) # Calling the pow() function from the math module
>>> 16.0
```
## Comments
[Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination.
Unlike many other programming languages, Python **does not support** multi-line comment marks.
Each line of a comment block must start with the `#` character.
## Docstrings
The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose.
Docstrings are declared using triple double quotes (""") indented at the same level as the code block:
```python
# An example from PEP257 of a multi-line docstring.
def complex(real=0.0, imag=0.0):
"""Form a complex number.
Keyword arguments:
real -- the real part (default 0.0)
imag -- the imaginary part (default 0.0)
"""
if imag == 0.0 and real == 0.0:
return complex_zero
```
Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name.
Docstring conventions are laid out in [PEP257][pep257].
Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later exercise.
```python
# An example on a user-defined function.
>>> def number_to_the_power_of(number_one, number_two):
"""Raise a number to an arbitrary power.
:param number_one: int the base number.
:param number_two: int the power to raise the base number to.
:return: int - number raised to power of second number
Takes number_one and raises it to the power of number_two, returning the result.
"""
return number_one ** number_two
...
# Calling the .__doc__ attribute of the function and printing the result.
>>> print(number_to_the_power_of.__doc__)
Raise a number to an arbitrary power.
:param number_one: int the base number.
:param number_two: int the power to raise the base number to.
:return: int - number raised to power of second number
Takes number_one and raises it to the power of number_two, returning the result.
```
[calls]: https://docs.python.org/3/reference/expressions.html#calls
[comments]: https://realpython.com/python-comments-guide/#python-commenting-basics
[docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings
[doctests]: https://docs.python.org/3/library/doctest.html
[duck typing]: https://en.wikipedia.org/wiki/Duck_typing
[dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed
[everythings an object]: https://docs.python.org/3/reference/datamodel.html
[facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html
[function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions
[gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing
[module]: https://docs.python.org/3/tutorial/modules.html
[none]: https://docs.python.org/3/library/constants.html
[parameters]: https://docs.python.org/3/glossary.html#term-parameter
[pep257]: https://www.python.org/dev/peps/pep-0257/
[return]: https://docs.python.org/3/reference/simple_stmts.html#return
[type hints]: https://docs.python.org/3/library/typing.html
## Instructions
You're going to write some code to help you cook a gorgeous lasagna from your favorite cookbook.
You have five tasks, all related to cooking your recipe.
## 1. Define expected bake time in minutes
Define an `EXPECTED_BAKE_TIME` constant that returns how many minutes the lasagna should bake in the oven.
According to your cookbook, the Lasagna should be in the oven for 40 minutes:
```python
>>> import lasagna
>>> lasagna.EXPECTED_BAKE_TIME
40
```
## 2. Calculate remaining bake time in minutes
Implement the `bake_time_remaining()` function that takes the actual minutes the lasagna has been in the oven as an argument and returns how many minutes the lasagna still needs to bake based on the `EXPECTED_BAKE_TIME`.
```python
>>> from lasagna import bake_time_remaining
>>> bake_time_remaining(30)
10
```
## 3. Calculate preparation time in minutes
Implement the `preparation_time_in_minutes(number_of_layers)` function that takes the number of layers you want to add to the lasagna as an argument and returns how many minutes you would spend making them.
Assume each layer takes 2 minutes to prepare.
```python
>>> from lasagna import preparation_time_in_minutes
>>> preparation_time_in_minutes(2)
4
```
## 4. Calculate total elapsed cooking time (prep + bake) in minutes
Implement the `elapsed_time_in_minutes(number_of_layers, elapsed_bake_time)` function that has two parameters: `number_of_layers` (_the number of layers added to the lasagna_) and `elapsed_bake_time` (_the number of minutes the lasagna has been baking in the oven_).
This function should return the total number of minutes you've been cooking, or the sum of your preparation time and the time the lasagna has already spent baking in the oven.
```python
>>> from lasagna import elapsed_time_in_minutes
>>> elapsed_time_in_minutes(3, 20)
26
```
## 5. Update the recipe with notes
Go back through the recipe, adding "notes" in the form of function docstrings.
```python
def elapsed_time_in_minutes(number_of_layers, elapsed_bake_time):
"""Calculate the elapsed cooking time.
:param number_of_layers: int - the number of layers in the lasagna.
:param elapsed_bake_time: int - elapsed cooking time.
:return: int - total time elapsed (in minutes) preparing and cooking.
This function takes two integers representing the number of lasagna layers and the
time already spent baking and calculates the total elapsed minutes spent cooking the
lasagna.
"""
```
## Source
### Created by
- @BethanyG

View file

@ -0,0 +1,34 @@
"""Functions used in preparing Guido's gorgeous lasagna.
Learn about Guido, the creator of the Python language:
https://en.wikipedia.org/wiki/Guido_van_Rossum
This is a module docstring, used to describe the functionality
of a module and its functions and/or classes.
"""
EXPECTED_BAKE_TIME = 40
def bake_time_remaining(elapsed_bake_time: int):
"""Calculate the bake time remaining.
:param elapsed_bake_time: int - baking time already elapsed.
:return: int - remaining bake time (in minutes) derived from 'EXPECTED_BAKE_TIME'.
Function that takes the actual minutes the lasagna has been in the oven as
an argument and returns how many minutes the lasagna still needs to bake
based on the `EXPECTED_BAKE_TIME`.
"""
return EXPECTED_BAKE_TIME - elapsed_bake_time
def preparation_time_in_minutes(number_of_layers: int):
"""Calculate preparation time with the given number of layers"""
return number_of_layers * 2
def elapsed_time_in_minutes(number_of_layers: int, elapsed_bake_time: int):
"""Returns the number of minutes you've been cooking"""
return preparation_time_in_minutes(number_of_layers) + elapsed_bake_time

View file

@ -0,0 +1,97 @@
import unittest
import pytest
# For this first exercise, it is really important to be clear about how we are importing names for tests.
# To that end, we are putting a try/catch around imports and throwing specific messages to help students
# decode that they need to create and title their constants and functions in a specific way.
try:
from lasagna import (EXPECTED_BAKE_TIME,
bake_time_remaining,
preparation_time_in_minutes,
elapsed_time_in_minutes)
# Here, we are separating the constant import errors from the function name import errors
except ImportError as import_fail:
message = import_fail.args[0].split('(', maxsplit=1)
item_name = import_fail.args[0].split()[3]
if 'EXPECTED_BAKE_TIME' in item_name:
# pylint: disable=raise-missing-from
raise ImportError(f'\n\nMISSING CONSTANT --> \nWe can not find or import the constant {item_name} in your'
" 'lasagna.py' file.\nDid you misname or forget to define it?") from None
else:
item_name = item_name[:-1] + "()'"
# pylint: disable=raise-missing-from
raise ImportError("\n\nMISSING FUNCTION --> In your 'lasagna.py' file, we can not find or import the"
f' function named {item_name}. \nDid you misname or forget to define it?') from None
# Here begins the formal test cases for the exercise.
class LasagnaTest(unittest.TestCase):
@pytest.mark.task(taskno=1)
def test_EXPECTED_BAKE_TIME(self):
failure_msg = 'Expected a constant of EXPECTED_BAKE_TIME with a value of 40.'
self.assertEqual(EXPECTED_BAKE_TIME, 40, msg=failure_msg)
@pytest.mark.task(taskno=2)
def test_bake_time_remaining(self):
input_data = [1, 2, 5, 10, 15, 23, 33, 39]
result_data = [39, 38, 35, 30, 25, 17, 7, 1]
for variant, (time, expected) in enumerate(zip(input_data, result_data), start=1):
with self.subTest(f'variation #{variant}', time=time, expected=expected):
actual_result = bake_time_remaining(time)
failure_msg = (f'Called bake_time_remaining({time}). '
f'The function returned {actual_result}, but the tests '
f'expected {expected} as the remaining bake time.')
self.assertEqual(actual_result, expected, msg=failure_msg)
@pytest.mark.task(taskno=3)
def test_preparation_time_in_minutes(self):
input_data = [1, 2, 5, 8, 11, 15]
result_data = [2, 4, 10, 16, 22, 30]
for variant, (layers, expected) in enumerate(zip(input_data, result_data), start=1):
with self.subTest(f'variation #{variant}', layers=layers, expected=expected):
actual_result = preparation_time_in_minutes(layers)
failure_msg = (f'Called preparation_time_in_minutes({layers}). '
f'The function returned {actual_result}, but the tests '
f'expected {expected} as the preparation time.')
self.assertEqual(actual_result, expected, msg=failure_msg)
@pytest.mark.task(taskno=4)
def test_elapsed_time_in_minutes(self):
layer_data = (1, 2, 5, 8, 11, 15)
time_data = (3, 7, 8, 4, 15, 20)
result_data = [5, 11, 18, 20, 37, 50]
for variant, (layers, time, expected) in enumerate(zip(layer_data, time_data, result_data), start=1):
with self.subTest(f'variation #{variant}', layers=layers, time=time, expected=expected):
actual_result = elapsed_time_in_minutes(layers, time)
failure_msg = (f'Called elapsed_time_in_minutes({layers}, {time}). '
f'The function returned {actual_result}, but the tests '
f'expected {expected} as the elapsed time.')
self.assertEqual(actual_result, expected, msg=failure_msg)
@pytest.mark.task(taskno=5)
def test_docstrings_were_written(self):
"""Validate function.__doc__ exists for each function.
Check the attribute dictionary of each listed function
for the presence of a __doc__ key.
:return: unexpectedly None error when __doc__ key is missing.
"""
functions = [bake_time_remaining, preparation_time_in_minutes, elapsed_time_in_minutes]
for variant, function in enumerate(functions, start=1):
with self.subTest(f'variation #{variant}', function=function):
actual_result = function.__doc__
failure_msg = (f'Called {function.__name__}.__doc__. {actual_result} was returned, '
f'but the tests expected a docstring for the {function.__name__} function.')
# Check that the __doc__ key is populated for the function.
self.assertIsNotNone(actual_result, msg=failure_msg)