From 81a7c228d967c577d1b01df41c4e277f2580f11a Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 20 Sep 2021 15:05:25 +0200 Subject: [PATCH 001/128] Update TESTS.md --- docs/TESTS.md | 136 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 54 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index c4754b24f3..9a892bd1ec 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -1,109 +1,129 @@ # Tests -## Tools to install +## Making sure -We recommend you install [pytest](http://pytest.org/en/latest/) with the following plugins: +The python track on Exercism.org uses Python 3.8 and above, make sure you are running Python 3.8 and above going to your terminal and typing: -- [pytest-cache](http://pythonhosted.org/pytest-cache/) -- [pytest-subtests](https://github.com/pytest-dev/pytest-subtests) -- [pytest-pylint](https://github.com/carsongee/pytest-pylint) +```bash +$ python3 --version +Python 3.8.1 +``` -The PyTest [Getting Started Guide](https://docs.pytest.org/en/latest/getting-started.html) has quick general instructions, although they do not cover installing the plugins. -Continue reading below for plugin installation. +If your version is not `3.8.x` and above, update your Python installation by selecting a compatible version on the [Python downloads](https://www.python.org/downloads/) page. -We also recommend [pylint](https://pylint.pycqa.org/en/latest/user_guide/), as it is part of our automated feedback on the website, and can be a very useful (if noisy!) code analysis tool. +## Pytest -Pylint can be a bit much, so this [tutorial](https://pylint.pycqa.org/en/latest/tutorial.html) can be helpful for getting started, as can this overview of [Code Quality: Tools and Best Practices](https://realpython.com/python-code-quality/) from Real Python. +_Official Pytest documentation can be found on the [Pytest Wiki](https://pytest.org/en/latest/) page._ -Finally, [this site](https://pycodequ.al/docs/pylint-messages.html) is a great place to look up more human-readable descriptions of Pylint linting messages. +Pytest let's you test your solutions using our provided tests, it is what we use to validate your solutions on the website. +### Installing -### Installing `pytest` +Pytest can be installed and updated using the built-in Python utility `pip`. -To install `pytest`, run the following command in the environment (_active `venv`, `conda env`, `docker container`, `vm` or other project space with Python 3.8 installed_): +#### Windows -```bash -pip3 install pytest pytest-cache pytest-subtests pytest-pylint +```powershell +PS C:\Users\foobar> python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint +Successfully installed pytest-6.2.5 ... ``` -If you get a `command not found` response from your system, you can find a -tutorial on how to install `pip` -[here](https://pip.pypa.io/en/stable/installing/). +#### Linux / MacOS + +```bash +$ sudo python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint +Successfully installed pytest-6.2.5 ... + +``` -Once the installation has completed, you can check what the default version of `pytest` is by running the following: +To check if the installation was succesful: ```bash -pytest --version +$ python3 -m pytest --version +pytest 6.2.5 ``` -## Testing +If you do not want to precede every command with `python3 -m` please refer to [adding to PATH](#adding-to-path) at the end of this document. -### Run All Tests for an Exercise +### Running the tests -To run all tests for a specific exercise (_we will take the `bob.py` exercise as -an example here_), navigate to the directory where that exercise has been -downloaded and run the following in the terminal: +To run your test, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with the path_). ```bash -pytest bob_test.py +$ cd {exercise-folder-location} ``` -**Note:** To run the tests you need to pass the name of the testsuite file to -`pytest` (_generally, this will be the file ending with `_test.py`_), **NOT** the file that was -created to solve the exercsim problem (which is the _solution implementation_). -`PyTest` needs the test definitions in the `_test.py` file in order to know how to call, run, and exercise the _solution implementation_ code. -Since there are no test cases defined in the _solution implementation_, `pytest` will just return a positive result, specifying that it found and ran zero tests. Like this: - +The file you'll want always ends with `_test.py`, this file contains the tests for your solution, and are the same tests run on the website. Now run the following command in your terminal, replacing `{exercise_test.py}` with the location/name of the the testing file: +```bash +$ python3 -m pytest {exercise_test.py} +==================== 7 passed in 0.08s ==================== ``` -============================= bob.py ============================== ---------------------------------------------------------------------- +#### Failures -Ran 0 tests in 0.000s +When your code returns an incorrect or unexpected value, pytest returns all the failed tests and the returned and expected values of each. Look at the following failed test file: -OK +```bash +$ python3 -m pytest {exercise_test.py} +=================== FAILURES ==================== +______________ name_of_failed_test ______________ +# Test code inside of {exercise_test.py} that failed. +... +E TypeOfError: ReturnedValue != ExpectedValue + +exercise_test.py:{line_of_failed_test}: TypeOfError +============ short test summary info ============ +FAILED exercise_test.py::ExerciseTest::name_of_failed_test +========== 1 failed, 2 passed in 0.13s ========== ``` +### Extra arguments -### More `pytest` Examples +If you really want to be specific about what Pytest returns on your screen, here are some handy arguments that will make Pytest return a more specific dataset. -Below are some additional examples and details for getting up and running quickly with Pytest. -[How to invoke pytest](https://docs.pytest.org/en/latest/how-to/usage.html#usage) and [pytest command-line flags](https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags) offer full details on all of pytests run options. +#### Stop After First Failure [`-x`] - -#### Stop After First Failure -The above will run all the tests, whether they fail or not. If you'd rather stop -the process and exit on the first failure, run: +Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests when it encounters a failed test. This will help when you want to debug a single failure at a time. ```bash -pytest -x bob_test.py +$ python -m pytest -x example_test.py +=================== FAILURES ==================== +_______________ example_test_foo ________________ +... +... +============ short test summary info ============ +FAILED example_test.py::ExampleTest::example_test_foo +!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!! +========== 1 failed, 5 passed in 0.28s ========== ``` -#### Failed Tests First +#### Failed Tests First [`--ff`] -`pytest-cache` remembers which tests failed, and can run those tests first. +`pytest-cache` remembers which tests failed last time you ran `pytest`, running `pytest --ff {exercise_test.py}` will run those previously failed tests first, then it will continue with the rest of the tests. This might speed up your testing if you are making a lot of smaller fixes. ```bash -pytest --ff bob_test.py +$ python -m pytest --ff bob_test.py ``` -#### Running All Tests for All Exercises +#### Recommended Workflow + +We recommend using the following commands to make your debugging easier and (possibly) faster: + +First change your working directory to the directory of the exercise you want to test: ```bash -cd exercism/python/ -pytest +cd path/to/exercise ``` -## Recommended Workflow - -Try this command while working on exercises: +Then, run the tests together with the previously explained arguments `-x` and`--ff`: ```bash -cd exercism/python/bob -pytest -x --ff bob_test.py +pytest -x -ff bob_test.py ``` +This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you run the test again, `pytest` will first test that failed test, then continue with the rest. + ## PDB Typing pdb on the command line will drop you into the python debugger when a test fails. @@ -113,3 +133,11 @@ To learn how to usepdb, check out the ```bash pytest --pdb bob_test.py ``` + +## Additional + +Here is some additional information, which could come in handy. + +### Adding to PATH + +TODO From 6548ec9297cacd42ca21020319ee7b0ac03b1375 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 21 Sep 2021 10:14:00 +0200 Subject: [PATCH 002/128] Update TESTS.md --- docs/TESTS.md | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 9a892bd1ec..88dbb38611 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -1,15 +1,17 @@ # Tests -## Making sure - -The python track on Exercism.org uses Python 3.8 and above, make sure you are running Python 3.8 and above going to your terminal and typing: - -```bash -$ python3 --version -Python 3.8.1 -``` - -If your version is not `3.8.x` and above, update your Python installation by selecting a compatible version on the [Python downloads](https://www.python.org/downloads/) page. +1. [Pytest](#pytest) + - [Installation](#installing-pytest) + - [Running the tests](#running-the-tests) + - [Failing tests](#failures) + - [Extra arguments](#extra-arguments) + - [Stop test after first failure](#stop-after-first-failure-[-x]) + - [Failed Tests First](#failed-tests-first-[--ff]) + - [Recommended setup](#recommended-workflow) +2. [Tools for your IDE](#extending-your-ide) +3. [Additional testing tools](#additional-testing) TODO + - [Python Debugger in Pytest](#PDB) TODO + - [Adding python scripts to path](#adding-to-path) TODO ## Pytest @@ -17,7 +19,7 @@ _Official Pytest documentation can be found on the [Pytest Wiki](https://pytest. Pytest let's you test your solutions using our provided tests, it is what we use to validate your solutions on the website. -### Installing +### Installing Pytest Pytest can be installed and updated using the built-in Python utility `pip`. @@ -124,20 +126,26 @@ pytest -x -ff bob_test.py This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you run the test again, `pytest` will first test that failed test, then continue with the rest. -## PDB +## Extending your IDE + +If you'd like to extend your IDE with some tools that will help you with testing/improving your code, check [this]() page. We go into multiple IDEs and editors and some handy extensions. + +## Additional testing + +Here is some additional information, which could come in handy. + +### PDB + +TODO Typing pdb on the command line will drop you into the python debugger when a test fails. -To learn how to usepdb, check out the +To learn how to use pdb, check out the [documentation](https://docs.python.org/3/library/pdb.html#debugger-commands). ```bash pytest --pdb bob_test.py ``` -## Additional - -Here is some additional information, which could come in handy. - ### Adding to PATH TODO From 2b5182955a22a5d9d789edb00f8e310f15824266 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Wed, 22 Sep 2021 18:31:17 +0200 Subject: [PATCH 003/128] Update TESTS.md --- docs/TESTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/TESTS.md b/docs/TESTS.md index 88dbb38611..90b86b5f09 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -149,3 +149,4 @@ pytest --pdb bob_test.py ### Adding to PATH TODO + From d447645340c556310c981e73643761b1fd80b145 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Fri, 24 Sep 2021 11:43:40 +0200 Subject: [PATCH 004/128] Start of TOOLS.md --- docs/TOOLS.md | 55 +++++++++++------- docs/img/AddFiles.png | Bin 22264 -> 0 bytes docs/img/SolutionExplorer.png | Bin 8643 -> 0 bytes docs/img/TestExplorer.png | Bin 25961 -> 0 bytes docs/img/VSCode-EXT-Python-Header.png | Bin 0 -> 33457 bytes .../VSCode-EXT-Python-SelectInterpreter-2.png | Bin 0 -> 33976 bytes ...VSCode-EXT-Python-TestingConfiguration.png | Bin 0 -> 17203 bytes docs/img/VSCode-EXT-Python-TestsStatuses.png | Bin 0 -> 13707 bytes 8 files changed, 33 insertions(+), 22 deletions(-) delete mode 100644 docs/img/AddFiles.png delete mode 100644 docs/img/SolutionExplorer.png delete mode 100644 docs/img/TestExplorer.png create mode 100644 docs/img/VSCode-EXT-Python-Header.png create mode 100644 docs/img/VSCode-EXT-Python-SelectInterpreter-2.png create mode 100644 docs/img/VSCode-EXT-Python-TestingConfiguration.png create mode 100644 docs/img/VSCode-EXT-Python-TestsStatuses.png diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 1f99c7ec62..ae38a024ed 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -1,35 +1,46 @@ # Tools -## Visual Studio on Windows +IMPORTANT TODO: UPDATE URL PATHS OF IMAGES -Follow the installation instructions for [Python Tools for Visual Studio](https://pytools.codeplex.com/wikipage?title=PTVS%20Installation) +Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/python/installation). -You can either start by creating your own project for working with the Exercism problems or you can download a Visual Studio solution that is already set up. +## Visual Studio Code -### Exercism.io Visual Studio Template +Visual studio code (VS Code) is a code editor created by Microsoft. It is not specialized to work for a specific programming language, but to be an editor that can do everything. You can extend the editor using extensions, but it comes with some great extensions as well. -This is a Visual Studio template that comes pre-configured to work on the problems in as many languages as Visual Studio supports. +### Python for VS Code -![Solution Explorer](https://raw.githubusercontent.com/exercism/python/main/docs/img/SolutionExplorer.png) +_Extension-id: ms-python.python_ -1. Download the [Exercism.io Visual Studio Template](https://github.com/rprouse/Exercism.VisualStudio) from GitHub by clicking the Download Zip button on the page. -2. Unzip the template into your exercises directory, for example `C:\src\exercises` -2. Install the [Exercism CLI](http://exercism.io/cli) -3. Open a command prompt to your exercise directory -4. Add your API key to exercism `exercism configure --key=YOUR_API_KEY` -5. Configure your source directory in exercism `exercism configure --dir=C:\src\exercises` -6. [Fetch your first exercise](http://exercism.io/languages/python) `exercism fetch python hello-world` -7. Open the Exercism solution in Visual Studio -8. Expand the Exercism.python project -9. Click on **Show All Files** in Solution Explorer (See below) -10. The exercise you just fetched will appear greyed out. Right click on the folder and **Include In Project** -11. Get coding... +![Python Extension Header on VS Code](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-Header.png) -![Add files](https://raw.githubusercontent.com/exercism/python/main/docs/img/AddFiles.png) +The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). + +#### Selecting the interpreter + +The Python extensions supports the switching multiple `interpreters`, this way you can use different Python versions for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](). + +![Interpreter selection]() + +Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. + +![Interpreter selection PT2](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-SelectInterpreter-2.png) + +Here, click on the Python installation you want to use and you can start coding! + +#### Included testing + +The Python extension comes with a `Testing` tab on your side-bar. This can be really useful to test your solutions with. + +![Python Testing Tab](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-TestingConfiguration.png) + +Configuration is easy, click on the `Configure Python Tests` button. A window will pop up asking you to select a _testing framework_, select `pytest` (find more about the installation of Pytest [here](/TESTS.md)) and then select the directory where the extension can find the tests. If the tests are in your current directory, select `root`. + +![Tests Statuses](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-TestsStatuses.png) + +It will now show a collapsible tree of all the directories containing tests, it even shows you every test per file. Hovering over a directory or file will show a play button that, when pressed, runs all the tests inside of it. Pressing the play button on a single test will only run that specific test, handy for when you're trying to fix a specific problem. -To run the tests, you can do so at the command line, or within Visual Studio. -![Test Explorer](https://raw.githubusercontent.com/exercism/python/main/docs/img/TestExplorer.png) ## Code Style and Linting @@ -41,4 +52,4 @@ It can be pretty picky though, so take its results with a grain of salt. If you don't agree with one of its points, that's a good topic for a discussion in the comments for your program! If you'd rather have a tool take care of your style issues, take a look at [autopep8](https://github.com/hhatto/autopep8)! -Run `autopep8 -d mycode.py` to get a diff of the changes it proposes and `autopep8 -i mycode.py` to format the code inplace! +Run `autopep8 -d mycode.py` to get a diff of the changes it proposes and `autopep8 -i mycode.py` to format the code in place! diff --git a/docs/img/AddFiles.png b/docs/img/AddFiles.png deleted file mode 100644 index 03e7a9163c7b65de67ca7d40e774dfecca42c458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22264 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 diff --git a/docs/img/TestExplorer.png b/docs/img/TestExplorer.png deleted file mode 100644 index 1cdc5e84aed29449496d0de02e56145cfdbb6983..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25961 zcmb@tbwFF~w(g4r2vP{{5TsZs+TzmSv}mCPiaW*KH8>QfKym3;pisQQ9U82-yF-EC zt|#rc)|PwM*>~@I&L2o7Gjo#U9b-J>H=Z|AO-23@4iydx3d$oz1)0|!NS$U*x4LK-r3yT-o?tn)!+>f`4qEdMHz^urvakb$CVV`eDKL_Y9O<5 zKl4+y#eTER=0JLe%f49|V%g=0i9)oAHY3#zPZ8+fr2BsBW!o)iLD%X(sYL+ zZr3Fpm;mZP!&X$_1EQOp@GP9`}g8TW5I-e|qiy4Zh;SoFTTzvM(L zyt&)md3)D#RiCkGp)8B9edMC))bA848rFLBPD~L9&{FDwrE96e6{mbt7Syx$VM&sPJ#Q(1*cn<#NKSa6fl#72(a6RAnK&M;lN_ZEN*!&z7+w`mDCy@xY^VSlzZx(T z@s)}4oIoRN)nsN=-lLvt@v^4>FTP1kH%-CGPQKO?pe;WH5G*yU&U@IYxfCL%yu8Z1 zq;bfE4PB;x8RtVQZAZ_l31o|Y0z4njKY<#F#N`V3D4r|GEHz$SXvRkX9X-v!@)%$2 zSQ2^6f%wJhB=d)JL-n_Pj-nzQ5B;@@aLr5|nCTR;)T-{#w_S09W;)2{6d4SLo}=GG{aqagH@wpKg_ z0SSymq#=WefnZni9>vHObZjX-1rU&t1NjXKI(-2F|MT~FFbKc^Mn0B$@q0bx%HyI5 zWP>Bt8E>@Xh(qY_wpE~D-{CBAysEeaFi3`5%Hy1;M?wyHZZmLUrW1Tafn0p2CCbW7PDU2@HIm5@X`itQQxG`cRnB<30wSot>d+cztdAu}dBZ_7uE==7kN3~1ZrdkhWV?3n#-L%Z1_{Y6A^jWDg+olxv~pQaqB7lR4i z^?pd=2G3$B8~tMG0W5Ygk0%sxFE-cWd-d|Kmi;~Bc)wB&sL@v@rc_q+-&gyfK4z51 zi8gsrNSD}`gpZAuAmW%F7#n8-c6c}|FRVboMC{+z0MSl{@h@?QXFc(Ak2B}2v}j5f*nl*5&#?==X`}Z0tg-Ro1uVv(47DEDg8&p zeD9JM>nk88R_&2963QSQ_T*-J zFjfl0j2W?Ks?P%K;U5xvLPIQ>vD0I;wvi;ryTkruT^@enVr4EWhsq3;xsWDD&lI5% z_fGcv@g7YMvw;HKP#q3AujI<%%LRoHq64VQ4Fl+7hnb!lcCVsHvM7*0gMa&xq25c{?n0NCu9J~I4Ag(%Ky&2y`lf_Gz9GkR0NW_hVmpmCXHu zTKw;2c$k5Hctilk2z=a*hZ`gG+l9HMjtikDv7G_D4G1e zqxH}4J{fi_Ngin&NH}>B-8J3ctOme%nTXXWqW-G^pz* z?_lW*0ap*3OA84U>VNqXF$F&08askoZ;)A4nVQ zevqjANPZ>2Rc3&T2?At{Bt-KVre4NMnjqA_w+~pf60RhU!ccmCNbwMaC=~T0mjmJs zbv!%=_Gd#M*4%{O#kY#)1n6Kqv||g3@=sizl?ObsnJRk@HUHvw8zBYKUSH1Kx>$tK zD#fyNwUksUxf9);7}8L?d)ik$nYAEfepz?$(AVc$c+dpy(TI9bj3Av|wQIJR$tlPj zx|sexyqgxv`03$5MsZX;#36(Nd<0nWad5$svwVBurcSH>{nJZ`i(sO+eqJXGbw>Iw^K&qhIK9Rxk zV%5mOpd4kl)r1Ogk09ye@He#5bXW7<_O$eb@sgr&XL-F0nM{S;=;&j83IBX(;LXc| z7Iaa0G0X=}+a7pT3-#`0(uW6+eCGFkpl9hGN+8EZ|CNQF{OB)uLZc_@t+)Wx_Slh6 zgUIVsNLh5bxw~?(-+s&@xPW_!J@lWS5dz>`I4CN_bz`nFpbERimQLE%YG^J@3oaqh zO1F7!P3snrHF)1j{J2<<^kI;)ePamL>7S7cPRpX%olQ_6$An6m@pyF@OacK8CdV{KysK%~p-Qg!I~N^EJ98;GZ<2?aoEAT##v&ZRc;^vAr! zq=38IfjYZ$VadN@8=6ZL)4@ft4gs454Y$?CY>>m-uV6dNA!gT)VC|mRAqml~`Z~gU z2BnbUIY9f>fc$D}&RkK*u5FiAHpU?#fHm{2ybCUi(5wJf0l0@SswL}tfeX~ARwr9v z`lkA1eeUxK>D)H~zokMIkNi&Pu!f=%$eX@c;$dHeh{)nP6AUVrX>5Jr>VvFQ3d?y_ z9Q3971gG3rhH>j&RQy@>C%(v3TL!s>+vDX3gH;2 z-6&a486XOwz#bv!u%5)2-XfDWdU)CP;5}Y)cB4gBMkB3kLH)IPo$NS6MAxr)a4v=K z{ngfjA;=s3EwiJ6hb!zG8kJdI8(UsImBD^kq-bQebO%&TTN_BuZBQe9G>C&n@axK5 zylZQT)Yj#cE2XaromYxe z@%Lve_@N<+x5no87F270>bPvlt=m1u;tCySD?s*c<{DKpz9?a zbd7nw*hDzCz-Bc!3PptAVW6M0j^YxCkAo%|E8(}yXgs%yaFq$fOG@Jp?QzWA^`g2GNgbr3FPOM;46jQ(GeQ0EsqpKd^Io_PRr$L zG&3%AcbpD>Jg%w+nikkUEJI{70ohQjbPHvHnAm6|I>;wg7~>G( zxqr1~G@RS+xE3SWE*V!hXB7^P*j9){uC^wz19P@g_7K_c(MF>wiaczVpj#_M+;UlI zqtWe;Bc5_+I+AhHwhnV#eud!W}%!x{yQ&rl0k_I+phH?$%9_v|&5||HV^e4%#Lo0l{oqI$w%|GDn z4>TlULw2>kshz?Z>jFCON*AGixJGV+R0Yiyu5uvT!d5^`<=8N%sJKX;BNs{FE63I5 z@X{fer(=fg188uTeHFW7`?=$G{^NHsyg~~~&|%YGsi;2OHPX5oc(Knf&>%vd+hDP# zGvhc4u;jw6#`ImU{i`dkw)tikW24CzFu&U`M{0Unwl(A_gYrO_X+JdgfVFSrilk7w zGD7GjkdlGN7!287%pDVa-u6y|5^MJq6Jpgok0UvFUn!5rBXX%faj-l(m1+e4MC-T| zc9UE+`{P}(xDPX|(;0V$A(Jq{u2$~oSftl0xDQPY12t}8PX?euyPr(A^=$lO!kj6? z?CNxHWeA)rNmf?E;|U*oITK|2XdaK!hx>(3TJZ}S$aYQuP1*Bxse7kJRec+68&!Vo zfcL>C=0kICSrMK4@>99S->g}kAyyAx^@Xkq=IBB{MB$~?xFts5r_ZIY{MDDb9P8!* zguK8PpnW#$l8`l+-MWir6hOd{gz$<~0-2eG%Zg4)M%6xIBJ$5clWauWjzG3-aTv7P zPUD^-Ai&CU>{xBq(xV|=b0vzh8N!ICG&V5;@!L!xufFjxIsh{8#q_VAJ^TK=eNYk+ zg7F-8dFQM|ZB`q{6z2z<3C}YRw(;lwRyFXfA8&==UKL}4Uk4dFZgTnFXGF4L1PRv( z5wH>sBO(s0%GI-=p0U4>H@^su8;wn(+T9L`0@W2PnaJXdZZAFP3+>bN$5P#fvN=Ph zHPrFPJd`bC@0WjI<=sn(`<_jeFOP9cnvbBWP@ist?R=pV!>ecoSFc zOp(;ZyD2qrEEw6a>0a(HwFEC%dUW<&$Z?nio*l=ox0ie>_qNSLZ}2$SC`R$$^tk=T z=Za?Dx2&}}E0Bka<|4(QP^B*#vL!I#zEkA(>1Q1wl#3DeD4p*Ve?5Ltb5P+ccslJ1 zK?1JS!+q@w9dk;vNi)81`ey%UoC~caixRg2pHn|~27cceN~rF$fT+6>iXFBI1&iwf zJkw~S6`2HFI12}y-);ObR?|VDgH*hLujjRxbP`hee|UkFJU2!ZtUV7zZSCJu5hT5# zQ6vJC!f(po_Yu{D=N&s^_8yIdtI`?g2VM~I;JjG3BtEwgQBhGDwL_S=W1?Mw8hB}`7d_Zee zc@;dG{(9QemPv^{y@QNr1zjiCxlRh(Y3XF>Sck)mf|7BwY4HFfN4Ax zD+_C6BX{iLV`kLTobPS%^9*#nZExbL`z@=P*?oo($XMj=Os&(*DHE9P#9w?POzOrX zb*Ku3<{n1Cq8t~Uj`N@9EB9?b<&Ll}4nGC}AP(FQ68wi^5oHAgTbf$?~MuAT$Mp4-*Z@h;D>{B@6Xt=>BXeLZ<|SlyAs zZW2h?abJBCbAI}`d!V=CL_S+1p#WP6G@!Tun?+;9rJPJ^(cN-?C!B3Cg$L8{ql~6% zjt9H)U$W71ze-dp0y6kRlevb9FOBc~k8o-uby+%!hg2cEQ{O<16v5Gk`XQn8T&FTv zo&!|+j++V!u&fE zw%=WwsK{PvVfs?MKf3ScEM$B=rio50psdyv_|RA3Ibjm_baF(tFh$<_mNRs6u!p8~ z;JclahrsP3g^6Ssqrvv{ZJ}fUWLP$22AIjGjeH%kcmruN!P^q2#hFpZF2L=*ONp>Y ztO9N4lJ=GdR?7}HtA|@4I`8RI8t7BB^N7^z%qR+-h>^+m2MOc>ckwIM0Tf6@3;)R4 zx$f$`=J*$8fkj{#Adftr8PevRUrr&z=nNUo(d-)NFIc4R_IF;7<38yL%+_R%D0}P- zdC{=-4$}*;71C5t9?vNX`MgSjgolt9tKZ!9ntXZ1g#4Q(fIi;~dNCoOkwiWuvdF)Y z{#WD~nIe2)Z3jPq~Pi$bt>P$0^W|rQa`Y$efKk~RRDX_&FcXh~UX{>}%;_L#Hl234y!L|~{PBsbE$?tHY-N~<=FI(z@un32{Rm$r6o{DP>}Z9XABj3xxW=uvsp1dA=Vrlad7<@w2#j zB~ENSNs*ph;XIG-h`1%J*zXZZQ9W{4x_cyXeagf_R7GLbjB!KO_H}QB5UA)e=AUQ#2Gc&x-nv$G%64$u$6lxp*81EZB|qEtLTMHU4qzWXN=QRY&tPi;wW zjw;oi3^wAO+Y&D4c`+-#rEPaCL{Vtl=A5&SgmBu21TJlyLLqyprR$oz-JGp^#wALIu1`HGL{K zbmG*$1Qb@s#U<3&PIyot*!(x$ya@kyYWdIE5(M~~1DLFlfYbu)gaD-i0pCl#9=P7FEGesHsZgQ zqI@_RRBNQY&vR5%70Dnhy*$yUWh~t*S9h_PAm{UsHcXXuGN;7ShC+$)uq&2zxh)i3 zZB7$6&LJREe6&dzMrn|FkvL+Y4SoA8Ob0RD?934+tJ_2RxGz19gtnSs%*L6+o_iMG zrGJYGi7Fm`N;`B2F8QM$(0GN*0_*%{2j5q;d~QRQd0O%i-UaSL6G5}nbR4!-+?F(< z;p068$Gw6>sue;`T=f#BU#&sq=R*gcWCHh^AEc_!$#N${Zf}pw31Z-@n za7f{SjW8*pQcRdS>U}pmVDoj;J;JTWzQ;n4%u9S7yrJpp1Ym*_@xcftKgLog=XSgL z-eo2qcyhj7V%&4>6@I&}QDPuZkF2kVew#gV45g%VrC7Ctr|p?hIhi+r zA9ghGOqyyI{bqks6+eHRNqR#9!-q2)7Ld0qkN2Hbk(m1Xn!*o8^NJNG7n_+;@9Jm$ zh4n7vfQxHb*l3dE_T2bZCyWbfN)!?=sfm&=i8!Ppa=?G}yKro7Zgi|Rbn_S?P}<0w z$g(-igh^Yk_Nj$NygP_#!IVCHU>l8;DLRp`_0Ju=c=)7Wm1bEAM3q2RPuR>ZL_Y7e z;V&qF^fKOtKO&M@kVp{n<1Jk6GQ36Gktj1IU^Y&UH%J^b75!RlnP%%?k{h%X> zi9_HJ^o#$CkF7Br5iLxb^b|Od?dX1UY40Ocw-9h0|JFO5F_?Lz(3xYEIK2NYXYg70 zWM_yn@iaG372%6xk^Iw$u{{)pDWVd?)Yu&H(v$z->A7pc6rH=L;Io4#$SZOI^+Gb& z1b?WA5<;8A*&GVq#jW`$M-C?aj`7t13s7Va25Ngq@qt+@y6|)1L%m-vj)v`ioT-dB z?QNw&Jjcxm@hwcWZ?($9_8!gf*v|{~6)S-BU%UjMk_+as4P-pvtlJL-zu72RCior@ z_}J~$^wP(VPZ&ysa2K_2F+b3Wi^%R$C>7|nJ>nB4S_Z`I&@zz16+i;YW3%h@!XWW1 zWDmeWu$7u0G~GF@O!B(9Dl?SGzH zh*L=WbA$iqhB|*WP5PW^yi0JCc=(;FiLl_i97`!L{s+z}hHENXG5`ts-a0Z8IAmUY zDWOdm>qhiMdS&@pc=6jLO~>XZ>^u;RAh!Pl5NyEJ=K#KCUasFihdIl#@w~JOi~8g& zo?ngx$RuQB9o^CU`ME0Ce{kS^R`}&@qckVhMRn)g`MHISO!Y&GDQmWlacK8D{hx$R zpVv1K6jIT4^C{AtEXqzaH`8>FB>MW!KiF*|Dri)aBW?0j3+LB<*+p+0?poO;omZAe z6L{M0dM|?Q)(C^?gqN?LX$=r9MYhOGL35)~M^}7+v15&SC2QO1LPd;$DsahHv2ERL z@{VetOQ%!y^ym<~=-M%-m;3W!J|HO02#B~~xf+c? z*CQI9?q|}H0z3?3zY~-Xt;AA+fvXgyEgSKp{`XLDfj(ANU=HycNZUz)A?39QJ90KA zECdCS5K;^?p)#G)pczA;!v))E;ItggB6eifFhXZ~N+sl)fHY~~KmFMHd@7p4m*2U1 zDI@+!dGI(RLUZjbI+-Mn5;O3Wt56pt0GrHKmIv_Zwe5>h^+vD5V!rW;*A%O>b;#~o^p8c{ zVLA&AkUZ#mi$2`uQGI;C{C>_9)#U6L-X#6pvhU1^yFTk`#m%qhR@x=A$i`~6)tX%8 zej6swP{4@JVwc67AG`^(@GL%lTxYsKD+P)VR>l&CNx8Ykj^{Z^;&~zP%fptA9OK>} z6b|Ms)WbwrYj=+i{8b9FRRcBLO(8%x&JJ{uiq)bcAOoO-E2Og0JroJg%Zm9x<>h7i$7oKH0qaNG}lH~&WaP%2pm;D+Tp`X75j~f<+m)~EL`d)7oa*DLQ z52mC2Y^NIhbVIl4e7YRh-{q|?UdWo_iGA}JriTd$x<>;8O(%a`Dq>mUT_bZi4l8ic zJHKgeQ5xvQ*?}kW_4)E)!n}4kvQ}60XBEHHQld0SyF%0Sd)=K$ckp=mba^h|j;!UT z_Q2?3X5n|KM(V2OCMK^#>&+55pJ}HFACwf(_=N7#8~Gox-jz&*P!^Fr&jF`r?N0eX zufAm!*1ZPbzGV-wA$Ql;PWH!FMR1EP7U61@D{{>~w&s*kUywATz@5~;hi8v@vy{4VVjxD~@uZ6Ht~s=HY5St;9VoPGp9U3Hyy_odzwtsod3Whqp`TPI8)*10SRSyx0W z!0bGE#4*WMwt%OMzxf_xq*^f&>}cP7CeYK<6PTXE(gtreKsd6!G82@zf#5=&qsBHPG%^J?u)2W zO0{v;Sd}WMMsn_yu2fAA_-7e_j3TcYfr3l7+=sh0&)B}PhsMAKI+F9*ZBdCO;-9hk zfcjGp&*dFD%dYi#d5{=?Yl?y}q;;|=_)(fR(&UZl*uYd)UD_cWyVi;5*_ zci6P3uq0DD2SOuak)xx-Wd^m%R%m1p(r}pgK%C;h`;G0Vzx4=F=*8}0csRY+v{hVQ zDSO1>uMQzVS>!PN?wMGz6}PrCxeUXxH?Z{tM?GAh{zM~W+M*J_>ty|NHCyz@xtr?i zQ%~pA-@Q)@Ux0+IX#GzLN<2M5(5B`<&=(tJHK#H(e}hXGGWJrj3Zj%!18Za=w{n9h zAM_U2Vf2L(--uDsT?1xNo ze{{g&YAc^vr}1DcjRelo&t8LUOJ*%9!tsaw-cE&*0dk&W1>J-@%Sh9@mwK}itaUk4 zpGC+q)@8{QBAXbR{!NNW9IqPs3d) z2unb$E%B@Uo#&HR?b$jRz0O?7UL60tJ~N8LTF61JyZ|<#3erbvG)&Bn0TPkjlBS`4{}Gj5?p;#=Z+~&b__K*NWb;G9yWGmh zY49Dn#A2)mdmJ39GxJi{HMN;a(${L^zPS^@QUB4WLNz)V1@j#E}r-jm~^3%SZ1nya;b zF~03)RgUYJX@<&>qOz7X{2AmYmt`wDQh+=+e!n55{n;$$P`xN>Y|C{74UtEPU@F#{ z9Qo$4(c$#zXOks{$I!qoT|}Mdm5Hb0t!D}0xwU-KBP4=&{9l8SOWUBOj0`^hr9h-8 zj%-+>mo%73Mb2%InQ3__0=soC!QY!XBKfzrqv&x6IPWi>Zd|%o0*MFrVB`fn=;oGI zefS;Jj(@EkHm%^@ij~-sk-Q;qM_>Cvi68_J(k9?`;PM*=epUVYApy0_FzvqGRmkNE zZ@hNAlY8mmaY?ENGl79_O&c=&Ir*Xvoz{c^T-PJy&lKahs^LLbYopj=gQa5Dh#R5G zrk<#BgD$(#MEiXBh)rT?3W7cOSK}9?zNr^|uRdwh@8b z)9jc8CbU&IW}A<+2c@7NPM^bND7k#U3aJ}cgfr=1D2@}p1M%eZGV7d;zd~876!*ei z-<@bKqh$zES<*@@JnEa0M^nB#sR{H@d=lW;gAFX68;Scre)65iteq*5-!N}4Ks%SuBMx%Id} zH~Gm*->86_eTT!5LVDhW_->Tlr_;44Z_UfkXK`uN?RY`4tUn8pQSU0(i(Q}OwYgPA zJ1=jeg)|3`^ZGG8Qdt%U5QW&w8!AMt&K%h zft6 zMWGlx|5BI~J6|`62-5bGtBLMDW&}q3C}% zVs%swxi6|X{hZ&l*}<*zDoupzTmVaV7cH#9KE-@y7gAbc|f-4ygtwA2KO`VK@c?r9%XWE9ZjcP1KqZ$+nFE%vsG~l7p z;UH8JCA(sQk)ORHIChf{huWN&c2VFa6rXg?1S<*5$}a|B;D60pF?|+nx(%!#z;_P0 z@;(VxvFub<84p^D+c+Yp zH*X&iSPXAIM@n`ZLj%)RHly?!%a=JpP1PoysY{n0{H5^lCNe z=4?7M3gY(J<&=gg+mmP^Nd+jC;phneRZm+p(?o8u)D_xEgliX|vJO4$tylf+8ye{4 zwHZbBSu5jPg+_NrXNPBotrlo`@>m5^abbPL9}Rq*MC-*~>M95xnK49ib2>8{Ayr2B zrn6aIlq{bM$1GrSWS=-5d`cuj%HSwJwX^z2Q67}$SEAZT82oY_8lRDh8@)@0^gf`Y z7#_a!{2ITZdAr%@uq?BSjU4)z?ezwhD(e{d1TlRTIDUnnl!h#B3iar9rtk41>f9aa zzv-hwLB75jo^HcmK%Cfw?Z{gx<&yylC2pPb? zgPBm4O4iV^*rvDnLp-gwGHup?4$7KTJ zlEsA!P0~d6(zw2YbHO9!c;bl>7^u5P)G>%Z$^xX6cCB(Q8n2OrKJ-xn>w-FC?WBl2}@wZ955W{%ANBsu^067&Cc?_JH5w0-neQo;bC0ADsE81Mf6QeJc z_C&prGw`U5^nOv?S_i^@-elbYP6vF>xO69*I4K(q;eq&C)wxGoa~XNQVP{_RB34#z z68&xw5{iF3oTA=R(0ClecqbZIz3-R$CXEo$!ZOkj%iO)D{XK$?*|4`I%KPplF`L@> zN+7q0mY3lG)b8bzA<2V#DCjZ&lid17Tqr4a<_K+;T}j|wm6}oVFimhK_G|Hc)x>|& zTl=^9zv*rLZ!>4AlyjH+*H3DTAC@xmX0CK4#%2d;HOoq5fx^7CP0b6=D3vjD&Jo?8 z*<(%V=Q>AXyLTP81nq3>Q~EBiy!NK;__9s$$-y=mMGjUOT{TrF!58A z6LI|*J!?Hto*0=-=i)p_GBd7G4Tpz%|@Q>%U!*gG7;#+bsL{lzkn(cVU)zvT{&8o;xHx2tS5SN_G zBRy@NeA;ZZ4og8Z~1LyxsZj3 zMxeXAPz1T^>Mw;ye*lh{B>CKiSpMup>y6oqjl-7_1n^cMjW1d6{O)al%9S zatPKH@UszXerQW%`6vs4Kt#@-w?L?d&hqcit*;((KlVCI;@>`0xc_*cQyJ^KA9nZM zuR=Wa_FhXo(4X?7V;({*S=@n3l?>TO9Fz7l>^i3K^40P0s;~gQ8Ge@2CZzoS!^{yYNpcGPsE>Qvfp}POh~i7PqBTOtMPKMlx{A%Qm=YNSW=#xYu$uzAa7!I-fYryak5X(mgx`I z7~Y{6k|&oAUhuqQ-T2=)O#}4)51po`(>VLuRHKqh?v7j_7=H*Y`@HGwO?_+x?Vy{I zfF)VhPbZ=JlRW7a-??dqjX|3^(z8RckTq}7dO!QOeufF1etJ%tj{v` zhe0zF)<;F2t;s!8Y9+yWW-$aKB!W)jrKpN^X#ui1T8{OPCS_MB2zc;VsdM2JwLT=z zN5V2ySz)E6=UKwn!PN92*SKeg6Nar&)z;f&Su7#D;ey~A#QTcBe50r6c2E7Y;AH5$ z$FCl4y8x)7iR<8|6H1XJ8~E_mht+P3SCUv>0Sj-{K1Dej{8($8mNL4;Xd8NeifHgt z-*+2auJ<_jlM5abI) zg{V_JwEdo0*xtP*#4b*X&JNX4r~vXJkpcY-9ZpAO|V~oVc@y({pzs2g<#pW|GkjUFV4)X zJv_|-%}1}^a)urevuMX`5BYz&Ev&qtM1cNo2{Rn-$uHgBQ6*0%cl{Z zSD;E3kJxzSc~ekptaZngCT=N!N8yM?jFI#YH_Z#U|E*Rl^sDI`9-j-_a~Z|}5p~R* zcS=hM__Fd*%YrWa)BLzB|8Ugscpid^rVZP>+L!^7y${&^wP|mYEhN8NMRwe{9dvfa ziP_e`)h0|u`Rugv&a6z z)M>P1_k9mD6=c#9@-Rt*xC$EqZ?a~V!qq1yTb;0^IpiF%%?;gHrJKg8VG=;P#j(5< zDruR}(TD!TxOe9-IUjedF|u!JDzb2|`6|tI&H2~+*^O5tMz6H{qPw3PJ&4|=I~EG+ zm(=L>tZaSF30@L@?7HBCqOu=ikfc%B@Fo2(!>Mi*N?jf@m|s)OU+SG9?3b~a^Y_D( z;YPX>0>RC(v7bkVDUsU&gh`*Hm1Dpim8UDEzTtfw5?|J9)S<`55b-nYEuKEs!Nu#P zT_r@X36?m0cbun*1MtjKT8~N(&mo>7S?8I$|g^Z`jYN*K8KUL9O{+qb^j_i#iE(Qgu!0!Rgp{Lq{97r2RASaX z$GAena5@%X=KnX!b~E#<$;*sVp{$LB#|afO`d@q&8LPB?ul|vc{^(_YuUjqUJ@=~j z+WqSsphWAwd`@36n`C(4VC;eEw4({S?ZcY;c`W8Vj0HacelhyJ2h3g=H9dg1p<{Py_Hu*%y@{&@MjEbfPMirM^6W#02bjtQDl zL*pK^CU&}@(h&p(*pug|C%5vp${0%#9Z|3=$fX4<~&}lkziMdao5pqt9 zbO`JZwo)I*ZFHtC`C|9ubDHAjIQq<Q?$-C3ScMg%R`NUYVtit7Gm4iWuyUS3L|j zheJh^V$ZESc9asLMt`RJnftR7ZmM7j?EQ`Xe1PA6Xjf+1<4vYE)&iDu8+cQ;pSbFTEd z9%>6#$D}KWKmNWsT!qH;3ZF-+Eh8+={ccF>0ncY}Kj_I+d0Tqiq-Q?*z~F6r9$$HL zT|w9yatfx$Iua`Lu;1SM>9AM)YylLP%8e%Yn}vH1V^ z%%xh@@b@;hZhv)(1WB|>S{?W?`#qwuwOu~Pze`4n48640XbDaGWAm&{oEuLoep!sn z>+qLKpo)T*S!trMyx@l4`Gka7+4Vi{jm{5q-*@U{AxKkC`{eaEp2v6C1>%zl zSqV#v7H*s`t9Fu|GCXhBnRO6Wm<2pHl@EliaCZq{psy0iS!whZ4sI z$5CproJ#iy#46xWaZ^*(j}x?p)QG zU(5cl+Ri(i?SJj#RFI&=ro^mKyR987rD*L^qiR!Hduz=GRijkR+M}&j?OlpkrPN5N zJz~`iQN-r?>hFx_I_F%^b$;hMPyWmw-;67{KllBwC)tTqWe-Cnikd4sPA;N`Tc!T!0hb1@jWl%eaj>yLqq}s?L0c z_S{3H_^&2-U>{$XRoctG??a4b~mUZwgz!j z9r)l)30O579pK-DvmNPx)DtPHM#xOS(Z$c63or&w4E`a!pylw@ zg?xE`!i4dbE5Na(+UebMCr$sVGkGF;E=rO_=*`>(?vjg9aXPEDO)A5B4wdaY|A}~R z1>=(d+@M4DhX5EzMi?8e&A-U-yfzEfET;(%g=LHl8zM=}JDW)UZ5=)1_ne%Jr$A?z zw|GvSS25*~K~k79v`o=8VmeFL=Lig=3uE?A%X#d|mS`hxwHx=dv2vD|ZxyxK`D;Hq zt^(u0vZ-e74~QAnqNTtxOiXM1`rCdbb0Mk+*w=4WNcA<|o!bM~`bYeoc*I|+XZHOu z2)Cy%dHlV;iS$Q@x2w0>fo3V8+wjVh26lC@w0E8DSBlAG3{>h0z z#^Ettkzx~D)9KoXCdbaQ<<22m_i^5L$Ns^1FIF`#gLn^td(O{2mC=JD2^^_T{-~OR zDc$xpZY^$g=<$j{7z$?gFpDIVM>C47RT8SHGi{JW2e>GUlRM`2Khk1zF2Xt*^vS-k=!(?Sn+O#U;iUbcdU%~V@ehCcI}3vFC> zeMXd@JWC*!d7wieae0Rt@Oe)MIsrObz?|DylwH~Bz?0OnhZl~BXFp`>w8T^}YhJE{ zT7s_DSPB&tnL%8CHg;sWh>t0!Rz}CG0+;|C0O9`_8>4Pn18~7wM|lVOQG{f#wF45m zd4?`{ZVWABYetW{{CUPYCnL&CJ!6HWt&mnqDv#%q_wE*}83TvDO#E%2#MIS6Zdy$r z8*%vhvlR(g5C^|MArksv+Im`2bRS~UVS@WICuO;_jJ{&pX?bbvSb3!>48{yHi?-F` zF*ab7eZ`dr)Hu{93CsUJbq%Wd2kP3F`~iSgnA5)O!8be)G(8`uV89`D3|AP!W(-=1r^fi2 z$U8+%oS_qX;%T$E^u15OHUROzbo~L?Yam^ryIQ@N`&Dph#ZA@qRgk#Hu)mYMm!^CiZr|P<3UkZ11#DqlE z(^`EVfD@G_ZOD3Av-KPi$13*7L4m?G7LfQGv;LRFzZ6T~<1X98OIZlcp>3e#%J_19 zRaPCz!D^hY@V>8SVnsT$vU!CKR9lcuT3L$iys1Vlf;KdQI;k0T=#LqdI?BZg@m^x% zO98cBPZE1hie7^VgL#1rG(|f%*d%t}Wy!F`!-RFczT|zEkMLy#v@cX0s|$-IN7|&r zT|+qhR3d*QO}x)+DCNLy$4_7yUAo;Dw-dU((6s!>oGCuQWh?a8RELItNOiC*%uie8 z16XYqU&#?4aU@4l%EW~HZv8!|F2Sg~xQNF&r5*iGl(q}0QD(fWZzkF!!y|CVdmV?i zsNJLnvB*?WiUOj)k6B}2g_aL%Q>jy2u0=ElVql`+hn4{pnUS%<31xuH7_2jWPUvYD zBK6JacBh>B`a9;KBEiH>&uu_WcZ$I`O`9AUw)*^YQ@E!BtFJ{By%0DGRvu>~XoUdp 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 W8g4Bwh9?mKk6SmjRZ4DHBL4^Zgl9Sc diff --git a/docs/img/VSCode-EXT-Python-Header.png b/docs/img/VSCode-EXT-Python-Header.png new file mode 100644 index 0000000000000000000000000000000000000000..27083d739f90ec3e71dd7ba7381e56acdb9e2644 GIT binary patch literal 33457 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�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 Zk4kf#lOLyT&|1Rle4uvbX;;t?Q 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 F{1;CCG1CA5 literal 0 HcmV?d00001 diff --git a/docs/img/VSCode-EXT-Python-TestsStatuses.png b/docs/img/VSCode-EXT-Python-TestsStatuses.png new file mode 100644 index 0000000000000000000000000000000000000000..923d8ab0ae38fd4c6ba0642daedcefb0d928a737 GIT binary patch literal 13707 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 Date: Mon, 27 Sep 2021 20:53:46 +0200 Subject: [PATCH 005/128] Finished writing VS Code part --- docs/TOOLS.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index ae38a024ed..54d3c0eb8d 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -4,6 +4,14 @@ IMPORTANT TODO: UPDATE URL PATHS OF IMAGES Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/python/installation). +- [Environments](#environments) + - [Global](#global-environments) + - [Virtualenv](#venv) + - [Conda](#Conda) + +- [VS Code](#visual-studio-code) + - [The Python extension](#python-for-vs-code) + ## Visual Studio Code Visual studio code (VS Code) is a code editor created by Microsoft. It is not specialized to work for a specific programming language, but to be an editor that can do everything. You can extend the editor using extensions, but it comes with some great extensions as well. @@ -18,29 +26,25 @@ The Python extension from Microsoft is extremely useful because of its range of #### Selecting the interpreter -The Python extensions supports the switching multiple `interpreters`, this way you can use different Python versions for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](). - -![Interpreter selection]() +The Python extensions supports the switching between multiple `interpreters`, this way you can use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](). Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. ![Interpreter selection PT2](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-SelectInterpreter-2.png) -Here, click on the Python installation you want to use and you can start coding! - -#### Included testing - -The Python extension comes with a `Testing` tab on your side-bar. This can be really useful to test your solutions with. +#### Other features -![Python Testing Tab](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-TestingConfiguration.png) +The Python plugin also comes with some other features that can help you debug and improve your python code, here are some of those tools. -Configuration is easy, click on the `Configure Python Tests` button. A window will pop up asking you to select a _testing framework_, select `pytest` (find more about the installation of Pytest [here](/TESTS.md)) and then select the directory where the extension can find the tests. If the tests are in your current directory, select `root`. +[Test discovery tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - A tool that generates a tree containing all the *Pytest* and *Unittest* inside a directory. It will give you an easier and more readable interface for *Pytest*. -![Tests Statuses](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-TestsStatuses.png) +[Debugging tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool then allows you to view all *private* and *global* variables at that point in your program. -It will now show a collapsible tree of all the directories containing tests, it even shows you every test per file. Hovering over a directory or file will show a play button that, when pressed, runs all the tests inside of it. Pressing the play button on a single test will only run that specific test, handy for when you're trying to fix a specific problem. +[Linting](https://code.visualstudio.com/docs/python/testing#_configure-tests) - Linting looks out for simple mistakes in your code and notifies you when you write bad-practice code according to PEP. Exercism currently focusses on the correct use of [PEP 8](https://www.python.org/dev/peps/pep-0008/). +[Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) - This tool automatically formats your code to look a certain way, we recommend `autopep8` because it adheres to [PEP 8](https://www.python.org/dev/peps/pep-0008/), other formatters are supported. +[Config reference](https://code.visualstudio.com/docs/python/settings-reference) - If you want to make your Python development on VS Code behave exactly like you want it to, view this reference guide. It explains options for the extension that can really improve your coding experience. ## Code Style and Linting From 1a72ef09665d7800b74c71980c7ac4a4473e4d28 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 27 Sep 2021 22:22:15 +0200 Subject: [PATCH 006/128] Added Pycharm docs & relative img links --- docs/TOOLS.md | 10 ++++++++++ docs/img/PyCharm-Config-InterpreterDropDown.png | Bin 0 -> 18075 bytes docs/img/PyCharm-Config-InterpreterNew.png | Bin 0 -> 35845 bytes .../VSCode-EXT-Python-TestingConfiguration.png | Bin 17203 -> 0 bytes docs/img/VSCode-EXT-Python-TestsStatuses.png | Bin 13707 -> 0 bytes 5 files changed, 10 insertions(+) create mode 100644 docs/img/PyCharm-Config-InterpreterDropDown.png create mode 100644 docs/img/PyCharm-Config-InterpreterNew.png delete mode 100644 docs/img/VSCode-EXT-Python-TestingConfiguration.png delete mode 100644 docs/img/VSCode-EXT-Python-TestsStatuses.png diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 54d3c0eb8d..dc64207229 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -46,6 +46,16 @@ The Python plugin also comes with some other features that can help you debug an [Config reference](https://code.visualstudio.com/docs/python/settings-reference) - If you want to make your Python development on VS Code behave exactly like you want it to, view this reference guide. It explains options for the extension that can really improve your coding experience. +## PyCharm + +PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can also extend it's features using plugins, but out-of-the-box it comes with a load of features pre-installed. + +### Selecting the interpreter + +Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `Python Interpreter`. Click on the dropdown menu and select the environment you will be using. If the environment you would like to use is not in the list click on the `Show All...` button: + +![Interpreter Selection Dropdown](./img/PyCharm-Config-InterpreterDropDown.png) + ## Code Style and Linting There's a style guide called [PEP8](http://legacy.python.org/dev/peps/pep-0008/) that many Python projects adhere to. diff --git a/docs/img/PyCharm-Config-InterpreterDropDown.png b/docs/img/PyCharm-Config-InterpreterDropDown.png new file mode 100644 index 0000000000000000000000000000000000000000..15372d87234970804fbd5ee20ec46bdc82b5d11e GIT binary patch literal 18075 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 literal 0 HcmV?d00001 diff --git a/docs/img/PyCharm-Config-InterpreterNew.png b/docs/img/PyCharm-Config-InterpreterNew.png new file mode 100644 index 0000000000000000000000000000000000000000..eae93a2d335a8555aebdc29440647a39cf3daaa3 GIT binary patch literal 35845 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 F{1;CCG1CA5 diff --git a/docs/img/VSCode-EXT-Python-TestsStatuses.png b/docs/img/VSCode-EXT-Python-TestsStatuses.png deleted file mode 100644 index 923d8ab0ae38fd4c6ba0642daedcefb0d928a737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13707 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 Date: Mon, 27 Sep 2021 23:54:10 +0200 Subject: [PATCH 007/128] Update TOOLS.md --- docs/TOOLS.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index dc64207229..0820a04f33 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -5,7 +5,6 @@ IMPORTANT TODO: UPDATE URL PATHS OF IMAGES Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/python/installation). - [Environments](#environments) - - [Global](#global-environments) - [Virtualenv](#venv) - [Conda](#Conda) @@ -20,7 +19,7 @@ Visual studio code (VS Code) is a code editor created by Microsoft. It is not sp _Extension-id: ms-python.python_ -![Python Extension Header on VS Code](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-Header.png) +![Python Extension Header on VS Code](.\img\VSCode-EXT-Python-Header.png) The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). @@ -30,7 +29,7 @@ The Python extensions supports the switching between multiple `interpreters`, th Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. -![Interpreter selection PT2](C:\Users\jobko\OneDrive\Documenten\GitHub\python\docs\img\VSCode-EXT-Python-SelectInterpreter-2.png) +![Interpreter selection PT2](.\img\VSCode-EXT-Python-SelectInterpreter-2.png) #### Other features @@ -56,6 +55,20 @@ Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `P ![Interpreter Selection Dropdown](./img/PyCharm-Config-InterpreterDropDown.png) +From there click on the `+` button to add a new interpreter. Select the type of interpreter on the left, we suggest you either run a [conda]() or [virtualenv]() environment, but running the *system interpreter* works fine too. Once you selected your interpreter, press the `Okay` button. + +![Add New Interpreter](.\img\PyCharm-Config-InterpreterNew.png) + +### Other features + +Some other features that we won't cover in this guide, but are really useful for development are: + +[Running Tests](https://www.jetbrains.com/help/pycharm/pytest.html#run-pytest-test) - Running tests directly from a GUI in your window is really easy, but don't forget to look at the [pytest parameters](TESTS.md#extra-arguments) to set it up to your liking. + +[Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm can get really extensive, but can be really useful as well. Start here to learn how to properly debug using PyCharm. + + + ## Code Style and Linting There's a style guide called [PEP8](http://legacy.python.org/dev/peps/pep-0008/) that many Python projects adhere to. From 1021bb8b54ffd6c47f00c1fe68bce2b0b7d27827 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 09:33:36 +0200 Subject: [PATCH 008/128] Added Spyder Docs --- docs/TOOLS.md | 16 ++++++++++++++++ docs/img/Spyder-Config-Interpreter.png | Bin 0 -> 32611 bytes 2 files changed, 16 insertions(+) create mode 100644 docs/img/Spyder-Config-Interpreter.png diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 0820a04f33..71ff49bdda 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -67,7 +67,23 @@ Some other features that we won't cover in this guide, but are really useful for [Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm can get really extensive, but can be really useful as well. Start here to learn how to properly debug using PyCharm. +## Spyder IDE +Spyder is a python IDE tailored for the scientific community. It packs some really good debugging utilities and it has an integrated *IPython terminal*. Out-of-the-box it does not come with support for *Unittests* and *Pytests*, but there is an official plugin available. + +### Selecting the interpreter + +To change the interpreter, go to `tools` >> `Preferences` >> `Python interpreter`. You can either select the global interpreter defined by Spyder or you can enter the path to your own Python environment. + +![Spyder Python Interpreter Selection](.\img\Spyder-Config-Interpreter.png) + +Do not forget to click `Apply` once you selected your interpreter. + +### Other features + +[Spyder Unittest](https://github.com/spyder-ide/spyder-unittest/releases/latest) - If you want to have a built-in interface for you tests, install this plugin. Clicking the link will bring you to the latest release on GitHub. + +[Code analyzer](https://docs.spyder-ide.org/current/panes/pylint.html#using-the-code-analyzer) - An integrated tool for analyzing your code, detecting bad practices, and catching bugs before you run your program. You can also configure it to only catch [PEP 8](https://www.python.org/dev/peps/pep-0008/) violations. ## Code Style and Linting diff --git a/docs/img/Spyder-Config-Interpreter.png b/docs/img/Spyder-Config-Interpreter.png new file mode 100644 index 0000000000000000000000000000000000000000..a5856aa7c0ab996acb9f0d6ab4a0b3ec984c8633 GIT binary patch literal 32611 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 literal 0 HcmV?d00001 From 3d66c2bb663e8162d4caf0ab179c39bc9c0d6954 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 10:50:45 +0200 Subject: [PATCH 009/128] Added VENV docs --- docs/TOOLS.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 71ff49bdda..96885667fe 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -6,10 +6,73 @@ Before you can start coding, make sure that you have the proper version of Pytho - [Environments](#environments) - [Virtualenv](#venv) - - [Conda](#Conda) + - [Conda](#conda) +- [Editors and IDEs](#visual-studio-code) + - [VS Code](#visual-studio-code) + - [Selecting an interpreter](#selecting-the-interpreter) + - [Other features](#other-features-1) + - [Pycharm](#pycharm) + - [Selecting an interpreter](#selecting-the-interpreter-2) + - [Other features](#other-features-2) + - [Spyder](#spyder-ide) + - [Selecting an interpreter](#selecting-the-interpreter-3) + - [Other features](#other-features-3) -- [VS Code](#visual-studio-code) - - [The Python extension](#python-for-vs-code) + + +--- + +## Environments + +Python environments are like separate Python installations, they can cleanup your workflow and projects by separating the packages you install. Making sure that you don't get bugs generated by something that you imported in another project. + +There are two major *virtual environments* in use today, `virtualenv` and `conda`. Both of which are generally easy to install. + +### Venv + +Also known as `virtualenv`, *venv* is a light-weight solution to virtual environments. It creates a separate Python binary for every virtual environment and stores that inside your directory. + +Installing `venv` is easy using `pip`: + +```bash +$ python3 -m pip install virtualenv +Successfully installed virtualenv-20.8.1 +``` + +#### Creating your virtual environment + +To create a virtual environment, `cd` to the directory you want to put the *venv* in. Then run the `virtualenv` command with the name folder which will store your *environment*, common convention is to call it `venv`: + +```bash +$ python3 -m virtualenv {name_of_virtualenv} +created virtual environment ... in 8568ms +``` + +#### Activating your virtual environment + +To activate the virtual environment on Windows, run the following command: + +```powershell +PS> .\{name_of_virtual_env}\Scripts\activate +(venv) PS> _ +``` + +To activate your virtual environment on Linux, or MacOS X, run the following command: + +```bash +$ source {name_of_virtual_env}/bin/activate +(venv) $ _ +``` + +From this terminal you can now run `pip` commands. All of the packages installed in the environment will go to `{name_of_virtual_env}/Lib`. + +### Conda + + + + + +--- ## Visual Studio Code @@ -45,6 +108,8 @@ The Python plugin also comes with some other features that can help you debug an [Config reference](https://code.visualstudio.com/docs/python/settings-reference) - If you want to make your Python development on VS Code behave exactly like you want it to, view this reference guide. It explains options for the extension that can really improve your coding experience. +--- + ## PyCharm PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can also extend it's features using plugins, but out-of-the-box it comes with a load of features pre-installed. @@ -67,6 +132,8 @@ Some other features that we won't cover in this guide, but are really useful for [Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm can get really extensive, but can be really useful as well. Start here to learn how to properly debug using PyCharm. +--- + ## Spyder IDE Spyder is a python IDE tailored for the scientific community. It packs some really good debugging utilities and it has an integrated *IPython terminal*. Out-of-the-box it does not come with support for *Unittests* and *Pytests*, but there is an official plugin available. @@ -85,6 +152,10 @@ Do not forget to click `Apply` once you selected your interpreter. [Code analyzer](https://docs.spyder-ide.org/current/panes/pylint.html#using-the-code-analyzer) - An integrated tool for analyzing your code, detecting bad practices, and catching bugs before you run your program. You can also configure it to only catch [PEP 8](https://www.python.org/dev/peps/pep-0008/) violations. +[Debugging](https://docs.spyder-ide.org/current/panes/debugging.html) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool will then let you go through your python code and quickly notice where it goes wrong. + +[Variable Explorer](https://docs.spyder-ide.org/current/panes/variableexplorer.html) - This tool is really useful in combination with the debugger. It allows you to view all the active variables in your code and it even allows editing of the values inside those variables. + ## Code Style and Linting There's a style guide called [PEP8](http://legacy.python.org/dev/peps/pep-0008/) that many Python projects adhere to. From 09b18138ddb39fb5e841ff5bb144daa7867edfaa Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 14:09:53 +0200 Subject: [PATCH 010/128] Added conda environments docs --- docs/TOOLS.md | 34 ++++++++++++++++++++++---------- docs/img/Anaconda-Conda-New.png | Bin 0 -> 31804 bytes 2 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 docs/img/Anaconda-Conda-New.png diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 96885667fe..9532f52d55 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -68,17 +68,31 @@ From this terminal you can now run `pip` commands. All of the packages installed ### Conda +*Latest download can be found [here](https://www.anaconda.com/products/individual)* +Packaged with *Anaconda*, `conda` environments are similar to [venv environments](#venv) with the key difference being that `conda` can support the `R programming language`. Anaconda is most commonly used by researchers and data scientists, because of its set of tools and programs tailored to them. The [Spyder IDE](#spider-ide) is one of the tools that comes with Anaconda. +To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `environments` and then press the `Create` button. Fill in a name for your `conda` environment, select Python `3.8 or above` and click `Create`: +![Creating New Conda Environment](.\img\Anaconda-Conda-New.png) + +#### Activating your virtual environment + +Activating your `conda` environment is easy, just click the `â–º` button next to your *environment*, then press `Open Terminal`. This should open a new terminal with an interface for your `conda` environment inside. + +From here you can run regular `pip` commands and other modules that you have installed inside your environment. All libraries will automatically be installed inside the `conda` environment. --- -## Visual Studio Code + + +## Editors and IDEs + +### Visual Studio Code Visual studio code (VS Code) is a code editor created by Microsoft. It is not specialized to work for a specific programming language, but to be an editor that can do everything. You can extend the editor using extensions, but it comes with some great extensions as well. -### Python for VS Code +#### Python for VS Code _Extension-id: ms-python.python_ @@ -86,7 +100,7 @@ _Extension-id: ms-python.python_ The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). -#### Selecting the interpreter +##### Selecting the interpreter The Python extensions supports the switching between multiple `interpreters`, this way you can use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](). @@ -94,7 +108,7 @@ Click on the "Select interpreter" button in the lower left-hand of your window, ![Interpreter selection PT2](.\img\VSCode-EXT-Python-SelectInterpreter-2.png) -#### Other features +##### Other features The Python plugin also comes with some other features that can help you debug and improve your python code, here are some of those tools. @@ -110,11 +124,11 @@ The Python plugin also comes with some other features that can help you debug an --- -## PyCharm +### PyCharm PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can also extend it's features using plugins, but out-of-the-box it comes with a load of features pre-installed. -### Selecting the interpreter +#### Selecting the interpreter Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `Python Interpreter`. Click on the dropdown menu and select the environment you will be using. If the environment you would like to use is not in the list click on the `Show All...` button: @@ -124,7 +138,7 @@ From there click on the `+` button to add a new interpreter. Select the type of ![Add New Interpreter](.\img\PyCharm-Config-InterpreterNew.png) -### Other features +#### Other features Some other features that we won't cover in this guide, but are really useful for development are: @@ -134,11 +148,11 @@ Some other features that we won't cover in this guide, but are really useful for --- -## Spyder IDE +### Spyder IDE Spyder is a python IDE tailored for the scientific community. It packs some really good debugging utilities and it has an integrated *IPython terminal*. Out-of-the-box it does not come with support for *Unittests* and *Pytests*, but there is an official plugin available. -### Selecting the interpreter +#### Selecting the interpreter To change the interpreter, go to `tools` >> `Preferences` >> `Python interpreter`. You can either select the global interpreter defined by Spyder or you can enter the path to your own Python environment. @@ -146,7 +160,7 @@ To change the interpreter, go to `tools` >> `Preferences` >> `Python interpreter Do not forget to click `Apply` once you selected your interpreter. -### Other features +#### Other features [Spyder Unittest](https://github.com/spyder-ide/spyder-unittest/releases/latest) - If you want to have a built-in interface for you tests, install this plugin. Clicking the link will bring you to the latest release on GitHub. diff --git a/docs/img/Anaconda-Conda-New.png b/docs/img/Anaconda-Conda-New.png new file mode 100644 index 0000000000000000000000000000000000000000..425b82164d0468a9d2e428577aeac9807929510c GIT binary patch literal 31804 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 Date: Tue, 28 Sep 2021 18:53:22 +0200 Subject: [PATCH 011/128] Wrote Windows tutorial for adding to path --- docs/TESTS.md | 70 ++++++++++++++++++---- docs/img/Windows-AddPythonPath.png | Bin 0 -> 14499 bytes docs/img/Windows-EnvironmentVariables.png | Bin 0 -> 7839 bytes docs/img/Windows-SystemProperties.png | Bin 0 -> 25917 bytes 4 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 docs/img/Windows-AddPythonPath.png create mode 100644 docs/img/Windows-EnvironmentVariables.png create mode 100644 docs/img/Windows-SystemProperties.png diff --git a/docs/TESTS.md b/docs/TESTS.md index 90b86b5f09..ce5bca69f4 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -1,18 +1,25 @@ # Tests +TODO: Change links to constant URL, not relative. + 1. [Pytest](#pytest) - [Installation](#installing-pytest) + - [Virtual environments](#virtual-environments) - [Running the tests](#running-the-tests) - [Failing tests](#failures) - [Extra arguments](#extra-arguments) - [Stop test after first failure](#stop-after-first-failure-[-x]) - [Failed Tests First](#failed-tests-first-[--ff]) - [Recommended setup](#recommended-workflow) + - [Python Debugger in Pytest](#python-debugger) 2. [Tools for your IDE](#extending-your-ide) -3. [Additional testing tools](#additional-testing) TODO - - [Python Debugger in Pytest](#PDB) TODO +3. [Additional Information](#additional-information) - [Adding python scripts to path](#adding-to-path) TODO + + +--- + ## Pytest _Official Pytest documentation can be found on the [Pytest Wiki](https://pytest.org/en/latest/) page._ @@ -47,6 +54,17 @@ pytest 6.2.5 If you do not want to precede every command with `python3 -m` please refer to [adding to PATH](#adding-to-path) at the end of this document. +#### Virtual environments + +*For more information about virtual environments please refer to the [TOOLS](.\TOOLS.md) file.* + +When installing Pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-environment). After which you can run: + +```bash +$ pip install pytest pytest-cache pytest-subtests pytest-pylint +Successfully installed pytest-6.2.5 ... +``` + ### Running the tests To run your test, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with the path_). @@ -126,27 +144,53 @@ pytest -x -ff bob_test.py This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you run the test again, `pytest` will first test that failed test, then continue with the rest. -## Extending your IDE +#### Python Debugger -If you'd like to extend your IDE with some tools that will help you with testing/improving your code, check [this]() page. We go into multiple IDEs and editors and some handy extensions. +If you want to truly debug like a pro, use the `--pdb` argument after the `pytest` command. -## Additional testing +```bash +$ python3 -m pytest --pdb bob_test.py +``` -Here is some additional information, which could come in handy. +When a test fails it allows you to look at variables and how your code responds. If you want to learn how to really use `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. -### PDB +## Extending your IDE -TODO +If you'd like to extend your IDE with some tools that will help you with testing/improving your code, check [this]() page. We go into multiple IDEs and editors and some useful extensions. -Typing pdb on the command line will drop you into the python debugger when a test fails. -To learn how to use pdb, check out the -[documentation](https://docs.python.org/3/library/pdb.html#debugger-commands). +## Additional information + +### Adding to PATH + +**Note:** If you are running a [virtual environment](.\TOOLS.md) you do not need to *add to path* as it should work fine. + +Preceding every module you want to run with `python3 -m` might get a little annoying. You can add the `Scripts` folder of your Python installation to your path. If you do not know where you have installed Python, run the following command in your terminal: ```bash -pytest --pdb bob_test.py +$ python3 -c "import os, sys; print(os.path.dirname(sys.executable))" +{python_directory} ``` -### Adding to PATH +The *returned* directory is where your Python version is installed, in this tutorial it is referred to as `{python_directory}`. + +#### Windows + +Click the `Windows Start` button and lookup *Edit the system environment variables* and press enter. Next press, `Environment Variables...`: + +![Press the blue button, lol](.\img\Windows-SystemProperties.png) + +Then find the `Path` variable in your *User variables*, select it, and click `Edit...`: + +![Selecting the path variable](.\img\Windows-EnvironmentVariables.png) + +Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: + +![Add python to path](.\img\Windows-AddPythonPath.png) + +#### MacOS X TODO +#### Linux + +TODO diff --git a/docs/img/Windows-AddPythonPath.png b/docs/img/Windows-AddPythonPath.png new file mode 100644 index 0000000000000000000000000000000000000000..3e91a6cd5179dea182440050465a8b77dc7f7917 GIT binary patch literal 14499 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 z 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 Xz1u91a?`L5KJ?h>vGDYNv+Di@H9H=e literal 0 HcmV?d00001 From 3c18af3fe53d8ddfdb56dbc6bc18170accdda6db Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 20:51:09 +0200 Subject: [PATCH 012/128] Improved shared test file and added disclaimer to TOOLS.md --- docs/TOOLS.md | 21 ++++++++------------- exercises/shared/.docs/tests.md | 24 ++++++++---------------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 9532f52d55..31caf115c0 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -2,11 +2,18 @@ IMPORTANT TODO: UPDATE URL PATHS OF IMAGES -Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/python/installation). +A list of tools, IDEs and editors that can help you write and debug your *python* code. + +*Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* + +Before you can start coding, make sure that you have the proper version of python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/python/installation). + +--- - [Environments](#environments) - [Virtualenv](#venv) - [Conda](#conda) + - [Editors and IDEs](#visual-studio-code) - [VS Code](#visual-studio-code) - [Selecting an interpreter](#selecting-the-interpreter) @@ -169,15 +176,3 @@ Do not forget to click `Apply` once you selected your interpreter. [Debugging](https://docs.spyder-ide.org/current/panes/debugging.html) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool will then let you go through your python code and quickly notice where it goes wrong. [Variable Explorer](https://docs.spyder-ide.org/current/panes/variableexplorer.html) - This tool is really useful in combination with the debugger. It allows you to view all the active variables in your code and it even allows editing of the values inside those variables. - -## Code Style and Linting - -There's a style guide called [PEP8](http://legacy.python.org/dev/peps/pep-0008/) that many Python projects adhere to. -Read it when you get a chance! - -If you just want a quick overview of some problems in your code, use [pylint](http://www.pylint.org/)! -It can be pretty picky though, so take its results with a grain of salt. -If you don't agree with one of its points, that's a good topic for a discussion in the comments for your program! - -If you'd rather have a tool take care of your style issues, take a look at [autopep8](https://github.com/hhatto/autopep8)! -Run `autopep8 -d mycode.py` to get a diff of the changes it proposes and `autopep8 -i mycode.py` to format the code in place! diff --git a/exercises/shared/.docs/tests.md b/exercises/shared/.docs/tests.md index 00b83fd491..533066c135 100644 --- a/exercises/shared/.docs/tests.md +++ b/exercises/shared/.docs/tests.md @@ -1,27 +1,19 @@ # Tests -You can run the included tests by typing `pytest _test.py` on the command line from within the exercise's directory. +To run the included *test files*, run the test file using the `pytest` module, replacing `{exercise_name}`: -You can also tell Python to run the pytest module on the command line from either within the exercise directory or with a path to the exercise directory. -`python -m pytest _test.py` from within the exercise directory. +```bash +$ python3 -m pytest {exercise_name}_test.py +``` -`python -m pytest /fully/qualified/path/to//` OR `python -m pytest realtive/path/to/` from a non-exercise directory. +Many IDE's and code editors also have built-in support for using Pytest to run tests, check them out [here](https://github.com/exercism/python/blob/main/docs/TOOLS.md#editors-and-ides). -Many IDE's and code editors also have built-in support for using PyTest to run tests. +For more information about running tests using `pytest`, checkout our [Python testing guide](https://github.com/exercism/python/blob/main/docs/TESTS.md#pytest). -- [Visual Studio Code](https://code.visualstudio.com/docs/python/testing) -- [PyCharm Professional & Community Editions](https://www.jetbrains.com/help/pycharm/pytest.html#create-pytest-test) -- [Atom](https://atom.io/packages/atom-python-test) -- [Spyder](https://www.spyder-ide.org/blog/introducing-unittest-plugin/) -- [Sublime](https://github.com/kaste/PyTest) -- [vim-test](https://github.com/vim-test/vim-test) - -See the [Python tests page](https://github.com/exercism/python/blob/main/docs/TESTS.md) for more information. - -### Common `pytest` options +### Common pytest options - `-v` : enable verbose output. - `-x` : stop running tests on first failure. - `--ff` : run failures from previous test before running other test cases. -For other options, see `python -m pytest -h`. PyTest documentation can be found [here](https://docs.pytest.org/en/latest/getting-started.html). +For other options, see `python3 -m pytest -h`. From 5ac3fa2962a1a0363edd5e5c1759f4e506c922fd Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 30 Sep 2021 14:45:33 +0200 Subject: [PATCH 013/128] Added docs for JupyterLab and Sublime Text --- docs/TOOLS.md | 77 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 27 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 31caf115c0..c57b5da6ba 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -14,24 +14,34 @@ Before you can start coding, make sure that you have the proper version of pytho - [Virtualenv](#venv) - [Conda](#conda) -- [Editors and IDEs](#visual-studio-code) - - [VS Code](#visual-studio-code) - - [Selecting an interpreter](#selecting-the-interpreter) - - [Other features](#other-features-1) - - [Pycharm](#pycharm) - - [Selecting an interpreter](#selecting-the-interpreter-2) - - [Other features](#other-features-2) - - [Spyder](#spyder-ide) - - [Selecting an interpreter](#selecting-the-interpreter-3) - - [Other features](#other-features-3) - - +- [Tools](#tools) + - [Environments](#environments) + - [Venv](#venv) + - [Creating your virtual environment](#creating-your-virtual-environment) + - [Activating your virtual environment](#activating-your-virtual-environment) + - [Conda](#conda) + - [Activating your conda environment](#activating-your-conda-environment) + - [Editors and IDEs](#editors-and-ides) + - [Visual Studio Code](#visual-studio-code) + - [Python for VS Code](#python-for-vs-code) + - [Selecting the interpreter](#selecting-the-interpreter) + - [Other features](#other-features) + - [PyCharm](#pycharm) + - [Selecting the interpreter](#selecting-the-interpreter-1) + - [Other features](#other-features-1) + - [Spyder IDE](#spyder-ide) + - [Selecting the interpreter](#selecting-the-interpreter-2) + - [Other features](#other-features-2) + - [Sublime text](#sublime-text) + - [Notable extensions](#notable-extensions) + - [JupyterLab](#jupyterlab) + - [Notable extensions](#notable-extensions-1) --- ## Environments -Python environments are like separate Python installations, they can cleanup your workflow and projects by separating the packages you install. Making sure that you don't get bugs generated by something that you imported in another project. +Python environments are like separate Python installations, they can cleanup your workflow and projects by separating the packages you install. Making sure that you don't get bugs generated by something that you imported in another project. There are two major *virtual environments* in use today, `virtualenv` and `conda`. Both of which are generally easy to install. @@ -39,13 +49,6 @@ There are two major *virtual environments* in use today, `virtualenv` and `conda Also known as `virtualenv`, *venv* is a light-weight solution to virtual environments. It creates a separate Python binary for every virtual environment and stores that inside your directory. -Installing `venv` is easy using `pip`: - -```bash -$ python3 -m pip install virtualenv -Successfully installed virtualenv-20.8.1 -``` - #### Creating your virtual environment To create a virtual environment, `cd` to the directory you want to put the *venv* in. Then run the `virtualenv` command with the name folder which will store your *environment*, common convention is to call it `venv`: @@ -83,7 +86,7 @@ To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `e ![Creating New Conda Environment](.\img\Anaconda-Conda-New.png) -#### Activating your virtual environment +#### Activating your conda environment Activating your `conda` environment is easy, just click the `â–º` button next to your *environment*, then press `Open Terminal`. This should open a new terminal with an interface for your `conda` environment inside. @@ -91,8 +94,6 @@ From here you can run regular `pip` commands and other modules that you have ins --- - - ## Editors and IDEs ### Visual Studio Code @@ -101,15 +102,15 @@ Visual studio code (VS Code) is a code editor created by Microsoft. It is not sp #### Python for VS Code -_Extension-id: ms-python.python_ +Extension: _Extension-id: ms-python.python_ ![Python Extension Header on VS Code](.\img\VSCode-EXT-Python-Header.png) -The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). +The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). ##### Selecting the interpreter -The Python extensions supports the switching between multiple `interpreters`, this way you can use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](). +The Python extensions supports the switching between multiple `interpreters`, this way you can use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](#environments). Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. @@ -119,7 +120,7 @@ Click on the "Select interpreter" button in the lower left-hand of your window, The Python plugin also comes with some other features that can help you debug and improve your python code, here are some of those tools. -[Test discovery tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - A tool that generates a tree containing all the *Pytest* and *Unittest* inside a directory. It will give you an easier and more readable interface for *Pytest*. +[Test discovery tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - A tool that generates a tree containing all the *Pytest* and *Unittest* inside a directory. It will give you an easier and more readable interface for *Pytest*. [Debugging tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool then allows you to view all *private* and *global* variables at that point in your program. @@ -176,3 +177,25 @@ Do not forget to click `Apply` once you selected your interpreter. [Debugging](https://docs.spyder-ide.org/current/panes/debugging.html) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool will then let you go through your python code and quickly notice where it goes wrong. [Variable Explorer](https://docs.spyder-ide.org/current/panes/variableexplorer.html) - This tool is really useful in combination with the debugger. It allows you to view all the active variables in your code and it even allows editing of the values inside those variables. + +--- + +### Sublime text + +A text editor for coding, made by *Sublime HQ Pty Ltd*. It is similar to [VS Code](#visual-studio-code) in a lot of regards. Sublime text is almost fully customizable. It also has a built-in auto-complete engine and comes with Python syntax highlighting out-of-the-box. + +#### Notable extensions + +Sublime Text comes with a lot of tools already, but some of these tools could make your python development easier. Make sure you have *package control* installed, go to `Tools` >> `Install Package Control...`. To install packages open your command pallet and type in `Package Control: Install Package`, here you can enter the name of the packages you'd like to install. + +[Terminus](https://packagecontrol.io/packages/Terminus) - A terminal for your sublime. It is fully featured and support for Windows operating systems. This package is almost a must-have for you to be able to run `pytest` scripts. + +[SublimeLinter](https://packagecontrol.io/packages/SublimeLinter) - A linter for sublime. This tool automatically notifies you of bad-practice python code. Useful for when you have it set up to use PEP8. + +### JupyterLab + +Jupyter lab is an in-browser code editor that runs on your machine. You install it using a `pip` command in your *environment* and then automatically uses that environment. Jupyter is most well-known for being able to create coding notebooks. It is commonly used among data-scientists. + +#### Notable extensions + +The built-in tools for Jupyter Lab are good-enough for almost all exercises on *Exercism*. Most extensions made for Jupyter are made with data-science in mind. Explore extensions at your own pleasure. From ae2de41aeae024398ddcaf685b1954cb8ef1cf6e Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 30 Sep 2021 14:53:47 +0200 Subject: [PATCH 014/128] Added Virtualenvwrapper piece. --- docs/TOOLS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index c57b5da6ba..dcb64bb87e 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -19,6 +19,7 @@ Before you can start coding, make sure that you have the proper version of pytho - [Venv](#venv) - [Creating your virtual environment](#creating-your-virtual-environment) - [Activating your virtual environment](#activating-your-virtual-environment) + - [Virtual Environment wrapper](#virtual-environment-wrapper) - [Conda](#conda) - [Activating your conda environment](#activating-your-conda-environment) - [Editors and IDEs](#editors-and-ides) @@ -76,6 +77,12 @@ $ source {name_of_virtual_env}/bin/activate From this terminal you can now run `pip` commands. All of the packages installed in the environment will go to `{name_of_virtual_env}/Lib`. +#### Virtual Environment wrapper + +Reference: [Wiki](https://virtualenvwrapper.readthedocs.io/en/latest/) + +The `virtualenvwrapper` module manages all your virtual environments in one place. You can create, copy, delete and switch between environments using the tools the module provides. It also allows for extensions so you can add tools you'd like. You can create your own extension using the tutorial found [here](https://virtualenvwrapper.readthedocs.io/en/latest/plugins.html#plugins). + ### Conda *Latest download can be found [here](https://www.anaconda.com/products/individual)* From b8c8a9aee74ae575d77c9a31dd5dda968d6e6d4e Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 30 Sep 2021 15:00:37 +0200 Subject: [PATCH 015/128] Cleaned up. --- docs/TESTS.md | 51 ++++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index ce5bca69f4..e0a80f8643 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -2,21 +2,23 @@ TODO: Change links to constant URL, not relative. -1. [Pytest](#pytest) - - [Installation](#installing-pytest) - - [Virtual environments](#virtual-environments) - - [Running the tests](#running-the-tests) - - [Failing tests](#failures) - - [Extra arguments](#extra-arguments) - - [Stop test after first failure](#stop-after-first-failure-[-x]) - - [Failed Tests First](#failed-tests-first-[--ff]) - - [Recommended setup](#recommended-workflow) - - [Python Debugger in Pytest](#python-debugger) -2. [Tools for your IDE](#extending-your-ide) -3. [Additional Information](#additional-information) - - [Adding python scripts to path](#adding-to-path) TODO - - +- [Tests](#tests) + - [Pytest](#pytest) + - [Installing Pytest](#installing-pytest) + - [Windows](#windows) + - [Linux / MacOS](#linux--macos) + - [Virtual environments](#virtual-environments) + - [Running the tests](#running-the-tests) + - [Failures](#failures) + - [Extra arguments](#extra-arguments) + - [Stop After First Failure [`-x`]](#stop-after-first-failure--x) + - [Failed Tests First [`--ff`]](#failed-tests-first---ff) + - [Recommended Workflow](#recommended-workflow) + - [Python Debugger](#python-debugger) + - [Extending your IDE](#extending-your-ide) + - [Additional information](#additional-information) + - [Adding to PATH](#adding-to-path) + - [Windows](#windows-1) --- @@ -28,7 +30,7 @@ Pytest let's you test your solutions using our provided tests, it is what we use ### Installing Pytest -Pytest can be installed and updated using the built-in Python utility `pip`. +Pytest can be installed and updated using the built-in Python utility `pip`. #### Windows @@ -56,7 +58,7 @@ If you do not want to precede every command with `python3 -m` please refer to [a #### Virtual environments -*For more information about virtual environments please refer to the [TOOLS](.\TOOLS.md) file.* +*For more information about virtual environments please refer to the [TOOLS](.\TOOLS.md) file.* When installing Pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-environment). After which you can run: @@ -67,7 +69,7 @@ Successfully installed pytest-6.2.5 ... ### Running the tests -To run your test, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with the path_). +To run your test, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with the path_). ```bash $ cd {exercise-folder-location} @@ -90,7 +92,7 @@ $ python3 -m pytest {exercise_test.py} ______________ name_of_failed_test ______________ # Test code inside of {exercise_test.py} that failed. ... -E TypeOfError: ReturnedValue != ExpectedValue +E TypeOfError: ReturnedValue != ExpectedValue exercise_test.py:{line_of_failed_test}: TypeOfError ============ short test summary info ============ @@ -124,6 +126,7 @@ FAILED example_test.py::ExampleTest::example_test_foo ```bash $ python -m pytest --ff bob_test.py +==================== 7 passed in 503s ==================== ``` #### Recommended Workflow @@ -150,13 +153,14 @@ If you want to truly debug like a pro, use the `--pdb` argument after the `pytes ```bash $ python3 -m pytest --pdb bob_test.py +=============== 4 passed in 0.15s =============== ``` When a test fails it allows you to look at variables and how your code responds. If you want to learn how to really use `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. ## Extending your IDE -If you'd like to extend your IDE with some tools that will help you with testing/improving your code, check [this]() page. We go into multiple IDEs and editors and some useful extensions. +If you'd like to extend your IDE with some tools that will help you with testing/improving your code, check [this](./TOOLS.md) page. We go into multiple IDEs and editors and some useful extensions. ## Additional information @@ -187,10 +191,3 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit ![Add python to path](.\img\Windows-AddPythonPath.png) -#### MacOS X - -TODO - -#### Linux - -TODO From 553b6d7b309addde8408e7452bb721ef1a291626 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 30 Sep 2021 15:06:34 +0200 Subject: [PATCH 016/128] Updated image paths --- docs/TESTS.md | 9 +++------ docs/TOOLS.md | 12 +++++------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index e0a80f8643..b24cadecc2 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -1,7 +1,5 @@ # Tests -TODO: Change links to constant URL, not relative. - - [Tests](#tests) - [Pytest](#pytest) - [Installing Pytest](#installing-pytest) @@ -181,13 +179,12 @@ The *returned* directory is where your Python version is installed, in this tuto Click the `Windows Start` button and lookup *Edit the system environment variables* and press enter. Next press, `Environment Variables...`: -![Press the blue button, lol](.\img\Windows-SystemProperties.png) +![Press the blue button, lol](./img/Windows-SystemProperties.png) Then find the `Path` variable in your *User variables*, select it, and click `Edit...`: -![Selecting the path variable](.\img\Windows-EnvironmentVariables.png) +![Selecting the path variable](./img/Windows-EnvironmentVariables.png) Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: -![Add python to path](.\img\Windows-AddPythonPath.png) - +![Add python to path](./img/Windows-AddPythonPath.png) \ No newline at end of file diff --git a/docs/TOOLS.md b/docs/TOOLS.md index dcb64bb87e..b9587b96d7 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -1,7 +1,5 @@ # Tools -IMPORTANT TODO: UPDATE URL PATHS OF IMAGES - A list of tools, IDEs and editors that can help you write and debug your *python* code. *Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* @@ -91,7 +89,7 @@ Packaged with *Anaconda*, `conda` environments are similar to [venv environments To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `environments` and then press the `Create` button. Fill in a name for your `conda` environment, select Python `3.8 or above` and click `Create`: -![Creating New Conda Environment](.\img\Anaconda-Conda-New.png) +![Creating New Conda Environment](./img/Anaconda-Conda-New.png) #### Activating your conda environment @@ -111,7 +109,7 @@ Visual studio code (VS Code) is a code editor created by Microsoft. It is not sp Extension: _Extension-id: ms-python.python_ -![Python Extension Header on VS Code](.\img\VSCode-EXT-Python-Header.png) +![Python Extension Header on VS Code](./img/VSCode-EXT-Python-Header.png) The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). @@ -121,7 +119,7 @@ The Python extensions supports the switching between multiple `interpreters`, th Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. -![Interpreter selection PT2](.\img\VSCode-EXT-Python-SelectInterpreter-2.png) +![Interpreter selection PT2](./img/VSCode-EXT-Python-SelectInterpreter-2.png) ##### Other features @@ -151,7 +149,7 @@ Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `P From there click on the `+` button to add a new interpreter. Select the type of interpreter on the left, we suggest you either run a [conda]() or [virtualenv]() environment, but running the *system interpreter* works fine too. Once you selected your interpreter, press the `Okay` button. -![Add New Interpreter](.\img\PyCharm-Config-InterpreterNew.png) +![Add New Interpreter](./img/PyCharm-Config-InterpreterNew.png) #### Other features @@ -171,7 +169,7 @@ Spyder is a python IDE tailored for the scientific community. It packs some real To change the interpreter, go to `tools` >> `Preferences` >> `Python interpreter`. You can either select the global interpreter defined by Spyder or you can enter the path to your own Python environment. -![Spyder Python Interpreter Selection](.\img\Spyder-Config-Interpreter.png) +![Spyder Python Interpreter Selection](./img/Spyder-Config-Interpreter.png) Do not forget to click `Apply` once you selected your interpreter. From 68eca1f4314670592e774c549c6565a65ae01687 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 30 Sep 2021 15:16:09 +0200 Subject: [PATCH 017/128] Fixed Images x2 --- docs/TESTS.md | 6 +++--- docs/TOOLS.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index b24cadecc2..dabe070b02 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -179,12 +179,12 @@ The *returned* directory is where your Python version is installed, in this tuto Click the `Windows Start` button and lookup *Edit the system environment variables* and press enter. Next press, `Environment Variables...`: -![Press the blue button, lol](./img/Windows-SystemProperties.png) +![Press the blue button, lol](img/Windows-SystemProperties.png) Then find the `Path` variable in your *User variables*, select it, and click `Edit...`: -![Selecting the path variable](./img/Windows-EnvironmentVariables.png) +![Selecting the path variable](img/Windows-EnvironmentVariables.png) Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: -![Add python to path](./img/Windows-AddPythonPath.png) \ No newline at end of file +![Add python to path](img/Windows-AddPythonPath.png) \ No newline at end of file diff --git a/docs/TOOLS.md b/docs/TOOLS.md index b9587b96d7..19433cce93 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -89,7 +89,7 @@ Packaged with *Anaconda*, `conda` environments are similar to [venv environments To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `environments` and then press the `Create` button. Fill in a name for your `conda` environment, select Python `3.8 or above` and click `Create`: -![Creating New Conda Environment](./img/Anaconda-Conda-New.png) +![Creating New Conda Environment](img/Anaconda-Conda-New.png) #### Activating your conda environment @@ -109,7 +109,7 @@ Visual studio code (VS Code) is a code editor created by Microsoft. It is not sp Extension: _Extension-id: ms-python.python_ -![Python Extension Header on VS Code](./img/VSCode-EXT-Python-Header.png) +![Python Extension Header on VS Code](img/VSCode-EXT-Python-Header.png) The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). @@ -119,7 +119,7 @@ The Python extensions supports the switching between multiple `interpreters`, th Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. -![Interpreter selection PT2](./img/VSCode-EXT-Python-SelectInterpreter-2.png) +![Interpreter selection PT2](img/VSCode-EXT-Python-SelectInterpreter-2.png) ##### Other features @@ -145,11 +145,11 @@ PyCharm is an *Integrated Development Environment* built by JetBrains. It is spe Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `Python Interpreter`. Click on the dropdown menu and select the environment you will be using. If the environment you would like to use is not in the list click on the `Show All...` button: -![Interpreter Selection Dropdown](./img/PyCharm-Config-InterpreterDropDown.png) +![Interpreter Selection Dropdown](img/PyCharm-Config-InterpreterDropDown.png) From there click on the `+` button to add a new interpreter. Select the type of interpreter on the left, we suggest you either run a [conda]() or [virtualenv]() environment, but running the *system interpreter* works fine too. Once you selected your interpreter, press the `Okay` button. -![Add New Interpreter](./img/PyCharm-Config-InterpreterNew.png) +![Add New Interpreter](img/PyCharm-Config-InterpreterNew.png) #### Other features @@ -169,7 +169,7 @@ Spyder is a python IDE tailored for the scientific community. It packs some real To change the interpreter, go to `tools` >> `Preferences` >> `Python interpreter`. You can either select the global interpreter defined by Spyder or you can enter the path to your own Python environment. -![Spyder Python Interpreter Selection](./img/Spyder-Config-Interpreter.png) +![Spyder Python Interpreter Selection](img/Spyder-Config-Interpreter.png) Do not forget to click `Apply` once you selected your interpreter. From 430bac3bc98378d2bb46603307c15e3f6695dbe5 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 30 Sep 2021 15:35:41 +0200 Subject: [PATCH 018/128] Images have absolute path --- docs/TESTS.md | 6 +++--- docs/TOOLS.md | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index dabe070b02..0fce5362cc 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -179,12 +179,12 @@ The *returned* directory is where your Python version is installed, in this tuto Click the `Windows Start` button and lookup *Edit the system environment variables* and press enter. Next press, `Environment Variables...`: -![Press the blue button, lol](img/Windows-SystemProperties.png) +![Press the blue button, lol](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-SystemProperties.png) Then find the `Path` variable in your *User variables*, select it, and click `Edit...`: -![Selecting the path variable](img/Windows-EnvironmentVariables.png) +![Selecting the path variable](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-EnvironmentVariables.png) Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: -![Add python to path](img/Windows-AddPythonPath.png) \ No newline at end of file +![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) \ No newline at end of file diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 19433cce93..002a8917dd 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -89,7 +89,7 @@ Packaged with *Anaconda*, `conda` environments are similar to [venv environments To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `environments` and then press the `Create` button. Fill in a name for your `conda` environment, select Python `3.8 or above` and click `Create`: -![Creating New Conda Environment](img/Anaconda-Conda-New.png) +![Creating New Conda Environment](https://raw.githubusercontent.com/exercism/python/main/docs/img/Anaconda-Conda-New.png) #### Activating your conda environment @@ -109,7 +109,7 @@ Visual studio code (VS Code) is a code editor created by Microsoft. It is not sp Extension: _Extension-id: ms-python.python_ -![Python Extension Header on VS Code](img/VSCode-EXT-Python-Header.png) +![Python Extension Header on VS Code](https://raw.githubusercontent.com/exercism/python/main/docs/img/VSCode-EXT-Python-Header.png) The Python extension from Microsoft is extremely useful because of its range of features. Notably it supports testing and has a testing explorer! It has many other features that you can view on [its homepage](https://marketplace.visualstudio.com/items?itemName=ms-python.python). @@ -119,7 +119,7 @@ The Python extensions supports the switching between multiple `interpreters`, th Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. -![Interpreter selection PT2](img/VSCode-EXT-Python-SelectInterpreter-2.png) +![Interpreter selection PT2](https://raw.githubusercontent.com/exercism/python/main/docs/img/VSCode-EXT-Python-SelectInterpreter-2.png) ##### Other features @@ -145,11 +145,11 @@ PyCharm is an *Integrated Development Environment* built by JetBrains. It is spe Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `Python Interpreter`. Click on the dropdown menu and select the environment you will be using. If the environment you would like to use is not in the list click on the `Show All...` button: -![Interpreter Selection Dropdown](img/PyCharm-Config-InterpreterDropDown.png) +![Interpreter Selection Dropdown](https://raw.githubusercontent.com/exercism/python/main/docs/img/PyCharm-Config-InterpreterDropDown.png) From there click on the `+` button to add a new interpreter. Select the type of interpreter on the left, we suggest you either run a [conda]() or [virtualenv]() environment, but running the *system interpreter* works fine too. Once you selected your interpreter, press the `Okay` button. -![Add New Interpreter](img/PyCharm-Config-InterpreterNew.png) +![Add New Interpreter](https://raw.githubusercontent.com/exercism/python/main/docs/img/PyCharm-Config-InterpreterNew.png) #### Other features @@ -169,7 +169,7 @@ Spyder is a python IDE tailored for the scientific community. It packs some real To change the interpreter, go to `tools` >> `Preferences` >> `Python interpreter`. You can either select the global interpreter defined by Spyder or you can enter the path to your own Python environment. -![Spyder Python Interpreter Selection](img/Spyder-Config-Interpreter.png) +![Spyder Python Interpreter Selection](https://raw.githubusercontent.com/exercism/python/main/docs/img/Spyder-Config-Interpreter.png) Do not forget to click `Apply` once you selected your interpreter. From a433864c2018da41e58bf6aea8cace58a1383128 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 17:56:25 +0200 Subject: [PATCH 019/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 0fce5362cc..4110ea6396 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -24,7 +24,7 @@ _Official Pytest documentation can be found on the [Pytest Wiki](https://pytest.org/en/latest/) page._ -Pytest let's you test your solutions using our provided tests, it is what we use to validate your solutions on the website. +Pytest lets you test your solutions using our provided tests, and is what we use to validate your solutions on the website. ### Installing Pytest From d2612d9a1ac442145738153c650eb394658679e9 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 17:56:38 +0200 Subject: [PATCH 020/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 4110ea6396..cc85cb739a 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -67,7 +67,7 @@ Successfully installed pytest-6.2.5 ... ### Running the tests -To run your test, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with the path_). +To run the tests, go to the folder where the exercise is stored using `cd` in your terminal (_replace `{exercise-folder-location}` below with the path_). ```bash $ cd {exercise-folder-location} From 584fd073ffcd731a0373643376efcaf23fa86916 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 17:56:58 +0200 Subject: [PATCH 021/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index cc85cb739a..2f8b5cf580 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -73,7 +73,9 @@ To run the tests, go to the folder where the exercise is stored using `cd` in yo $ cd {exercise-folder-location} ``` -The file you'll want always ends with `_test.py`, this file contains the tests for your solution, and are the same tests run on the website. Now run the following command in your terminal, replacing `{exercise_test.py}` with the location/name of the the testing file: +The file you'll want always ends with `_test.py`. +This file contains the tests for your solution, and are the same tests which run on the website. +Now run the following command in your terminal, replacing `{exercise_test.py}` with the location/name of the test file: ```bash $ python3 -m pytest {exercise_test.py} From 2e15fac1c9b64b7297c3d5727a00691bcf7746c7 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 17:59:11 +0200 Subject: [PATCH 022/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 2f8b5cf580..e6f4be3fd8 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -102,7 +102,7 @@ FAILED exercise_test.py::ExerciseTest::name_of_failed_test ### Extra arguments -If you really want to be specific about what Pytest returns on your screen, here are some handy arguments that will make Pytest return a more specific dataset. +If you really want to be specific about what Pytest returns on your screen, here are some handy arguments that allows you to configure its behavior. #### Stop After First Failure [`-x`] From 782c5df6d370978c885cd362d394f26679cfedbc Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 18:01:47 +0200 Subject: [PATCH 023/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index e6f4be3fd8..526cc8de73 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -156,7 +156,7 @@ $ python3 -m pytest --pdb bob_test.py =============== 4 passed in 0.15s =============== ``` -When a test fails it allows you to look at variables and how your code responds. If you want to learn how to really use `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. +When a test fails, `PDB` allows you to look at variables and how your code responds. If you want to learn how to use the `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. ## Extending your IDE From d00a764ee18afd882babc99de1076a35a8d5eb44 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 18:02:29 +0200 Subject: [PATCH 024/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 526cc8de73..ccd71b4d0c 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -160,7 +160,7 @@ When a test fails, `PDB` allows you to look at variables and how your code respo ## Extending your IDE -If you'd like to extend your IDE with some tools that will help you with testing/improving your code, check [this](./TOOLS.md) page. We go into multiple IDEs and editors and some useful extensions. +If you'd like to extend your IDE with some tools that will help you with testing and improving your code, check [this](./TOOLS.md) page. We go into multiple IDEs, editors and some useful extensions. ## Additional information From 5400ae34b51fd205d0c6bd9dcc80b015a3ec9046 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 18:11:43 +0200 Subject: [PATCH 025/128] Update docs/TOOLS.md Co-authored-by: Isaac Good --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 002a8917dd..4ad879fa74 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -93,7 +93,7 @@ To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `e #### Activating your conda environment -Activating your `conda` environment is easy, just click the `â–º` button next to your *environment*, then press `Open Terminal`. This should open a new terminal with an interface for your `conda` environment inside. +Activating your `conda` environment is easy, just click the `â–º` button next to your *environment*, then press `Open Terminal`. This should open a new terminal with an interface for your `conda` environment. From here you can run regular `pip` commands and other modules that you have installed inside your environment. All libraries will automatically be installed inside the `conda` environment. From 3e9b5060a3228cb1e9cc23d3b6e85c6afafbde38 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 21:50:49 +0200 Subject: [PATCH 026/128] Update docs/TOOLS.md Co-authored-by: Isaac Good --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 4ad879fa74..a913acbd14 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -195,7 +195,7 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma [Terminus](https://packagecontrol.io/packages/Terminus) - A terminal for your sublime. It is fully featured and support for Windows operating systems. This package is almost a must-have for you to be able to run `pytest` scripts. -[SublimeLinter](https://packagecontrol.io/packages/SublimeLinter) - A linter for sublime. This tool automatically notifies you of bad-practice python code. Useful for when you have it set up to use PEP8. +[SublimeLinter](https://packagecontrol.io/packages/SublimeLinter) - A linter for sublime. This tool notifies you about python code that does not follow best practices. We recommend you set it up to use PEP-8. ### JupyterLab From fcc43f9475a257cfc1625395f5693c2744f9dfee Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 21:52:15 +0200 Subject: [PATCH 027/128] Update docs/TESTS.md Co-authored-by: Isaac Good --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index ccd71b4d0c..563778f73c 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -106,7 +106,7 @@ If you really want to be specific about what Pytest returns on your screen, here #### Stop After First Failure [`-x`] -Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests when it encounters a failed test. This will help when you want to debug a single failure at a time. +Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests after the first failed test. This will help when you want to debug a single failure at a time. ```bash $ python -m pytest -x example_test.py From 7dc6c9ce5fa4daf6e50db3088d62344acfd7508e Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 21:53:15 +0200 Subject: [PATCH 028/128] Update docs/TOOLS.md Co-authored-by: Isaac Good --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index a913acbd14..7c03c627e1 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -199,7 +199,7 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma ### JupyterLab -Jupyter lab is an in-browser code editor that runs on your machine. You install it using a `pip` command in your *environment* and then automatically uses that environment. Jupyter is most well-known for being able to create coding notebooks. It is commonly used among data-scientists. +Jupyter lab is an in-browser code editor that runs on your machine. You can install it using a `pip` command in your *environment* and Jupyter will use that environment. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists. #### Notable extensions From 15ae0db661ccbc257f1d56d0e1005e808a3de3b9 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 21:55:10 +0200 Subject: [PATCH 029/128] Update docs/TOOLS.md Co-authored-by: Isaac Good --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 7c03c627e1..9ed37f266d 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -1,6 +1,6 @@ # Tools -A list of tools, IDEs and editors that can help you write and debug your *python* code. +A list of tools, IDEs and editors that can help you write and debug your *Python* code. *Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* From d6d57f593092a15b217b265cc886cf681e61834a Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 22:04:42 +0200 Subject: [PATCH 030/128] Update exercises/shared/.docs/tests.md Co-authored-by: Isaac Good --- exercises/shared/.docs/tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/shared/.docs/tests.md b/exercises/shared/.docs/tests.md index 533066c135..8fe55ae2f6 100644 --- a/exercises/shared/.docs/tests.md +++ b/exercises/shared/.docs/tests.md @@ -6,7 +6,7 @@ To run the included *test files*, run the test file using the `pytest` module, r $ python3 -m pytest {exercise_name}_test.py ``` -Many IDE's and code editors also have built-in support for using Pytest to run tests, check them out [here](https://github.com/exercism/python/blob/main/docs/TOOLS.md#editors-and-ides). +Many IDE's and code editors have built-in support for using Pytest to run tests; check them out [here](https://github.com/exercism/python/blob/main/docs/TOOLS.md#editors-and-ides). For more information about running tests using `pytest`, checkout our [Python testing guide](https://github.com/exercism/python/blob/main/docs/TESTS.md#pytest). From d770ab8bc46225a4dcd711e0d65f2f7fc21b8c17 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 22:04:53 +0200 Subject: [PATCH 031/128] Update exercises/shared/.docs/tests.md Co-authored-by: Isaac Good --- exercises/shared/.docs/tests.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/shared/.docs/tests.md b/exercises/shared/.docs/tests.md index 8fe55ae2f6..e3905ce64d 100644 --- a/exercises/shared/.docs/tests.md +++ b/exercises/shared/.docs/tests.md @@ -1,6 +1,6 @@ # Tests -To run the included *test files*, run the test file using the `pytest` module, replacing `{exercise_name}`: +To run the included *tests*, run the test file using the `pytest` module, replacing `{exercise_name}`: ```bash $ python3 -m pytest {exercise_name}_test.py From 994ad50bae89a7637072cc128fbf94d0b9f96bc8 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:01:05 +0200 Subject: [PATCH 032/128] Fixed capitalization --- docs/TESTS.md | 10 +++++----- docs/TOOLS.md | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 563778f73c..4f90109b3d 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -2,7 +2,7 @@ - [Tests](#tests) - [Pytest](#pytest) - - [Installing Pytest](#installing-pytest) + - [Installing pytest](#installing-pytest) - [Windows](#windows) - [Linux / MacOS](#linux--macos) - [Virtual environments](#virtual-environments) @@ -22,11 +22,11 @@ ## Pytest -_Official Pytest documentation can be found on the [Pytest Wiki](https://pytest.org/en/latest/) page._ +_Official pytest documentation can be found on the [pytest Wiki](https://pytest.org/en/latest/) page._ Pytest lets you test your solutions using our provided tests, and is what we use to validate your solutions on the website. -### Installing Pytest +### Installing pytest Pytest can be installed and updated using the built-in Python utility `pip`. @@ -58,7 +58,7 @@ If you do not want to precede every command with `python3 -m` please refer to [a *For more information about virtual environments please refer to the [TOOLS](.\TOOLS.md) file.* -When installing Pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-environment). After which you can run: +When installing pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-environment). After which you can run: ```bash $ pip install pytest pytest-cache pytest-subtests pytest-pylint @@ -102,7 +102,7 @@ FAILED exercise_test.py::ExerciseTest::name_of_failed_test ### Extra arguments -If you really want to be specific about what Pytest returns on your screen, here are some handy arguments that allows you to configure its behavior. +If you really want to be specific about what pytest returns on your screen, here are some handy arguments that allows you to configure its behavior. #### Stop After First Failure [`-x`] diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 9ed37f266d..024e8d1cc3 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -1,10 +1,10 @@ # Tools -A list of tools, IDEs and editors that can help you write and debug your *Python* code. +A list of tools, IDEs and editors that can help you write and debug your _Python_ code. *Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* -Before you can start coding, make sure that you have the proper version of python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/python/installation). +Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/Python/installation). --- @@ -89,7 +89,7 @@ Packaged with *Anaconda*, `conda` environments are similar to [venv environments To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `environments` and then press the `Create` button. Fill in a name for your `conda` environment, select Python `3.8 or above` and click `Create`: -![Creating New Conda Environment](https://raw.githubusercontent.com/exercism/python/main/docs/img/Anaconda-Conda-New.png) +![Creating New Conda Environment](https://raw.githubusercontent.com/exercism/Python/main/docs/img/Anaconda-Conda-New.png) #### Activating your conda environment @@ -123,9 +123,9 @@ Click on the "Select interpreter" button in the lower left-hand of your window, ##### Other features -The Python plugin also comes with some other features that can help you debug and improve your python code, here are some of those tools. +The Python plugin also comes with some other features that can help you debug and improve your Python code, here are some of those tools. -[Test discovery tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - A tool that generates a tree containing all the *Pytest* and *Unittest* inside a directory. It will give you an easier and more readable interface for *Pytest*. +[Test discovery tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - A tool that generates a tree containing all the *pytests* and *unittests* inside a directory. It will give you an easier and more readable interface for *pytest*. [Debugging tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool then allows you to view all *private* and *global* variables at that point in your program. @@ -163,7 +163,7 @@ Some other features that we won't cover in this guide, but are really useful for ### Spyder IDE -Spyder is a python IDE tailored for the scientific community. It packs some really good debugging utilities and it has an integrated *IPython terminal*. Out-of-the-box it does not come with support for *Unittests* and *Pytests*, but there is an official plugin available. +Spyder is a Python IDE tailored for the scientific community. It packs some really good debugging utilities and it has an integrated *IPython terminal*. Out-of-the-box it does not come with support for *unittests* and *pytests*, but there is an official plugin available. #### Selecting the interpreter @@ -179,7 +179,7 @@ Do not forget to click `Apply` once you selected your interpreter. [Code analyzer](https://docs.spyder-ide.org/current/panes/pylint.html#using-the-code-analyzer) - An integrated tool for analyzing your code, detecting bad practices, and catching bugs before you run your program. You can also configure it to only catch [PEP 8](https://www.python.org/dev/peps/pep-0008/) violations. -[Debugging](https://docs.spyder-ide.org/current/panes/debugging.html) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool will then let you go through your python code and quickly notice where it goes wrong. +[Debugging](https://docs.spyder-ide.org/current/panes/debugging.html) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool will then let you go through your Python code and quickly notice where it goes wrong. [Variable Explorer](https://docs.spyder-ide.org/current/panes/variableexplorer.html) - This tool is really useful in combination with the debugger. It allows you to view all the active variables in your code and it even allows editing of the values inside those variables. @@ -191,11 +191,11 @@ A text editor for coding, made by *Sublime HQ Pty Ltd*. It is similar to [VS Cod #### Notable extensions -Sublime Text comes with a lot of tools already, but some of these tools could make your python development easier. Make sure you have *package control* installed, go to `Tools` >> `Install Package Control...`. To install packages open your command pallet and type in `Package Control: Install Package`, here you can enter the name of the packages you'd like to install. +Sublime Text comes with a lot of tools already, but some of these tools could make your Python development easier. Make sure you have *package control* installed, go to `Tools` >> `Install Package Control...`. To install packages open your command pallet and type in `Package Control: Install Package`, here you can enter the name of the packages you'd like to install. [Terminus](https://packagecontrol.io/packages/Terminus) - A terminal for your sublime. It is fully featured and support for Windows operating systems. This package is almost a must-have for you to be able to run `pytest` scripts. -[SublimeLinter](https://packagecontrol.io/packages/SublimeLinter) - A linter for sublime. This tool notifies you about python code that does not follow best practices. We recommend you set it up to use PEP-8. +[SublimeLinter](https://packagecontrol.io/packages/SublimeLinter) - A linter for sublime. This tool notifies you about Python code that does not follow best practices. We recommend you set it up to use PEP-8. ### JupyterLab From f4224c06ed130688c0c86e211ce8b20fba87b87e Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:03:28 +0200 Subject: [PATCH 033/128] Update docs/TOOLS.md Co-authored-by: Isaac Good --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 024e8d1cc3..25311b510f 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -127,7 +127,7 @@ The Python plugin also comes with some other features that can help you debug an [Test discovery tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - A tool that generates a tree containing all the *pytests* and *unittests* inside a directory. It will give you an easier and more readable interface for *pytest*. -[Debugging tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool then allows you to view all *private* and *global* variables at that point in your program. +[Debugging tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - This tool will help you debug your code. You can set a `breakpoint` on a line. The debugging tool then allows you to view all *private* and *global* variables at that point in your program. [Linting](https://code.visualstudio.com/docs/python/testing#_configure-tests) - Linting looks out for simple mistakes in your code and notifies you when you write bad-practice code according to PEP. Exercism currently focusses on the correct use of [PEP 8](https://www.python.org/dev/peps/pep-0008/). From 75b0ad6ec177e31d982ca4c63c69e1291d6ab33c Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:10:29 +0200 Subject: [PATCH 034/128] Fixed language Co-authored-by: Isaac Good --- docs/TOOLS.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 25311b510f..0df814083c 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -42,7 +42,7 @@ Before you can start coding, make sure that you have the proper version of Pytho Python environments are like separate Python installations, they can cleanup your workflow and projects by separating the packages you install. Making sure that you don't get bugs generated by something that you imported in another project. -There are two major *virtual environments* in use today, `virtualenv` and `conda`. Both of which are generally easy to install. +There are two major *virtual environments* in use today, `virtualenv` and `conda`. Both of are easy to install. ### Venv @@ -50,7 +50,7 @@ Also known as `virtualenv`, *venv* is a light-weight solution to virtual environ #### Creating your virtual environment -To create a virtual environment, `cd` to the directory you want to put the *venv* in. Then run the `virtualenv` command with the name folder which will store your *environment*, common convention is to call it `venv`: +To create a virtual environment, `cd` to the directory you want to put the *venv* in. Then run the `virtualenv` command with the name of the folder where you want to store your *environment*. Common convention is to call that folder `venv`: ```bash $ python3 -m virtualenv {name_of_virtualenv} @@ -73,19 +73,19 @@ $ source {name_of_virtual_env}/bin/activate (venv) $ _ ``` -From this terminal you can now run `pip` commands. All of the packages installed in the environment will go to `{name_of_virtual_env}/Lib`. +From this terminal you can now run `pip` commands. All of the packages installed in the environment will be installed inside `{name_of_virtual_env}/Lib`. #### Virtual Environment wrapper Reference: [Wiki](https://virtualenvwrapper.readthedocs.io/en/latest/) -The `virtualenvwrapper` module manages all your virtual environments in one place. You can create, copy, delete and switch between environments using the tools the module provides. It also allows for extensions so you can add tools you'd like. You can create your own extension using the tutorial found [here](https://virtualenvwrapper.readthedocs.io/en/latest/plugins.html#plugins). +The `virtualenvwrapper` module manages all your virtual environments in one place. You can create, copy, delete and switch between environments using the tools the module provides. It also allows you to add additional tools using extensions. You can create your own extension using the tutorial found [here](https://virtualenvwrapper.readthedocs.io/en/latest/plugins.html#plugins). ### Conda *Latest download can be found [here](https://www.anaconda.com/products/individual)* -Packaged with *Anaconda*, `conda` environments are similar to [venv environments](#venv) with the key difference being that `conda` can support the `R programming language`. Anaconda is most commonly used by researchers and data scientists, because of its set of tools and programs tailored to them. The [Spyder IDE](#spider-ide) is one of the tools that comes with Anaconda. +Packaged with *Anaconda*, `conda` environments are similar to [venv environments](#venv) with the key difference being that `conda` can support the `R programming language`. Anaconda is most commonly used by researchers and data scientists because it contains a set of tools and programs tailored to them. The [Spyder IDE](#spider-ide) is one of the tools that comes with Anaconda. To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `environments` and then press the `Create` button. Fill in a name for your `conda` environment, select Python `3.8 or above` and click `Create`: @@ -115,7 +115,7 @@ The Python extension from Microsoft is extremely useful because of its range of ##### Selecting the interpreter -The Python extensions supports the switching between multiple `interpreters`, this way you can use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you find more about [here](#environments). +The Python extensions supports the switching between multiple `interpreters`. This allows you to use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you can find more about [here](#environments). Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. @@ -129,17 +129,17 @@ The Python plugin also comes with some other features that can help you debug an [Debugging tool](https://code.visualstudio.com/docs/python/testing#_configure-tests) - This tool will help you debug your code. You can set a `breakpoint` on a line. The debugging tool then allows you to view all *private* and *global* variables at that point in your program. -[Linting](https://code.visualstudio.com/docs/python/testing#_configure-tests) - Linting looks out for simple mistakes in your code and notifies you when you write bad-practice code according to PEP. Exercism currently focusses on the correct use of [PEP 8](https://www.python.org/dev/peps/pep-0008/). +[Linting](https://code.visualstudio.com/docs/python/testing#_configure-tests) - Linting looks out for simple mistakes in your code and notifies you when you write code which does not follow best practices according to PEP. Exercism currently focuses on following [PEP 8](https://www.python.org/dev/peps/pep-0008/). -[Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) - This tool automatically formats your code to look a certain way, we recommend `autopep8` because it adheres to [PEP 8](https://www.python.org/dev/peps/pep-0008/), other formatters are supported. +[Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) - This tool automatically formats your code to look a certain way. We recommend `autopep8` because it adheres to [PEP 8](https://www.python.org/dev/peps/pep-0008/). Other formatters are also supported. -[Config reference](https://code.visualstudio.com/docs/python/settings-reference) - If you want to make your Python development on VS Code behave exactly like you want it to, view this reference guide. It explains options for the extension that can really improve your coding experience. +[Config reference](https://code.visualstudio.com/docs/python/settings-reference) - If you want to configure your Python development on VS Code to behave exactly like you want it, view this reference guide. It explains options for the extensions which can really improve your coding experience. --- ### PyCharm -PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can also extend it's features using plugins, but out-of-the-box it comes with a load of features pre-installed. +PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can extend it plugins, but out-of-the-box it comes with a load of features already available. #### Selecting the interpreter @@ -147,13 +147,13 @@ Open your project, then navigate to `File` >> `Settings` >> `Project: ...` >> `P ![Interpreter Selection Dropdown](https://raw.githubusercontent.com/exercism/python/main/docs/img/PyCharm-Config-InterpreterDropDown.png) -From there click on the `+` button to add a new interpreter. Select the type of interpreter on the left, we suggest you either run a [conda]() or [virtualenv]() environment, but running the *system interpreter* works fine too. Once you selected your interpreter, press the `Okay` button. +From there click on the `+` button to add a new interpreter. Select the type of interpreter on the left. We suggest you either run a [conda]() or [virtualenv]() environment, but running the *system interpreter* works fine, too. Once you selected your interpreter, press the `Okay` button. ![Add New Interpreter](https://raw.githubusercontent.com/exercism/python/main/docs/img/PyCharm-Config-InterpreterNew.png) #### Other features -Some other features that we won't cover in this guide, but are really useful for development are: +Below are some other features that we won't cover in this guide, but are really useful for development. [Running Tests](https://www.jetbrains.com/help/pycharm/pytest.html#run-pytest-test) - Running tests directly from a GUI in your window is really easy, but don't forget to look at the [pytest parameters](TESTS.md#extra-arguments) to set it up to your liking. @@ -175,25 +175,25 @@ Do not forget to click `Apply` once you selected your interpreter. #### Other features -[Spyder Unittest](https://github.com/spyder-ide/spyder-unittest/releases/latest) - If you want to have a built-in interface for you tests, install this plugin. Clicking the link will bring you to the latest release on GitHub. +[Spyder Unittest](https://github.com/spyder-ide/spyder-unittest/releases/latest) - This plugin gives you a built-in interface for running your tests. This link will bring you to the latest release on GitHub. -[Code analyzer](https://docs.spyder-ide.org/current/panes/pylint.html#using-the-code-analyzer) - An integrated tool for analyzing your code, detecting bad practices, and catching bugs before you run your program. You can also configure it to only catch [PEP 8](https://www.python.org/dev/peps/pep-0008/) violations. +[Code analyzer](https://docs.spyder-ide.org/current/panes/pylint.html#using-the-code-analyzer) - This tool can analyze your code, detecting bad practices, and catching bugs before you run your program. You can also configure it to only show [PEP 8](https://www.python.org/dev/peps/pep-0008/) violations. [Debugging](https://docs.spyder-ide.org/current/panes/debugging.html) - This tool will help you debug your code, it allows you to set a `breakpoint` on a line. The debugging tool will then let you go through your Python code and quickly notice where it goes wrong. -[Variable Explorer](https://docs.spyder-ide.org/current/panes/variableexplorer.html) - This tool is really useful in combination with the debugger. It allows you to view all the active variables in your code and it even allows editing of the values inside those variables. +[Variable Explorer](https://docs.spyder-ide.org/current/panes/variableexplorer.html) - This tool is really useful in combination with the debugger. It allows you to view all the active variables in your code and it even allows you to edit of the values inside those variables. --- ### Sublime text -A text editor for coding, made by *Sublime HQ Pty Ltd*. It is similar to [VS Code](#visual-studio-code) in a lot of regards. Sublime text is almost fully customizable. It also has a built-in auto-complete engine and comes with Python syntax highlighting out-of-the-box. +Sublime text is text editor for coding, made by *Sublime HQ Pty Ltd*. It is similar to [VS Code](#visual-studio-code) in many regards. Sublime text is very customizable. It comes with an auto-complete engine and with Python syntax highlighting out-of-the-box. #### Notable extensions Sublime Text comes with a lot of tools already, but some of these tools could make your Python development easier. Make sure you have *package control* installed, go to `Tools` >> `Install Package Control...`. To install packages open your command pallet and type in `Package Control: Install Package`, here you can enter the name of the packages you'd like to install. -[Terminus](https://packagecontrol.io/packages/Terminus) - A terminal for your sublime. It is fully featured and support for Windows operating systems. This package is almost a must-have for you to be able to run `pytest` scripts. +[Terminus](https://packagecontrol.io/packages/Terminus) - A terminal for Sublime. Terminus is fully featured and has support for Windows operating systems. This package is a must-have for running `pytest` scripts. [SublimeLinter](https://packagecontrol.io/packages/SublimeLinter) - A linter for sublime. This tool notifies you about Python code that does not follow best practices. We recommend you set it up to use PEP-8. From cb990e3ee5b6c1c9b8d4f14bcdcf33e4cf35f9a4 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:11:03 +0200 Subject: [PATCH 035/128] Update docs/TOOLS.md Co-authored-by: Isaac Good --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 0df814083c..dbacce8186 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -40,7 +40,7 @@ Before you can start coding, make sure that you have the proper version of Pytho ## Environments -Python environments are like separate Python installations, they can cleanup your workflow and projects by separating the packages you install. Making sure that you don't get bugs generated by something that you imported in another project. +Python environments are like separate python installations: they can organize your workflow and projects by keeping the packages you install inside that project. This helps avoid bugs generated by something that you imported in another project. There are two major *virtual environments* in use today, `virtualenv` and `conda`. Both of are easy to install. From 092a25bd7176086a7bf9749d7c4aa0eea0b3afe9 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:12:49 +0200 Subject: [PATCH 036/128] Update docs/TOOLS.md --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index dbacce8186..b7b112bf6f 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -40,7 +40,7 @@ Before you can start coding, make sure that you have the proper version of Pytho ## Environments -Python environments are like separate python installations: they can organize your workflow and projects by keeping the packages you install inside that project. This helps avoid bugs generated by something that you imported in another project. +Python environments are like separate Python installations: they can organize your workflow and projects by keeping the packages you install inside that project. This helps avoid bugs generated by something that you imported in another project. There are two major *virtual environments* in use today, `virtualenv` and `conda`. Both of are easy to install. From 95b28cced537207e86575fcd3d24fb9187ebee25 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:14:24 +0200 Subject: [PATCH 037/128] Update docs/TOOLS.md --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index b7b112bf6f..7be80877be 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -157,7 +157,7 @@ Below are some other features that we won't cover in this guide, but are really [Running Tests](https://www.jetbrains.com/help/pycharm/pytest.html#run-pytest-test) - Running tests directly from a GUI in your window is really easy, but don't forget to look at the [pytest parameters](TESTS.md#extra-arguments) to set it up to your liking. -[Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm can get really extensive, but can be really useful as well. Start here to learn how to properly debug using PyCharm. +[Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm is a great way to take advantage of it's tools. Start here to learn how to properly debug using PyCharm. --- From 1ff0be9dc64b7dee46b1af113f7e1f6d55a0ace9 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:15:22 +0200 Subject: [PATCH 038/128] Update docs/TESTS.md --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 4f90109b3d..d22dff5310 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -168,7 +168,7 @@ If you'd like to extend your IDE with some tools that will help you with testing **Note:** If you are running a [virtual environment](.\TOOLS.md) you do not need to *add to path* as it should work fine. -Preceding every module you want to run with `python3 -m` might get a little annoying. You can add the `Scripts` folder of your Python installation to your path. If you do not know where you have installed Python, run the following command in your terminal: +Typing `python3 -m` every time you want to run a module can get a little annoying. You can add the `Scripts` folder of your Python installation to your path. If you do not know where you have installed Python, run the following command in your terminal: ```bash $ python3 -c "import os, sys; print(os.path.dirname(sys.executable))" From 6c1328cba8348afe1f7507e1bc4dee317bc8b7ae Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:16:11 +0200 Subject: [PATCH 039/128] Update docs/TOOLS.md --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 7be80877be..8161a2c77f 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -103,7 +103,7 @@ From here you can run regular `pip` commands and other modules that you have ins ### Visual Studio Code -Visual studio code (VS Code) is a code editor created by Microsoft. It is not specialized to work for a specific programming language, but to be an editor that can do everything. You can extend the editor using extensions, but it comes with some great extensions as well. +Visual studio code (VS Code) is a code editor created by Microsoft. It is a general-purpose editor and not designed specifically for any one language. You can extend the editor using extensions; it comes with some great extensions already installed. #### Python for VS Code From 170d298d152644a8becff45b538a97b4c8a0b3ed Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Sat, 2 Oct 2021 23:20:16 +0200 Subject: [PATCH 040/128] Update docs/TOOLS.md --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 8161a2c77f..895f855728 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -203,4 +203,4 @@ Jupyter lab is an in-browser code editor that runs on your machine. You can inst #### Notable extensions -The built-in tools for Jupyter Lab are good-enough for almost all exercises on *Exercism*. Most extensions made for Jupyter are made with data-science in mind. Explore extensions at your own pleasure. +The built-in tools for Jupyter Lab are sufficient for almost all exercises on Exercism. Most extensions to Jupyter focus more on data-science applications and not general-purpose programming. Explore extensions at your own pleasure. From 92c6c4e52e91f620b85f3a1394ad4dbec0a12937 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:43:12 +0200 Subject: [PATCH 041/128] Update docs/TESTS.md Co-authored-by: Mukesh Gurpude <55982424+mukeshgurpude@users.noreply.github.com> --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index d22dff5310..6df98066b9 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -58,7 +58,7 @@ If you do not want to precede every command with `python3 -m` please refer to [a *For more information about virtual environments please refer to the [TOOLS](.\TOOLS.md) file.* -When installing pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-environment). After which you can run: +When installing pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-virtual-environment). After which you can run: ```bash $ pip install pytest pytest-cache pytest-subtests pytest-pylint From 85ae17e579b89c3de23074dd7cb63f301be0692e Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:45:26 +0200 Subject: [PATCH 042/128] Update docs/TESTS.md Co-authored-by: Mukesh Gurpude <55982424+mukeshgurpude@users.noreply.github.com> --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 6df98066b9..2dbd09db75 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -160,7 +160,7 @@ When a test fails, `PDB` allows you to look at variables and how your code respo ## Extending your IDE -If you'd like to extend your IDE with some tools that will help you with testing and improving your code, check [this](./TOOLS.md) page. We go into multiple IDEs, editors and some useful extensions. +If you'd like to extend your IDE with some tools that will help you with testing and improving your code, check the [TOOLS](./TOOLS.md) page. We go into multiple IDEs, editors and some useful extensions. ## Additional information From f053a2211e9e010cd1dc8b2921c7b0745f60d5ef Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:45:55 +0200 Subject: [PATCH 043/128] Update docs/TESTS.md Co-authored-by: Mukesh Gurpude <55982424+mukeshgurpude@users.noreply.github.com> --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 2dbd09db75..c18690d2b2 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -56,7 +56,7 @@ If you do not want to precede every command with `python3 -m` please refer to [a #### Virtual environments -*For more information about virtual environments please refer to the [TOOLS](.\TOOLS.md) file.* +*For more information about virtual environments please refer to the [TOOLS](./TOOLS.md) file.* When installing pytest or any other module(s), make sure that you have [activated your environment](.\TOOLS.md#activating-your-virtual-environment). After which you can run: From b8ad4b55757c52a3cb1159272c1595cd240e103b Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 4 Oct 2021 19:48:29 +0200 Subject: [PATCH 044/128] Update docs/TOOLS.md Co-authored-by: Mukesh Gurpude <55982424+mukeshgurpude@users.noreply.github.com> --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 895f855728..1ce6e3f48c 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -157,7 +157,7 @@ Below are some other features that we won't cover in this guide, but are really [Running Tests](https://www.jetbrains.com/help/pycharm/pytest.html#run-pytest-test) - Running tests directly from a GUI in your window is really easy, but don't forget to look at the [pytest parameters](TESTS.md#extra-arguments) to set it up to your liking. -[Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm is a great way to take advantage of it's tools. Start here to learn how to properly debug using PyCharm. +[Debug Tools](https://www.jetbrains.com/help/pycharm/debugging-code.html) - Debugging in PyCharm is a great way to take advantage of its tools. --- From 568de19eebbfbf4ca5f10bb77b88874427850748 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Wed, 6 Oct 2021 21:44:23 +0200 Subject: [PATCH 045/128] Layed out ground work --- .../concept/plane-tickets/.docs/hints.md | 0 .../plane-tickets/.docs/instructions.md | 0 .../plane-tickets/.docs/introduction.md | 0 .../concept/plane-tickets/.meta/config.json | 11 +++ .../concept/plane-tickets/.meta/design.md | 50 +++++++++++++ .../concept/plane-tickets/.meta/exemplar.py | 72 +++++++++++++++++++ .../concept/plane-tickets/plane_tickets.py | 51 +++++++++++++ .../plane-tickets/plane_tickets_test.py | 0 8 files changed, 184 insertions(+) create mode 100644 exercises/concept/plane-tickets/.docs/hints.md create mode 100644 exercises/concept/plane-tickets/.docs/instructions.md create mode 100644 exercises/concept/plane-tickets/.docs/introduction.md create mode 100644 exercises/concept/plane-tickets/.meta/config.json create mode 100644 exercises/concept/plane-tickets/.meta/design.md create mode 100644 exercises/concept/plane-tickets/.meta/exemplar.py create mode 100644 exercises/concept/plane-tickets/plane_tickets.py create mode 100644 exercises/concept/plane-tickets/plane_tickets_test.py diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json new file mode 100644 index 0000000000..18a44a57c0 --- /dev/null +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -0,0 +1,11 @@ +{ + "blurb": "Learn about generators by assigning seats to passengers.", + "authors": ["J08K"], + "icon": "poker", + "contributors": [], + "files": { + "solution": ["plane_tickets.py"], + "test": ["plane_tickets_test.py"], + "exemplar": [".meta/exemplar.py"] + } +} diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md new file mode 100644 index 0000000000..1bf4f13582 --- /dev/null +++ b/exercises/concept/plane-tickets/.meta/design.md @@ -0,0 +1,50 @@ +## Goal + +The goal of this exercise is to teach the syntax and use of generators in Python. + +## Learning objectives + +- Understand what generators are and how/when to use them +- Understand how generators relate to `loops` and `iterators` +- Understand how to use the `yield` keyword +- Understand the `__next__()` method +- Create a generator + +## Out of scope + +- rich comparison with `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__` +- understanding (_and using the concept_) that the `==` operator calls the dunder method `__eq__()` on a specific object, and uses that object's implementation for comparison. Where no implementation is present, the default `__eq__()` from generic `object` is used. +- overloading the default implementation of the `__eq__()` dunder method on a specific object to customize comparison behavior. +- `set operations` +- performance considerations + +## Concepts + +- Memory and performance characteristics and optimizations +- `throw(type, value=None, traceback=None)` +- `close()` +- `generator expressions` +- `yield from` +- `generators` used as coroutines + +## Prerequisites + +- `conditionals` +- `dicts` +- `functions` +- `higher-order-functions` +- `lists` +- `loops` +- `iteration` +- `iterators` +- `sequences` + +## Resources + +- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) +- [generator (Python official docs glossary)](https://docs.python.org/3/glossary.html#term-generator) +- [The yield statement (Python official docs)](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) +- [Yield expressions (Python official docs)](https://docs.python.org/3/reference/expressions.html#yieldexpr) +- [Iterators(Python official docs)](https://docs.python.org/3/howto/functional.html?#iterators) +- [Generator-iterator methods (Python official docs)](https://docs.python.org/3/reference/expressions.html#generator-iterator-methods) +- [How to Use Generators and yield in Python (Real Python)](https://realpython.com/introduction-to-python-generators/) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py new file mode 100644 index 0000000000..0b6c7b3208 --- /dev/null +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -0,0 +1,72 @@ +def value_of_card(card): + """ + + :param card: str - given card. + :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). + """ + + if card == 'J' or card == 'Q' or card == 'K': + value = 10 + else: + value = int(card) + return value + + +def value_of_ace(hand_value): + """ + + :param hand_value: int - current hand value. + :return: int - value of the upcoming ace card (either 1 or 11). + """ + + if hand_value + 11 > 21: + value = 1 + else: + value = 11 + return value + + +def is_blackjack(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand is a blackjack (two cards worth 21). + """ + + if card_one == 'A' and card_two != 'A': + blackjack = value_of_card(card_two) == 10 + elif card_one != 'A' and card_two == 'A': + blackjack = value_of_card(card_one) == 10 + else: + blackjack = False + return blackjack + + +def can_split_pairs(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). + """ + + if card_one == 'A' or card_two == 'A': + split_pairs = card_one == card_two + else: + split_pairs = value_of_card(card_one) == value_of_card(card_two) + return split_pairs + + +def can_double_down(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). + """ + + card_one_value = 1 if card_one == 'A' else value_of_card(card_one) + card_two_value = 1 if card_two == 'A' else value_of_card(card_two) + hand_value = card_one_value + card_two_value + return 9 <= hand_value <= 11 diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py new file mode 100644 index 0000000000..7343184f97 --- /dev/null +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -0,0 +1,51 @@ +def value_of_card(card): + """ + + :param card: str - given card. + :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). + """ + + pass + + +def value_of_ace(hand_value): + """ + + :param hand_value: int - current hand value. + :return: int - value of the upcoming ace card (either 1 or 11). + """ + + pass + + +def is_blackjack(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand is a blackjack (two cards worth 21). + """ + + pass + + +def can_split_pairs(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). + """ + + pass + + +def can_double_down(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). + """ + + pass diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py new file mode 100644 index 0000000000..e69de29bb2 From f2d797a1a898470ea94484b2d235e69ba852e5bb Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 7 Oct 2021 21:02:36 +0200 Subject: [PATCH 046/128] Write introduction --- .../plane-tickets/.docs/introduction.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index e69de29bb2..7afa7f9785 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -0,0 +1,80 @@ +# Instructions + +A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). + +_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. + +## Constructing a generator + +Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). + +Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: + +```python +>>> def squares(list_of_numbers): +>>> for number in list_of_numbers: +>>> yield number ** 2 +``` + +## Using a generator + +It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. + +For example, if you want to use the `squares()` generator we just constructed, we simply use: + +```python +>>> list_of_numbers = [1, 2, 3, 4] + +>>> for square in squares(list_of_numbers): +>>> print(square) + 1 +4 +9 +16 +``` + +You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. + +```python +square_generator = squares([1, 2]) + +>>> next(square_generator) +1 +>>> next(square_generator) +4 +``` + +When the `generator` has no more values to return it throws a `StopIteration` error. + +```python +>>> next(square_generator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +## The yield expression + +The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. + +When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. + +`yield` expressions are _prohibited_ to be used outside of functions. + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. + +You can also use it to generate complicated or infinite sequences, like this: + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. From e6b8588cb0900b5f035d2318024ab8635dd2a4fc Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 7 Oct 2021 23:01:34 +0200 Subject: [PATCH 047/128] Update docs/TESTS.md Co-authored-by: ynfle <23086821+ynfle@users.noreply.github.com> --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index c18690d2b2..0c4d55ea58 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -40,7 +40,7 @@ Successfully installed pytest-6.2.5 ... #### Linux / MacOS ```bash -$ sudo python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint +$ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint Successfully installed pytest-6.2.5 ... ``` From c913f7582c65e60fde8e63fc875ba44b48be41b1 Mon Sep 17 00:00:00 2001 From: J08K Date: Thu, 14 Oct 2021 00:37:33 +0200 Subject: [PATCH 048/128] Added pytest.ini docs --- docs/TESTS.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 0c4d55ea58..8c4d4b524c 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -17,6 +17,7 @@ - [Additional information](#additional-information) - [Adding to PATH](#adding-to-path) - [Windows](#windows-1) + - [Fixing warnings](#fixing-warnings) --- @@ -189,4 +190,22 @@ Then find the `Path` variable in your *User variables*, select it, and click `Ed Then add a new line, as shown in the picture, replacing `{python_directory}` with your Python installation's directory: -![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) \ No newline at end of file +![Add python to path](https://raw.githubusercontent.com/exercism/python/main/docs/img/Windows-AddPythonPath.png) + +### Fixing warnings + +It is possible that you will get `warnings` when running a test that uses _our_ new syntax. + +To solve this, we use a `pytest.ini` file, which can be downloaded [here](https://github.com/exercism/python/blob/main/pytest.ini). + +You can also create a file containing the following: + +```ini +[pytest] +markers = + task: A concept exercise task. +``` + +Whenever you run your tests, make sure that this file is in your _root_ or _working_ directory. + +_More information on customizing pytest can be found [here](https://docs.pytest.org/en/6.2.x/customize.html#pytest-ini)_ \ No newline at end of file From 81abb8059aef747ce7e7841ee29cdcf6d39bddf3 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 01:04:34 +0200 Subject: [PATCH 049/128] Update docs/TESTS.md Co-authored-by: BethanyG --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 8c4d4b524c..9a49956eca 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -196,7 +196,7 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit It is possible that you will get `warnings` when running a test that uses _our_ new syntax. -To solve this, we use a `pytest.ini` file, which can be downloaded [here](https://github.com/exercism/python/blob/main/pytest.ini). +To solve this, we use a `pytest.ini` file, which can be downloaded from the top level of the Python track directory: [pytest.ini](https://github.com/exercism/python/blob/main/pytest.ini). You can also create a file containing the following: From 3684cdd7ba7ecba39bc8185f70a54ccff08f086e Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 01:04:45 +0200 Subject: [PATCH 050/128] Update docs/TESTS.md Co-authored-by: BethanyG --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 9a49956eca..92e88ae42e 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -198,7 +198,7 @@ It is possible that you will get `warnings` when running a test that uses _our_ To solve this, we use a `pytest.ini` file, which can be downloaded from the top level of the Python track directory: [pytest.ini](https://github.com/exercism/python/blob/main/pytest.ini). -You can also create a file containing the following: +You can also create your own file with the following content: ```ini [pytest] From 3d66a5c4471839dc63a9df2ff3243216ed4f528e Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 01:04:58 +0200 Subject: [PATCH 051/128] Update docs/TESTS.md Co-authored-by: BethanyG --- docs/TESTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 92e88ae42e..e3885342d3 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -208,4 +208,4 @@ markers = Whenever you run your tests, make sure that this file is in your _root_ or _working_ directory. -_More information on customizing pytest can be found [here](https://docs.pytest.org/en/6.2.x/customize.html#pytest-ini)_ \ No newline at end of file +_More information on customizing pytest can be found in the [PyTest docs](https://docs.pytest.org/en/6.2.x/customize.html#pytest-ini)_ \ No newline at end of file From 8f2692ba62e25d034e5180202a77ee34f6471a32 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:02:44 +0200 Subject: [PATCH 052/128] Update docs/TOOLS.md Co-authored-by: BethanyG --- docs/TOOLS.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 1ce6e3f48c..b3f8a3dff2 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -199,7 +199,9 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma ### JupyterLab -Jupyter lab is an in-browser code editor that runs on your machine. You can install it using a `pip` command in your *environment* and Jupyter will use that environment. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists. +[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. + +Jupyter comes pre-packaged with Anaconda. You can also install it in a **virtual environment** using `pip` or `conda`. Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists. #### Notable extensions From 822a54be6d8076330147e25cfac93eb0e8f58007 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:12:13 +0200 Subject: [PATCH 053/128] Apply suggestions from code review Co-authored-by: BethanyG --- docs/TESTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index e3885342d3..af773d629f 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -27,7 +27,7 @@ _Official pytest documentation can be found on the [pytest Wiki](https://pytest. Pytest lets you test your solutions using our provided tests, and is what we use to validate your solutions on the website. -### Installing pytest +### Installing pytest Globally Pytest can be installed and updated using the built-in Python utility `pip`. @@ -55,7 +55,7 @@ pytest 6.2.5 If you do not want to precede every command with `python3 -m` please refer to [adding to PATH](#adding-to-path) at the end of this document. -#### Virtual environments +#### Installing pytest within a Virtual environment *For more information about virtual environments please refer to the [TOOLS](./TOOLS.md) file.* @@ -148,7 +148,7 @@ pytest -x -ff bob_test.py This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you run the test again, `pytest` will first test that failed test, then continue with the rest. -#### Python Debugger +#### Using PDB, the Python Debugger, with pytest If you want to truly debug like a pro, use the `--pdb` argument after the `pytest` command. @@ -165,7 +165,7 @@ If you'd like to extend your IDE with some tools that will help you with testing ## Additional information -### Adding to PATH +### Adding pytest to your PATH **Note:** If you are running a [virtual environment](.\TOOLS.md) you do not need to *add to path* as it should work fine. @@ -194,9 +194,9 @@ Then add a new line, as shown in the picture, replacing `{python_directory}` wit ### Fixing warnings -It is possible that you will get `warnings` when running a test that uses _our_ new syntax. +It is possible that you will get `warnings` about "unknown markers" when running a test that uses our _new_ syntax. -To solve this, we use a `pytest.ini` file, which can be downloaded from the top level of the Python track directory: [pytest.ini](https://github.com/exercism/python/blob/main/pytest.ini). +To resolve this issue, we use a `pytest.ini` file, which can be downloaded from the top level of the Python track directory: [pytest.ini](https://github.com/exercism/python/blob/main/pytest.ini). You can also create your own file with the following content: From 0ee4359a195f111728c5b3be4264b9c9157d3774 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:13:13 +0200 Subject: [PATCH 054/128] Update docs/TOOLS.md Co-authored-by: BethanyG --- docs/TOOLS.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index b3f8a3dff2..6663be79ea 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -50,12 +50,11 @@ Also known as `virtualenv`, *venv* is a light-weight solution to virtual environ #### Creating your virtual environment -To create a virtual environment, `cd` to the directory you want to put the *venv* in. Then run the `virtualenv` command with the name of the folder where you want to store your *environment*. Common convention is to call that folder `venv`: +To create a virtual environment, `cd` to the directory you want to store your environments in. Next, run the `venv` command with the name of a folder where you want to store this particular **environment**. Common convention is to call that folder `_venv`: ```bash -$ python3 -m virtualenv {name_of_virtualenv} +$ python3 -m venv {name_of_virtualenv} created virtual environment ... in 8568ms -``` #### Activating your virtual environment From 0c53083f3b9ca2f7bdc39e9272b60445e33ef0bd Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:13:22 +0200 Subject: [PATCH 055/128] Update docs/TOOLS.md Co-authored-by: BethanyG --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 6663be79ea..60da227f32 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -114,7 +114,7 @@ The Python extension from Microsoft is extremely useful because of its range of ##### Selecting the interpreter -The Python extensions supports the switching between multiple `interpreters`. This allows you to use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you can find more about [here](#environments). +The Python extensions supports the switching between multiple `interpreters`. This allows you to use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you can find more about in the [environments section](#environments). Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. From cde80caf2d62095b4b0496e4ef2c11b00b2aa0f2 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:14:38 +0200 Subject: [PATCH 056/128] Update docs/TOOLS.md Co-authored-by: BethanyG --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 60da227f32..d910fd712e 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -94,7 +94,7 @@ To create a new `conda` environment, go to the *Anaconda Navigator*. Click on `e Activating your `conda` environment is easy, just click the `â–º` button next to your *environment*, then press `Open Terminal`. This should open a new terminal with an interface for your `conda` environment. -From here you can run regular `pip` commands and other modules that you have installed inside your environment. All libraries will automatically be installed inside the `conda` environment. +From here you can run `conda` commands to install python packages from the Anaconda repository, or run regular `pip` commands to install python packages from PyPi inside your environment. All libraries will automatically be installed inside the `conda` environment. --- From f2ea08d484ae9a18f12e35900493219a7c8cc0af Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:15:10 +0200 Subject: [PATCH 057/128] Update docs/TOOLS.md Co-authored-by: BethanyG --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index d910fd712e..c0123c7b3f 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -138,7 +138,7 @@ The Python plugin also comes with some other features that can help you debug an ### PyCharm -PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can extend it plugins, but out-of-the-box it comes with a load of features already available. +PyCharm is an *Integrated Development Environment* built by JetBrains. It is specialized to work for Python and is commonly used among professionals. You can extend it with various paid and unpaid plugins, but out-of-the-box it comes with a load of features already available. #### Selecting the interpreter From 4cc8d6e4677a8f64b0c2c5e20f4d5a37f3ac7a49 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:23:01 +0200 Subject: [PATCH 058/128] Start on instructions --- .../plane-tickets/.docs/instructions.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index e69de29bb2..89a297bee8 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -0,0 +1,22 @@ +# Instructions + +Conda airlines has over 10.000 flights a day, but they need to automate. They are currently assigning all seats to passengers by hand. + +They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. + +Conda's airplanes have up to _6 seats_ in each row and can have many more rows. + +While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being `seat 1` in its row. + +You can use this _table_ as a guide: + +| x | 1 | 2 | +| :----: | :----: | :----:| +| Row | 5 | 21 | +| Seat number | 1 | 4 | +| Seat letter | A | D | +| Result | 5A | 21D | + +## 1. Generate seats + +... From 1db4ce908e3c2268447d0751a13f1373e7bc88b9 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:28:17 +0200 Subject: [PATCH 059/128] Update headers and links --- docs/TESTS.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index af773d629f..62b6d19508 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -2,20 +2,20 @@ - [Tests](#tests) - [Pytest](#pytest) - - [Installing pytest](#installing-pytest) + - [Installing pytest Globally](#installing-pytest-globally) - [Windows](#windows) - [Linux / MacOS](#linux--macos) - - [Virtual environments](#virtual-environments) + - [Installing pytest within a virtual environment](#installing-pytest-within-a-virtual-environment) - [Running the tests](#running-the-tests) - [Failures](#failures) - [Extra arguments](#extra-arguments) - [Stop After First Failure [`-x`]](#stop-after-first-failure--x) - [Failed Tests First [`--ff`]](#failed-tests-first---ff) - [Recommended Workflow](#recommended-workflow) - - [Python Debugger](#python-debugger) + - [Using PDB, the Python Debugger, with pytest](#using-pdb-the-python-debugger-with-pytest) - [Extending your IDE](#extending-your-ide) - [Additional information](#additional-information) - - [Adding to PATH](#adding-to-path) + - [Adding pytest to your PATH](#adding-pytest-to-your-path) - [Windows](#windows-1) - [Fixing warnings](#fixing-warnings) @@ -55,7 +55,7 @@ pytest 6.2.5 If you do not want to precede every command with `python3 -m` please refer to [adding to PATH](#adding-to-path) at the end of this document. -#### Installing pytest within a Virtual environment +#### Installing pytest within a virtual environment *For more information about virtual environments please refer to the [TOOLS](./TOOLS.md) file.* From 5d207285a8ffbd3e7612c109fc69dd2933800078 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:32:01 +0200 Subject: [PATCH 060/128] Removed template --- .../concept/plane-tickets/plane_tickets.py | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 7343184f97..8b13789179 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1,51 +1 @@ -def value_of_card(card): - """ - :param card: str - given card. - :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). - """ - - pass - - -def value_of_ace(hand_value): - """ - - :param hand_value: int - current hand value. - :return: int - value of the upcoming ace card (either 1 or 11). - """ - - pass - - -def is_blackjack(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand is a blackjack (two cards worth 21). - """ - - pass - - -def can_split_pairs(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). - """ - - pass - - -def can_double_down(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). - """ - - pass From 6e937d03f0b338369d8930edf15c22d44a1b19fa Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:53:17 +0200 Subject: [PATCH 061/128] Task 1 --- .../plane-tickets/.docs/instructions.md | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 89a297bee8..90712e49d9 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -4,7 +4,7 @@ Conda airlines has over 10.000 flights a day, but they need to automate. They ar They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _6 seats_ in each row and can have many more rows. +Conda's airplanes have up to _6 seats_ in each row, and each airplane can have many rows. While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being `seat 1` in its row. @@ -17,6 +17,20 @@ You can use this _table_ as a guide: | Seat letter | A | D | | Result | 5A | 21D | -## 1. Generate seats +## 1. Generate an amount of seats + +Implement the `generate_seats()` function that returns an iterable of seats given the following variable: + +`amount`: The amount of seats to be generated. + +_Note: The returned seats should be ordered, so: 1A 1B 1C._ + +```python +>>> seats = generate_seats(10) + +>>> next(seats) +1A +``` + +## 2. Assign seats to people -... From 924c083634132fd8bae3305a093709ee318058c4 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Wed, 3 Nov 2021 18:10:26 +0100 Subject: [PATCH 062/128] sync with PCs --- .../concept/plane-tickets/.docs/hints.md | 11 +++++++ .../plane-tickets/.docs/instructions.md | 30 +++++++++++++------ .../plane-tickets/.docs/introduction.md | 18 ++++++++++- .../concept/plane-tickets/plane_tickets.py | 24 +++++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md index e69de29bb2..72d814b22a 100644 --- a/exercises/concept/plane-tickets/.docs/hints.md +++ b/exercises/concept/plane-tickets/.docs/hints.md @@ -0,0 +1,11 @@ +# Hints + +## 1. Generate an amount of seats + +- The returned value should be of _type_ `generator`. +- Row `13` should be skipped, so go from `12` to `14`. +- Keep in mind that the returned values should be ordered from low to high. `1A, 1B, 2A, ...` + +## 2. Assign seats to passengers + +- Make sure your seat numbers do not have any space in them. diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 90712e49d9..a278a693b2 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -4,33 +4,45 @@ Conda airlines has over 10.000 flights a day, but they need to automate. They ar They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _6 seats_ in each row, and each airplane can have many rows. +Conda's airplanes have up to _4 seats_ in each row, and each airplane can have many more rows. -While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being `seat 1` in its row. +While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being the first _seat_ in the row. -You can use this _table_ as a guide: +You can use this table as a guide: | x | 1 | 2 | | :----: | :----: | :----:| | Row | 5 | 21 | -| Seat number | 1 | 4 | | Seat letter | A | D | | Result | 5A | 21D | ## 1. Generate an amount of seats -Implement the `generate_seats()` function that returns an iterable of seats given the following variable: +Implement the `generate_seats()` function that returns an _iterable_ of seats given the following variable: `amount`: The amount of seats to be generated. -_Note: The returned seats should be ordered, so: 1A 1B 1C._ +Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Make sure you also _don't_ assign seats to _row_ number 13. + +_Note: The returned seats should be ordered, like: 1A 1B 1C._ ```python >>> seats = generate_seats(10) - >>> next(seats) -1A +"1A" +>>> next(seats) +"1B" ``` -## 2. Assign seats to people +## 2. Assign seats to passengers + +Implement the `assign_seats()` function that returns a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. Given is the following _list_: +`passengers`: A list containing passenger names. + +```python +>>> passengers = ["Jerimiah", "Eric", "Bethaney"] + +>>> assign_seats(passengers) +{"Jerimiah" : "1A", "Eric" : "1B", "Bethaney" : "1C"} +``` diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 7afa7f9785..6395f408a3 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -59,7 +59,23 @@ The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yi When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. -`yield` expressions are _prohibited_ to be used outside of functions. +Note: _`yield` expressions are prohibited to be used outside of functions._ + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 + +>>> lets_try = yield_expression() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` ## Why generators? diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 8b13789179..0402a1519c 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1 +1,25 @@ +""" Plane Tickets Exercise """ +def generate_seats(amount): + + """ Generate a series of seat numbers for airline boarding. + + :param amount: Amount of seats to be generated. (int) + :return: Iterable that generates seat numbers. (generator) + + Seat numbers are generated with each row having 4 seats. + These should count up from low to high. + + Example: 3C, 3D, 4A, 4B + + """ + + pass + +def assign_seats(passengers): + + """ Assign seats to passenger. + + :param passengers: + + """ \ No newline at end of file From ac506325f24d0b0bc13feaf8ee0d83c031402272 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Wed, 3 Nov 2021 22:11:37 +0100 Subject: [PATCH 063/128] Sync --- .../concept/plane-tickets/.meta/design.md | 89 +++++++++++---- .../concept/plane-tickets/.meta/exemplar.py | 107 ++++++------------ .../concept/plane-tickets/plane_tickets.py | 11 +- .../plane-tickets/plane_tickets_test.py | 19 ++++ 4 files changed, 126 insertions(+), 100 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md index 1bf4f13582..58542aab87 100644 --- a/exercises/concept/plane-tickets/.meta/design.md +++ b/exercises/concept/plane-tickets/.meta/design.md @@ -1,24 +1,19 @@ +This issue describes how to implement the `generators` concept exercise for the Python track. + ## Goal -The goal of this exercise is to teach the syntax and use of generators in Python. +The goal of this exercise is to teach the syntax and use of `generators` in Python. ## Learning objectives -- Understand what generators are and how/when to use them -- Understand how generators relate to `loops` and `iterators` -- Understand how to use the `yield` keyword -- Understand the `__next__()` method -- Create a generator - -## Out of scope +- Understand what generators are and how/when to use them +- Understand how generators relate to `loops` and `iterators` +- Understand how to use the `yield` keyword +- Understand the `__next__()` method +- Create a generator -- rich comparison with `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__` -- understanding (_and using the concept_) that the `==` operator calls the dunder method `__eq__()` on a specific object, and uses that object's implementation for comparison. Where no implementation is present, the default `__eq__()` from generic `object` is used. -- overloading the default implementation of the `__eq__()` dunder method on a specific object to customize comparison behavior. -- `set operations` -- performance considerations -## Concepts +## Out of scope - Memory and performance characteristics and optimizations - `throw(type, value=None, traceback=None)` @@ -27,24 +22,68 @@ The goal of this exercise is to teach the syntax and use of generators in Python - `yield from` - `generators` used as coroutines + +## Concepts covered + +- `generators` +- `yield` +- `__next__()` +- `iterators` + + ## Prerequisites -- `conditionals` -- `dicts` -- `functions` -- `higher-order-functions` -- `lists` -- `loops` -- `iteration` -- `iterators` -- `sequences` +- `conditionals` +- `dicts` +- `functions` +- `higher-order-functions` +- `lists` +- `loops` +- `iteration` +- `iterators` +- `sequences` + -## Resources -- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) +## Resources to refer to + +- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) - [generator (Python official docs glossary)](https://docs.python.org/3/glossary.html#term-generator) - [The yield statement (Python official docs)](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) - [Yield expressions (Python official docs)](https://docs.python.org/3/reference/expressions.html#yieldexpr) - [Iterators(Python official docs)](https://docs.python.org/3/howto/functional.html?#iterators) - [Generator-iterator methods (Python official docs)](https://docs.python.org/3/reference/expressions.html#generator-iterator-methods) - [How to Use Generators and yield in Python (Real Python)](https://realpython.com/introduction-to-python-generators/) + +### Hints + +- Referring to one or more of the resources linked above, or analogous resources from a trusted source. +- `Generators` section of the Python Docs Functional How to tutorial: [Generators](https://docs.python.org/3/howto/functional.html#generators) + + +## Concept Description + +(_a variant of this can be used for the `v3/languages/python/concepts//about.md` doc and this exercises `introduction.md` doc._) + +_**Concept Description Needs to Be Filled In Here/Written**_ + +_Some "extras" that we might want to include as notes in the concept description, or as links in `links.json`:_ + +- Additional `Generator-iterator methods`, such as `generator.send()` and `generator.throw()` +- `generator expressions` +- Asynchronous generator functions +- `generators` used as coroutines + +## Implementing + +The general Python track concept exercise implantation guide can be found [here](https://github.com/exercism/v3/blob/master/languages/python/reference/implementing-a-concept-exercise.md). + +Tests should be written using `unittest.TestCase` and the test file named `generators_test.py`. + +Code in the `.meta/example.py` file should **only use syntax & concepts introduced in this exercise or one of its prerequisites.** Please do not use comprehensions, generator expressions, or other syntax not previously covered. Please also follow [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines. + +## Help + +If you have any questions while implementing the exercise, please post the questions as comments in this issue, or contact one of the maintainers on our Slack channel. + + diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 0b6c7b3208..c262c9d2a2 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,72 +1,35 @@ -def value_of_card(card): - """ - - :param card: str - given card. - :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). - """ - - if card == 'J' or card == 'Q' or card == 'K': - value = 10 - else: - value = int(card) - return value - - -def value_of_ace(hand_value): - """ - - :param hand_value: int - current hand value. - :return: int - value of the upcoming ace card (either 1 or 11). - """ - - if hand_value + 11 > 21: - value = 1 - else: - value = 11 - return value - - -def is_blackjack(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand is a blackjack (two cards worth 21). - """ - - if card_one == 'A' and card_two != 'A': - blackjack = value_of_card(card_two) == 10 - elif card_one != 'A' and card_two == 'A': - blackjack = value_of_card(card_one) == 10 - else: - blackjack = False - return blackjack - - -def can_split_pairs(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). - """ - - if card_one == 'A' or card_two == 'A': - split_pairs = card_one == card_two - else: - split_pairs = value_of_card(card_one) == value_of_card(card_two) - return split_pairs - - -def can_double_down(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). - """ - - card_one_value = 1 if card_one == 'A' else value_of_card(card_one) - card_two_value = 1 if card_two == 'A' else value_of_card(card_two) - hand_value = card_one_value + card_two_value - return 9 <= hand_value <= 11 +""" Plane Tickets Exercise """ + +def generate_seats(amount): + + """ Generate a series of seat numbers for airline boarding. + + :param amount: Amount of seats to be generated. (int) + :return: Iterable that generates seat numbers. (generator) + + Seat numbers are generated with each row having 4 seats. + These should be sorted from low to high. + + Example: 3C, 3D, 4A, 4B + + """ + + for seat_index in range(1, amount+1): + yield f"{-(-5 // 4)}{['A','B','C','D'][seat_index % 4]}" + +def assign_seats(passengers): + + """ Assign seats to passenger. + + :param passengers: A list of strings containing names of passengers. (list[str]) + :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. + + Example output: {"Foo": "1A", "Bar": "1B"} + + """ + + pass + +if __name__ == "__main__": + for x in generate_seats(10): + print(x) \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 0402a1519c..8f8f2bdaab 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -8,7 +8,7 @@ def generate_seats(amount): :return: Iterable that generates seat numbers. (generator) Seat numbers are generated with each row having 4 seats. - These should count up from low to high. + These should be sorted from low to high. Example: 3C, 3D, 4A, 4B @@ -20,6 +20,11 @@ def assign_seats(passengers): """ Assign seats to passenger. - :param passengers: + :param passengers: A list of strings containing names of passengers. (list[str]) + :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. + + Example output: {"Foo": "1A", "Bar": "1B"} - """ \ No newline at end of file + """ + + pass \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index e69de29bb2..75a242aafd 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -0,0 +1,19 @@ +from types import GeneratorType +import unittest +import pytest + +from plane_tickets import ( + generate_seats, + assign_seats +) + +class PlaneTicketsTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_task_1_type(self): + input = [5] + output = ["1A"] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + generator = generate_seats(input) + self.assertEqual(generator.__next__(), output) From d999678c103e4520f1e95d95e68f667581d14bb4 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 11:35:47 +0100 Subject: [PATCH 064/128] Task 1 Exemplar --- exercises/concept/plane-tickets/.meta/exemplar.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index c262c9d2a2..7f91abaa30 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -14,8 +14,16 @@ def generate_seats(amount): """ - for seat_index in range(1, amount+1): - yield f"{-(-5 // 4)}{['A','B','C','D'][seat_index % 4]}" + # Could also be solved in two lines: + # for seat_index in range(amount): + # yield f"{-(-(seat_index+1) // 4)}{['A','B','C','D'][seat_index % 4]}" + + SEATS_IN_ROW = ["A", "B", "C", "D"] + + for seat in range(amount): + seat_letter = SEATS_IN_ROW[seat % 4] + row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? + yield str(row_number)+str(seat_letter) def assign_seats(passengers): From 79b9fb6a4af4792f9675eee17cd3031ace8e5887 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 11:59:19 +0100 Subject: [PATCH 065/128] Exemplar now also skips row 13 --- .../concept/plane-tickets/.meta/exemplar.py | 19 ++++++++++--------- .../plane-tickets/plane_tickets_test.py | 10 +++++++++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 7f91abaa30..9c09e42cfa 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -14,16 +14,15 @@ def generate_seats(amount): """ - # Could also be solved in two lines: - # for seat_index in range(amount): - # yield f"{-(-(seat_index+1) // 4)}{['A','B','C','D'][seat_index % 4]}" - SEATS_IN_ROW = ["A", "B", "C", "D"] + amount = amount+1 if amount >= 13 else amount + for seat in range(amount): - seat_letter = SEATS_IN_ROW[seat % 4] row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? - yield str(row_number)+str(seat_letter) + if row_number != 13: + seat_letter = SEATS_IN_ROW[seat % 4] + yield str(row_number)+str(seat_letter) def assign_seats(passengers): @@ -36,8 +35,10 @@ def assign_seats(passengers): """ - pass + amount = len(passengers) + output = {} + for passenger, seat_number in zip(passengers, generate_seats(amount)): + output[passenger] = seat_number if __name__ == "__main__": - for x in generate_seats(10): - print(x) \ No newline at end of file + print([seat for seat in generate_seats(13*4+3+1)]) \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 75a242aafd..be54278dd2 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -10,10 +10,18 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_task_1_type(self): + def test_task_1_type(self): # * Tests if [Task 1] actually returns a generator. input = [5] output = ["1A"] for variant, (input, output) in enumerate(zip(input, output), start=1): with self.subTest(f"variation #{variant}", input_data=input, output_data=output): generator = generate_seats(input) self.assertEqual(generator.__next__(), output) + + @pytest.mark.task(taskno=1) + def test_task_1_output(self): + input = [1, 2, 3, 4, 5] + output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + self.assertEqual([seat for seat in generate_seats(input)], output) \ No newline at end of file From 5bfd965bda3ce9e2d814e127e1594c96aa440e9d Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:06:04 +0100 Subject: [PATCH 066/128] Fixed (skip row 13) --- exercises/concept/plane-tickets/.meta/exemplar.py | 5 +++-- exercises/concept/plane-tickets/plane_tickets.py | 2 ++ .../concept/plane-tickets/plane_tickets_test.py | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 9c09e42cfa..51c85cedf1 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -16,7 +16,7 @@ def generate_seats(amount): SEATS_IN_ROW = ["A", "B", "C", "D"] - amount = amount+1 if amount >= 13 else amount + amount = amount+4 if amount >= 13 else amount for seat in range(amount): row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? @@ -41,4 +41,5 @@ def assign_seats(passengers): output[passenger] = seat_number if __name__ == "__main__": - print([seat for seat in generate_seats(13*4+3+1)]) \ No newline at end of file + print([seat for seat in generate_seats(14*4)]) + print(len([seat for seat in generate_seats(5)])) \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 8f8f2bdaab..d725d78334 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -7,6 +7,8 @@ def generate_seats(amount): :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) + There should be no row 13 + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index be54278dd2..904164e108 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -10,7 +10,7 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_task_1_type(self): # * Tests if [Task 1] actually returns a generator. + def test_task1_type(self): # * Tests if [Task 1] actually returns a generator. input = [5] output = ["1A"] for variant, (input, output) in enumerate(zip(input, output), start=1): @@ -19,9 +19,18 @@ def test_task_1_type(self): # * Tests if [Task 1] actually returns a generator. self.assertEqual(generator.__next__(), output) @pytest.mark.task(taskno=1) - def test_task_1_output(self): + def test_task1_basic(self): input = [1, 2, 3, 4, 5] output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] for variant, (input, output) in enumerate(zip(input, output), start=1): with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual([seat for seat in generate_seats(input)], output) \ No newline at end of file + self.assertEqual([seat for seat in generate_seats(input)], output) + + @pytest.mark.task(taskno=1) + def test_task1_skips_row_13(self): + input = [14*4] + output = [['1A', '1B', '1C', '1D', '2A', '2B', '2C', '2D', '3A', '3B', '3C', '3D', '4A', '4B', '4C', '4D', '5A', '5B', '5C', '5D', '6A', '6B', '6C', '6D', '7A', '7B', '7C', '7D', '8A', '8B', '8C', '8D', '9A', '9B', '9C', '9D', '10A', '10B', '10C', '10D', '11A', '11B', '11C', '11D', '12A', '12B', '12C', '12D', '14A', '14B', '14C', '14D', '15A', '15B', '15C', '15D']] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + self.assertEqual([seat for seat in generate_seats(input)], output) + \ No newline at end of file From 4650a4a8b143f33fb01a2716ef70976881fd7608 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:28:25 +0100 Subject: [PATCH 067/128] Finished Task 1 & 2 --- .../concept/plane-tickets/.docs/introduction.md | 2 +- exercises/concept/plane-tickets/.meta/exemplar.py | 7 +++---- exercises/concept/plane-tickets/plane_tickets.py | 2 +- .../concept/plane-tickets/plane_tickets_test.py | 13 ++++++++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 6395f408a3..8c5dc214bc 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -27,7 +27,7 @@ For example, if you want to use the `squares()` generator we just constructed, w >>> for square in squares(list_of_numbers): >>> print(square) - 1 +1 4 9 16 diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 51c85cedf1..299b695401 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -7,6 +7,8 @@ def generate_seats(amount): :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) + There should be no row 13 + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. @@ -39,7 +41,4 @@ def assign_seats(passengers): output = {} for passenger, seat_number in zip(passengers, generate_seats(amount)): output[passenger] = seat_number - -if __name__ == "__main__": - print([seat for seat in generate_seats(14*4)]) - print(len([seat for seat in generate_seats(5)])) \ No newline at end of file + return output diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index d725d78334..93823180ac 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -29,4 +29,4 @@ def assign_seats(passengers): """ - pass \ No newline at end of file + pass diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 904164e108..634aa1fa56 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -10,7 +10,7 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_task1_type(self): # * Tests if [Task 1] actually returns a generator. + def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. input = [5] output = ["1A"] for variant, (input, output) in enumerate(zip(input, output), start=1): @@ -19,7 +19,7 @@ def test_task1_type(self): # * Tests if [Task 1] actually returns a generator. self.assertEqual(generator.__next__(), output) @pytest.mark.task(taskno=1) - def test_task1_basic(self): + def test_task1_output(self): input = [1, 2, 3, 4, 5] output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] for variant, (input, output) in enumerate(zip(input, output), start=1): @@ -33,4 +33,11 @@ def test_task1_skips_row_13(self): for variant, (input, output) in enumerate(zip(input, output), start=1): with self.subTest(f"variation #{variant}", input_data=input, output_data=output): self.assertEqual([seat for seat in generate_seats(input)], output) - \ No newline at end of file + + @pytest.mark.task(taskno=2) + def test_task2(self): + input = [['Passenger1', 'Passenger2', 'Passenger3', 'Passenger4', 'Passenger5'], ['TicketNo=5644', 'TicketNo=2273', 'TicketNo=493', 'TicketNo=5411', 'TicketNo=824']] + output = [{'Passenger1': '1A', 'Passenger2': '1B', 'Passenger3': '1C', 'Passenger4': '1D', 'Passenger5': '2A'}, {'TicketNo=5644': '1A', 'TicketNo=2273': '1B', 'TicketNo=493': '1C', 'TicketNo=5411': '1D', 'TicketNo=824': '2A'}] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + self.assertEqual(assign_seats(input), output) From 5c3940aeccaa6fb84c51d6ba93965db21bb6b2ff Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:28:44 +0100 Subject: [PATCH 068/128] Remove Typing --- exercises/concept/plane-tickets/plane_tickets_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 634aa1fa56..a54f53ca73 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -1,4 +1,3 @@ -from types import GeneratorType import unittest import pytest From b8ea390cfa2638089e97d5ff9bed672820f14182 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:51:22 +0100 Subject: [PATCH 069/128] Cleanup --- .../plane-tickets/plane_tickets_test.py | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index a54f53ca73..410d0d850b 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -8,35 +8,43 @@ class PlaneTicketsTest(unittest.TestCase): + @pytest.mark.task(taskno=1) - def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. - input = [5] + def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. + input_vars = [5] output = ["1A"] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - generator = generate_seats(input) - self.assertEqual(generator.__next__(), output) + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(generate_seats(input_var).__next__(), output) @pytest.mark.task(taskno=1) def test_task1_output(self): - input = [1, 2, 3, 4, 5] - output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual([seat for seat in generate_seats(input)], output) - + input_vars = [1, 2, 3, 4, 5] + output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(list(generate_seats(input_var)), output) + @pytest.mark.task(taskno=1) def test_task1_skips_row_13(self): - input = [14*4] - output = [['1A', '1B', '1C', '1D', '2A', '2B', '2C', '2D', '3A', '3B', '3C', '3D', '4A', '4B', '4C', '4D', '5A', '5B', '5C', '5D', '6A', '6B', '6C', '6D', '7A', '7B', '7C', '7D', '8A', '8B', '8C', '8D', '9A', '9B', '9C', '9D', '10A', '10B', '10C', '10D', '11A', '11B', '11C', '11D', '12A', '12B', '12C', '12D', '14A', '14B', '14C', '14D', '15A', '15B', '15C', '15D']] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual([seat for seat in generate_seats(input)], output) - + input_vars = [14 * 4] + output = [["1A", "1B", "1C", "1D", "2A", "2B", "2C", "2D", + "3A", "3B", "3C", "3D", "4A", "4B", "4C", "4D", + "5A", "5B", "5C", "5D", "6A", "6B", "6C", "6D", + "7A", "7B", "7C", "7D", "8A", "8B", "8C", "8D", + "9A", "9B", "9C", "9D", "10A", "10B", "10C", "10D", + "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D", + "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(list(generate_seats(input_var)), output) + @pytest.mark.task(taskno=2) def test_task2(self): - input = [['Passenger1', 'Passenger2', 'Passenger3', 'Passenger4', 'Passenger5'], ['TicketNo=5644', 'TicketNo=2273', 'TicketNo=493', 'TicketNo=5411', 'TicketNo=824']] - output = [{'Passenger1': '1A', 'Passenger2': '1B', 'Passenger3': '1C', 'Passenger4': '1D', 'Passenger5': '2A'}, {'TicketNo=5644': '1A', 'TicketNo=2273': '1B', 'TicketNo=493': '1C', 'TicketNo=5411': '1D', 'TicketNo=824': '2A'}] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual(assign_seats(input), output) + input_vars = [["Passenger1", "Passenger2", "Passenger3", "Passenger4", "Passenger5"], + ["TicketNo=5644", "TicketNo=2273", "TicketNo=493", "TicketNo=5411", "TicketNo=824"]] + output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, + {"TicketNo=5644": "1A", "TicketNo=2273": "1B", "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(assign_seats(input_var), output) From 8cafa6905928d2f24fa58478a590f99d6fbe8926 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 13:09:45 +0100 Subject: [PATCH 070/128] Add some data in ./concepts --- concepts/generators/.meta/config.json | 4 +- concepts/generators/introduction.md | 96 ++++++++++++++++++++++++++- concepts/generators/links.json | 16 ++--- 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json index 9b9e8da5a9..63b59e1ead 100644 --- a/concepts/generators/.meta/config.json +++ b/concepts/generators/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Learn about generators by assigning seats to passengers.", + "authors": ["bethanyg", "cmccandless", "J08K"], "contributors": [] } diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index fcde74642c..ca48239c2d 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -1,2 +1,96 @@ -#TODO: Add introduction for this concept. +# Instructions +A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). + +_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. + +## Constructing a generator + +Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). + +Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: + +```python +>>> def squares(list_of_numbers): +>>> for number in list_of_numbers: +>>> yield number ** 2 +``` + +## Using a generator + +It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. + +For example, if you want to use the `squares()` generator we just constructed, we simply use: + +```python +>>> list_of_numbers = [1, 2, 3, 4] + +>>> for square in squares(list_of_numbers): +>>> print(square) +1 +4 +9 +16 +``` + +You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. + +```python +square_generator = squares([1, 2]) + +>>> next(square_generator) +1 +>>> next(square_generator) +4 +``` + +When the `generator` has no more values to return it throws a `StopIteration` error. + +```python +>>> next(square_generator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +## The yield expression + +The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. + +When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. + +Note: _`yield` expressions are prohibited to be used outside of functions._ + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 + +>>> lets_try = yield_expression() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. + +You can also use it to generate complicated or infinite sequences, like this: + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/concepts/generators/links.json b/concepts/generators/links.json index eb5fb7c38a..b8ae2f7b64 100644 --- a/concepts/generators/links.json +++ b/concepts/generators/links.json @@ -1,18 +1,10 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3.8/reference/expressions.html#yield-expressions", + "description": "Official Python 3.8 docs for the yield expression." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://en.wikipedia.org/wiki/Lazy_evaluation", + "description": "Wikipedia page about lazy evaluation" } ] From 19c471c7eda286d62f38abad673194181a8f7e2d Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 14:02:14 +0100 Subject: [PATCH 071/128] Add exercise to config.json --- config.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/config.json b/config.json index 134bd35f4d..e766e54a04 100644 --- a/config.json +++ b/config.json @@ -175,6 +175,24 @@ "concepts": ["sets"], "prerequisites": ["basics", "dicts", "lists", "loops", "tuples"], "status": "beta" + }, + { + "slug": "plane-tickets", + "name": "Plane Tickets", + "uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810", + "concepts": ["generators"], + "prerequisites": [ + "conditionals", + "dicts", + "functions", + "higher-order-functions", + "lists", + "loops", + "iteration", + "iterators", + "sequences" + ], + "status": "beta" } ], "practice": [ From fedabbec73eeac866ef0750fcd667544fcf6cf2e Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 14:15:03 +0100 Subject: [PATCH 072/128] fixing --- config.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/config.json b/config.json index e766e54a04..ca80ef2297 100644 --- a/config.json +++ b/config.json @@ -184,13 +184,8 @@ "prerequisites": [ "conditionals", "dicts", - "functions", - "higher-order-functions", "lists", - "loops", - "iteration", - "iterators", - "sequences" + "loops" ], "status": "beta" } From 1394f9afa4513d790d422f1f10d189be3f708bc1 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:33:51 +0100 Subject: [PATCH 073/128] Update concepts/generators/.meta/config.json Co-authored-by: BethanyG --- concepts/generators/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json index 63b59e1ead..2204a700df 100644 --- a/concepts/generators/.meta/config.json +++ b/concepts/generators/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "Learn about generators by assigning seats to passengers.", - "authors": ["bethanyg", "cmccandless", "J08K"], + "authors": ["J08K"], "contributors": [] } From 0859ccc61f9a0f4bd034acf36956ca565250fc97 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:17:54 +0100 Subject: [PATCH 074/128] Update concepts/generators/introduction.md --- concepts/generators/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index ca48239c2d..576136c830 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -1,4 +1,4 @@ -# Instructions +# Introduction A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). From 46ca5577c6a5f8e38c6a0ed8b79ce6ebff28eb4e Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:18:41 +0100 Subject: [PATCH 075/128] Update exercises/concept/plane-tickets/.docs/instructions.md Co-authored-by: BethanyG --- exercises/concept/plane-tickets/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index a278a693b2..711665ab27 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -4,7 +4,7 @@ Conda airlines has over 10.000 flights a day, but they need to automate. They ar They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _4 seats_ in each row, and each airplane can have many more rows. +Conda's airplanes have up to _4 seats_ in each row, and each airplane has many rows. While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being the first _seat_ in the row. From 81aef4ce61d787c5f093a18c562feca7b9fd6bdf Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:26:11 +0100 Subject: [PATCH 076/128] Update exercises/concept/plane-tickets/.docs/introduction.md Co-authored-by: BethanyG --- exercises/concept/plane-tickets/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 8c5dc214bc..6bb14722c5 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -6,7 +6,7 @@ _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key ## Constructing a generator -Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). +Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: From 87dbc72295be17ca816e717bca7dbca628e6050f Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Fri, 5 Nov 2021 10:12:34 +0100 Subject: [PATCH 077/128] Update concepts/generators/introduction.md Co-authored-by: Isaac Good --- concepts/generators/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index 576136c830..1049680fed 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -20,7 +20,8 @@ Lets say you want to construct a `generator` that generates all the _squares_ fr It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. -For example, if you want to use the `squares()` generator we just constructed, we simply use: +For example, if you want to use the `squares()` generator we just constructed, we simply write: + ```python >>> list_of_numbers = [1, 2, 3, 4] From 38e435172a7a19224f928a596e728f167115968c Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Fri, 5 Nov 2021 10:33:03 +0100 Subject: [PATCH 078/128] Fixed all pylint warnings in Exemplar --- .../concept/plane-tickets/.meta/exemplar.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 299b695401..8fe0ddbda6 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,25 +1,25 @@ """ Plane Tickets Exercise """ +SEATS_IN_ROW = ["A", "B", "C", "D"] + def generate_seats(amount): - + """ Generate a series of seat numbers for airline boarding. - + :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) - + There should be no row 13 - + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. - + Example: 3C, 3D, 4A, 4B - + """ - - SEATS_IN_ROW = ["A", "B", "C", "D"] - + amount = amount+4 if amount >= 13 else amount - + for seat in range(amount): row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? if row_number != 13: @@ -27,14 +27,14 @@ def generate_seats(amount): yield str(row_number)+str(seat_letter) def assign_seats(passengers): - + """ Assign seats to passenger. - + :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. Example output: {"Foo": "1A", "Bar": "1B"} - + """ amount = len(passengers) From 74fc18ad4c634d4eff82416a5a18c17ad37074eb Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Fri, 5 Nov 2021 11:28:26 +0100 Subject: [PATCH 079/128] Update exercises/concept/plane-tickets/.meta/exemplar.py Co-authored-by: Isaac Good --- exercises/concept/plane-tickets/.meta/exemplar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 8fe0ddbda6..08c759c288 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -18,7 +18,7 @@ def generate_seats(amount): """ - amount = amount+4 if amount >= 13 else amount + amount = amount + 4 if amount >= 13 else amount for seat in range(amount): row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? From 7ea0eb4888ff0aa27ee6a7d5ed2a323b96f43699 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 9 Dec 2021 13:07:07 +0100 Subject: [PATCH 080/128] Committed suggestions and Added new task 3 --- concepts/generators/about.md | 119 +++++++++++++++++- concepts/generators/introduction.md | 92 -------------- .../plane-tickets/.docs/instructions.md | 30 ++++- .../plane-tickets/.docs/introduction.md | 93 +------------- .../concept/plane-tickets/.meta/exemplar.py | 30 +++-- .../concept/plane-tickets/plane_tickets.py | 32 +++-- 6 files changed, 190 insertions(+), 206 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index c628150d56..df41e9f014 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -1,2 +1,119 @@ -#TODO: Add about for this concept. +# About +## Constructing a generator + +Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. + +An example is a function that returns the _squares_ from a given list of numbers. As currently written, all input must be processed before any values can be returned: + +```python +>>> def squares(list_of_numbers): +>>> squares = [] +>>> for number in list_of_numbers: +>>> squares.append(number ** 2) +>>> return squares +``` + +You can convert that function into a generator like this: + +```python +def squares(list_of_numbers): + for number in list_of_number: + yield number ** 2 +``` + +The rationale behind this is that you use a generator when you do not need all the values _at once_. + +This would safe you memory, since you store just the value you are _currently_ working on. + +## Using a generator + +Generators can usually be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. + +For example, if you want to use the `squares()` generator we just constructed, we simply use: + +```python +>>> squared_numbers = squares([1, 2, 3, 4]) + +>>> for square in squared_numbers: +>>> print(square) +1 +4 +9 +16 +``` + +Values within a generator can also be produced/accessed via the `next()` function. `next()` calls the `__next__()` attribute of a generator, which "advances" or evaluates the generator code to the `yield` expression, returning the "next" value. + +```python +square_generator = squares([1, 2]) + +>>> next(square_generator) +1 +>>> next(square_generator) +4 +``` + +When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error. + +```python +>>> next(square_generator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +### Difference between iterables and generators + +Generators and (`iterables`)[https://wiki.python.org/moin/Iterator] act very similar, but there are some important differences to note: + +- Generators are _one-way_; no backing up to a previous value. +- Iterating over generators consume the returned values; no resetting. +- Generators are not sortable and can not be reversed. +- Generators do _not_ have `indexes`, so you can't reference a previous or future value. +- Generators do _not_ implement the `len()` function. +- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator. + +## The yield expression + +The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. + +_Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested. + +When `yield` is evaluated, it pauses the execution of the enclosing function and returns any values of the function _at that point in time_. + +The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. + +Note: _Using `yield` expressions is prohibited outside of functions._ + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 + +>>> lets_try = infinite_sequence() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of its values into `memory`. A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. + +Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_: + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index 1049680fed..2c14837133 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -3,95 +3,3 @@ A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. - -## Constructing a generator - -Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). - -Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: - -```python ->>> def squares(list_of_numbers): ->>> for number in list_of_numbers: ->>> yield number ** 2 -``` - -## Using a generator - -It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. - -For example, if you want to use the `squares()` generator we just constructed, we simply write: - - -```python ->>> list_of_numbers = [1, 2, 3, 4] - ->>> for square in squares(list_of_numbers): ->>> print(square) -1 -4 -9 -16 -``` - -You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. - -```python -square_generator = squares([1, 2]) - ->>> next(square_generator) -1 ->>> next(square_generator) -4 -``` - -When the `generator` has no more values to return it throws a `StopIteration` error. - -```python ->>> next(square_generator) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -## The yield expression - -The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. - -When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. - -Note: _`yield` expressions are prohibited to be used outside of functions._ - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 - ->>> lets_try = yield_expression() ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -1 -``` - -## Why generators? - -Generators are useful in a lot of applications. - -When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. - -You can also use it to generate complicated or infinite sequences, like this: - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 -``` - -Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 711665ab27..dc7a79a77f 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Conda airlines has over 10.000 flights a day, but they need to automate. They are currently assigning all seats to passengers by hand. +Conda airlines is the programming-world's biggest airline, with over 10.000 flights a day! + +They are currently assigning all seats to passengers by hand, this will need to automated. They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. @@ -41,8 +43,30 @@ Implement the `assign_seats()` function that returns a _dictionary_ of `passenge `passengers`: A list containing passenger names. ```python ->>> passengers = ["Jerimiah", "Eric", "Bethaney"] +>>> passengers = ['Jerimiah', 'Eric', 'Bethaney', 'Byte', 'SqueekyBoots', 'Bob'] >>> assign_seats(passengers) -{"Jerimiah" : "1A", "Eric" : "1B", "Bethaney" : "1C"} +{'Jerimiah': '1A', 'Eric': '1B', 'Bethaney': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'} +``` + +## 3. Ticket codes + +Each ticket has a _12_ character long string code for identification. + +This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. + +Implement a `generator` that yields a `ticket_number` given the following arguments: + +`seat_numbers`: A _list_ of *seat_numbers*. +`flight_id`: A string containing the flight identification. + +```python +>>> seat_numbers = ['1A', '17D'] +>>> flight_id = 'CO1234' +>>> ticket_ids = generate_codes(seat_numbers, flight_id) + +>>> next(ticket_ids) +'1ACO12340000' +>>> next(ticket_ids) +'17DCO1234000' ``` diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 6bb14722c5..fe531872ea 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -1,96 +1,5 @@ -# Instructions +# Introduction A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. - -## Constructing a generator - -Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. - -Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: - -```python ->>> def squares(list_of_numbers): ->>> for number in list_of_numbers: ->>> yield number ** 2 -``` - -## Using a generator - -It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. - -For example, if you want to use the `squares()` generator we just constructed, we simply use: - -```python ->>> list_of_numbers = [1, 2, 3, 4] - ->>> for square in squares(list_of_numbers): ->>> print(square) -1 -4 -9 -16 -``` - -You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. - -```python -square_generator = squares([1, 2]) - ->>> next(square_generator) -1 ->>> next(square_generator) -4 -``` - -When the `generator` has no more values to return it throws a `StopIteration` error. - -```python ->>> next(square_generator) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -## The yield expression - -The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. - -When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. - -Note: _`yield` expressions are prohibited to be used outside of functions._ - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 - ->>> lets_try = yield_expression() ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -1 -``` - -## Why generators? - -Generators are useful in a lot of applications. - -When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. - -You can also use it to generate complicated or infinite sequences, like this: - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 -``` - -Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 08c759c288..fa8927d759 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,13 +1,15 @@ -""" Plane Tickets Exercise """ +"""Plane Tickets Exercise""" -SEATS_IN_ROW = ["A", "B", "C", "D"] +import math + +SEATS_IN_ROW = ['A', 'B', 'C', 'D'] def generate_seats(amount): - """ Generate a series of seat numbers for airline boarding. + """Generate a series of seat numbers for airline boarding. :param amount: Amount of seats to be generated. (int) - :return: Iterable that generates seat numbers. (generator) + :return: Generator that generates seat numbers. (generator) There should be no row 13 @@ -21,14 +23,14 @@ def generate_seats(amount): amount = amount + 4 if amount >= 13 else amount for seat in range(amount): - row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? + row_number = math.ceil((seat+1) / 4) if row_number != 13: seat_letter = SEATS_IN_ROW[seat % 4] - yield str(row_number)+str(seat_letter) + yield f'{str(row_number)}{seat_letter}' def assign_seats(passengers): - """ Assign seats to passenger. + """Assign seats to passenger. :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. @@ -42,3 +44,17 @@ def assign_seats(passengers): for passenger, seat_number in zip(passengers, generate_seats(amount)): output[passenger] = seat_number return output + +def generate_codes(seat_numbers, flight_id): + + """Generate codes for a ticket. + + :param seat_numbers: A list of seat numbers. (list[str]) + :param flight_id: A string containing the flight identification. (str) + :return: Generator that generates 12 character long strings. (generator[str]) + + """ + + for seat in seat_numbers: + base_string = f'{seat}{flight_id}' + yield base_string + '0' * (12 - len(base_string)) diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 93823180ac..10184a0850 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1,32 +1,42 @@ -""" Plane Tickets Exercise """ +"""Plane Tickets Exercise""" def generate_seats(amount): - + """ Generate a series of seat numbers for airline boarding. - + :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) - + There should be no row 13 - + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. - + Example: 3C, 3D, 4A, 4B - + """ - + pass def assign_seats(passengers): - + """ Assign seats to passenger. - + :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. Example output: {"Foo": "1A", "Bar": "1B"} - + """ pass + +def generate_codes(seat_numbers, flight_id): + + """Generate codes for a ticket. + + :param seat_numbers: A list of seat numbers. (list[str]) + :param flight_id: A string containing the flight identification. (str) + :return: Generator that generates 12 character long strings. (generator[str]) + + """ From 6557816a4edbe1d16b45d00022c3029975b67162 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 10 Dec 2021 14:49:09 -0800 Subject: [PATCH 081/128] Apply suggestions from code review Co-authored-by: Victor Goff --- concepts/generators/about.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index df41e9f014..82aafd928c 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -28,7 +28,7 @@ This would safe you memory, since you store just the value you are _currently_ w ## Using a generator -Generators can usually be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. +Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. For example, if you want to use the `squares()` generator we just constructed, we simply use: @@ -104,7 +104,8 @@ Note: _Using `yield` expressions is prohibited outside of functions._ Generators are useful in a lot of applications. -When working with a large collection, you might not want to put all of its values into `memory`. A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. +When working with a large collection, you might not want to put all of its values into `memory`. +A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_: From e20949c649084342ae2130991f98701b85f8b4cd Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:55:22 +0100 Subject: [PATCH 082/128] Apply suggestions from code review WHOOOOP Co-authored-by: BethanyG Co-authored-by: Victor Goff --- concepts/generators/about.md | 32 +++++++++++++------ .../plane-tickets/.docs/instructions.md | 3 +- .../plane-tickets/.docs/introduction.md | 2 +- .../concept/plane-tickets/.meta/config.json | 2 +- .../concept/plane-tickets/plane_tickets.py | 4 +-- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index 82aafd928c..f1b97291e4 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -2,9 +2,12 @@ ## Constructing a generator -Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. +Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. + + +An example is a function that returns the _squares_ from a given list of numbers. +As currently written, all input must be processed before any values can be returned: -An example is a function that returns the _squares_ from a given list of numbers. As currently written, all input must be processed before any values can be returned: ```python >>> def squares(list_of_numbers): @@ -24,13 +27,14 @@ def squares(list_of_numbers): The rationale behind this is that you use a generator when you do not need all the values _at once_. -This would safe you memory, since you store just the value you are _currently_ working on. +This saves memory and processing power, since only the value you are _currently working on_ is calculated. + ## Using a generator Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. -For example, if you want to use the `squares()` generator we just constructed, we simply use: +To use the `squares()` generator: ```python >>> squared_numbers = squares([1, 2, 3, 4]) @@ -43,7 +47,8 @@ For example, if you want to use the `squares()` generator we just constructed, w 16 ``` -Values within a generator can also be produced/accessed via the `next()` function. `next()` calls the `__next__()` attribute of a generator, which "advances" or evaluates the generator code to the `yield` expression, returning the "next" value. +Values within a generator can also be produced/accessed via the `next()` function. +`next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value. ```python square_generator = squares([1, 2]) @@ -65,13 +70,20 @@ StopIteration ### Difference between iterables and generators -Generators and (`iterables`)[https://wiki.python.org/moin/Iterator] act very similar, but there are some important differences to note: +Generators are a special sub-set of _iterators_. +`Iterators` are the mechanism/protocol that enables looping over _iterables_. +Generators and and the iterators returned by common Python (`iterables`)[https://wiki.python.org/moin/Iterator] act very similarly, but there are some important differences to note: + + +- Generators are _one-way_; there is no "backing up" to a previous value. -- Generators are _one-way_; no backing up to a previous value. - Iterating over generators consume the returned values; no resetting. -- Generators are not sortable and can not be reversed. -- Generators do _not_ have `indexes`, so you can't reference a previous or future value. -- Generators do _not_ implement the `len()` function. +- Generators (_being lazily evaluated_) are not sortable and can not be reversed. + +- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction. + +- Generators cannot be used with the `len()` function. + - Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator. ## The yield expression diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index dc7a79a77f..9ed19e38f5 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -24,7 +24,8 @@ Implement the `generate_seats()` function that returns an _iterable_ of seats gi `amount`: The amount of seats to be generated. -Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Make sure you also _don't_ assign seats to _row_ number 13. +Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. +Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. _Note: The returned seats should be ordered, like: 1A 1B 1C._ diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index fe531872ea..1b557b447f 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -2,4 +2,4 @@ A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). -_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. +_Lazy iterators_ are similar to iterables such as `lists`, and other types of `iterators` in Python -- but with one key difference: `generators` do not store their `values` in memory, but _generate_ their values as needed or when called. diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json index 18a44a57c0..96559aa24c 100644 --- a/exercises/concept/plane-tickets/.meta/config.json +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -2,7 +2,7 @@ "blurb": "Learn about generators by assigning seats to passengers.", "authors": ["J08K"], "icon": "poker", - "contributors": [], + "contributors": ["BethanyG"], "files": { "solution": ["plane_tickets.py"], "test": ["plane_tickets_test.py"], diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 10184a0850..44d685535b 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -5,7 +5,7 @@ def generate_seats(amount): """ Generate a series of seat numbers for airline boarding. :param amount: Amount of seats to be generated. (int) - :return: Iterable that generates seat numbers. (generator) + :return: Generator that yields seat numbers. There should be no row 13 @@ -20,7 +20,7 @@ def generate_seats(amount): def assign_seats(passengers): - """ Assign seats to passenger. + """ Assign seats to passengers. :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. From d751ec44c7dedb5b5304343f677decc3c5951145 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 13 Dec 2021 14:18:04 +0100 Subject: [PATCH 083/128] Add tests for task 3 --- .../concept/plane-tickets/.docs/instructions.md | 2 +- .../concept/plane-tickets/plane_tickets_test.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 9ed19e38f5..290aad2ebb 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -24,7 +24,7 @@ Implement the `generate_seats()` function that returns an _iterable_ of seats gi `amount`: The amount of seats to be generated. -Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. +Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. _Note: The returned seats should be ordered, like: 1A 1B 1C._ diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 410d0d850b..0b50c0422e 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -3,7 +3,8 @@ from plane_tickets import ( generate_seats, - assign_seats + assign_seats, + generate_codes ) class PlaneTicketsTest(unittest.TestCase): @@ -48,3 +49,14 @@ def test_task2(self): for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): self.assertEqual(assign_seats(input_var), output) + + @pytest.mark.task(taskno=3) + def test_task3(self): + input_vars = [(["12A", "38B", "69C", "102B"],"KL1022"), + (["22C", "88B", "33A", "44B"], "DL1002")] + output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], + ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + seats, flight_nr = input_var + self.assertEqual(list(generate_codes(seats, flight_nr)), output) \ No newline at end of file From cdc58be882b4b8af811888b9e26891648a166253 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 13 Dec 2021 14:21:59 +0100 Subject: [PATCH 084/128] Update config.json Co-authored-by: BethanyG --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index ca80ef2297..7d28ffe7a1 100644 --- a/config.json +++ b/config.json @@ -187,7 +187,7 @@ "lists", "loops" ], - "status": "beta" + "status": "wip" } ], "practice": [ From b5ac15019675a6f2afadff90bf3f1cc789d776be Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 13 Dec 2021 14:32:11 +0100 Subject: [PATCH 085/128] Better test case 1; add test for type task 3 --- exercises/concept/plane-tickets/plane_tickets_test.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 0b50c0422e..f51b9fa1c1 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -1,3 +1,4 @@ +from typing import Generator import unittest import pytest @@ -12,11 +13,9 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. - input_vars = [5] - output = ["1A"] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(generate_seats(input_var).__next__(), output) + input_var = 5 + # Output technically not needed here, since we are testing for type. + self.assertIsInstance(generate_seats(input_var), Generator) @pytest.mark.task(taskno=1) def test_task1_output(self): From 006091373cbf40ea6cd926dd4a303c1a0b8e06f7 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 13 Dec 2021 16:12:06 +0100 Subject: [PATCH 086/128] Don't you love pytests? --- .../plane-tickets/plane_tickets_test.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index f51b9fa1c1..f4255c43af 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -14,16 +14,18 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. input_var = 5 - # Output technically not needed here, since we are testing for type. - self.assertIsInstance(generate_seats(input_var), Generator) + output_type = Generator + error_message = f"Expected: {str(output_type)} type, but got a different type." + self.assertIsInstance(generate_seats(input_var), output_type, msg=error_message) @pytest.mark.task(taskno=1) def test_task1_output(self): input_vars = [1, 2, 3, 4, 5] output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seats(input_var)), output) + self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) @pytest.mark.task(taskno=1) def test_task1_skips_row_13(self): @@ -36,8 +38,9 @@ def test_task1_skips_row_13(self): "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D", "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seats(input_var)), output) + self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) @pytest.mark.task(taskno=2) def test_task2(self): @@ -46,8 +49,16 @@ def test_task2(self): output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, {"TicketNo=5644": "1A", "TicketNo=2273": "1B", "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while assigning seats to passengers {input_var}." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(assign_seats(input_var), output) + self.assertEqual(assign_seats(input_var), output, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_task3_is_generator(self): + input_var = ("11B", "HA80085") + output_type = Generator + error_message = f"Expected: {str(output_type)} type, but got a different type." + self.assertIsInstance(generate_codes(input_var[0], input_var[1]), output_type, msg=error_message) @pytest.mark.task(taskno=3) def test_task3(self): @@ -56,6 +67,6 @@ def test_task3(self): output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {input_var}, but something went wrong while generating ticket numbers." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - seats, flight_nr = input_var - self.assertEqual(list(generate_codes(seats, flight_nr)), output) \ No newline at end of file + self.assertEqual(list(generate_codes(input_var[0], input_var[1])), output, msg=error_message) \ No newline at end of file From b21b0aae7eb0fa4b775cd441e2728ad35710b884 Mon Sep 17 00:00:00 2001 From: Exercism Bot <66069679+exercism-bot@users.noreply.github.com> Date: Wed, 9 Mar 2022 06:35:03 +0000 Subject: [PATCH 087/128] =?UTF-8?q?=F0=9F=A4=96=20Sync=20org-wide=20files?= =?UTF-8?q?=20to=20upstream=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More info: https://github.com/exercism/org-wide-files/commit/cc4bf900aaee77af9629a751dadc8092d82e379a --- .github/workflows/configlet.yml | 23 +++++++++++------------ .github/workflows/sync-labels.yml | 18 ++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/.github/workflows/configlet.yml b/.github/workflows/configlet.yml index 5f7632d9be..47eb875436 100644 --- a/.github/workflows/configlet.yml +++ b/.github/workflows/configlet.yml @@ -1,16 +1,15 @@ -name: Configlet CI +name: Configlet -on: [push, pull_request, workflow_dispatch] +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read jobs: configlet: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Fetch configlet - uses: exercism/github-actions/configlet-ci@main - - - name: Configlet Linter - run: configlet lint + uses: exercism/github-actions/.github/workflows/configlet.yml@main diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 9b9509afae..e7b99e5048 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -2,20 +2,18 @@ name: Tools on: push: - branches: [main] + branches: + - main paths: - .github/labels.yml - .github/workflows/sync-labels.yml - schedule: - - cron: 0 0 1 * * workflow_dispatch: + schedule: + - cron: 0 0 1 * * # First day of each month + +permissions: + issues: write jobs: sync-labels: - name: Sync labels - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: exercism/github-actions/.github/workflows/labels.yml@main From 324e499592fa6a8765242a107ef9bae75d91c49f Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Wed, 6 Oct 2021 21:44:23 +0200 Subject: [PATCH 088/128] Layed out ground work --- .../concept/plane-tickets/.docs/hints.md | 0 .../plane-tickets/.docs/instructions.md | 0 .../plane-tickets/.docs/introduction.md | 0 .../concept/plane-tickets/.meta/config.json | 11 +++ .../concept/plane-tickets/.meta/design.md | 50 +++++++++++++ .../concept/plane-tickets/.meta/exemplar.py | 72 +++++++++++++++++++ .../concept/plane-tickets/plane_tickets.py | 51 +++++++++++++ .../plane-tickets/plane_tickets_test.py | 0 8 files changed, 184 insertions(+) create mode 100644 exercises/concept/plane-tickets/.docs/hints.md create mode 100644 exercises/concept/plane-tickets/.docs/instructions.md create mode 100644 exercises/concept/plane-tickets/.docs/introduction.md create mode 100644 exercises/concept/plane-tickets/.meta/config.json create mode 100644 exercises/concept/plane-tickets/.meta/design.md create mode 100644 exercises/concept/plane-tickets/.meta/exemplar.py create mode 100644 exercises/concept/plane-tickets/plane_tickets.py create mode 100644 exercises/concept/plane-tickets/plane_tickets_test.py diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json new file mode 100644 index 0000000000..18a44a57c0 --- /dev/null +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -0,0 +1,11 @@ +{ + "blurb": "Learn about generators by assigning seats to passengers.", + "authors": ["J08K"], + "icon": "poker", + "contributors": [], + "files": { + "solution": ["plane_tickets.py"], + "test": ["plane_tickets_test.py"], + "exemplar": [".meta/exemplar.py"] + } +} diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md new file mode 100644 index 0000000000..1bf4f13582 --- /dev/null +++ b/exercises/concept/plane-tickets/.meta/design.md @@ -0,0 +1,50 @@ +## Goal + +The goal of this exercise is to teach the syntax and use of generators in Python. + +## Learning objectives + +- Understand what generators are and how/when to use them +- Understand how generators relate to `loops` and `iterators` +- Understand how to use the `yield` keyword +- Understand the `__next__()` method +- Create a generator + +## Out of scope + +- rich comparison with `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__` +- understanding (_and using the concept_) that the `==` operator calls the dunder method `__eq__()` on a specific object, and uses that object's implementation for comparison. Where no implementation is present, the default `__eq__()` from generic `object` is used. +- overloading the default implementation of the `__eq__()` dunder method on a specific object to customize comparison behavior. +- `set operations` +- performance considerations + +## Concepts + +- Memory and performance characteristics and optimizations +- `throw(type, value=None, traceback=None)` +- `close()` +- `generator expressions` +- `yield from` +- `generators` used as coroutines + +## Prerequisites + +- `conditionals` +- `dicts` +- `functions` +- `higher-order-functions` +- `lists` +- `loops` +- `iteration` +- `iterators` +- `sequences` + +## Resources + +- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) +- [generator (Python official docs glossary)](https://docs.python.org/3/glossary.html#term-generator) +- [The yield statement (Python official docs)](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) +- [Yield expressions (Python official docs)](https://docs.python.org/3/reference/expressions.html#yieldexpr) +- [Iterators(Python official docs)](https://docs.python.org/3/howto/functional.html?#iterators) +- [Generator-iterator methods (Python official docs)](https://docs.python.org/3/reference/expressions.html#generator-iterator-methods) +- [How to Use Generators and yield in Python (Real Python)](https://realpython.com/introduction-to-python-generators/) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py new file mode 100644 index 0000000000..0b6c7b3208 --- /dev/null +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -0,0 +1,72 @@ +def value_of_card(card): + """ + + :param card: str - given card. + :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). + """ + + if card == 'J' or card == 'Q' or card == 'K': + value = 10 + else: + value = int(card) + return value + + +def value_of_ace(hand_value): + """ + + :param hand_value: int - current hand value. + :return: int - value of the upcoming ace card (either 1 or 11). + """ + + if hand_value + 11 > 21: + value = 1 + else: + value = 11 + return value + + +def is_blackjack(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand is a blackjack (two cards worth 21). + """ + + if card_one == 'A' and card_two != 'A': + blackjack = value_of_card(card_two) == 10 + elif card_one != 'A' and card_two == 'A': + blackjack = value_of_card(card_one) == 10 + else: + blackjack = False + return blackjack + + +def can_split_pairs(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). + """ + + if card_one == 'A' or card_two == 'A': + split_pairs = card_one == card_two + else: + split_pairs = value_of_card(card_one) == value_of_card(card_two) + return split_pairs + + +def can_double_down(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). + """ + + card_one_value = 1 if card_one == 'A' else value_of_card(card_one) + card_two_value = 1 if card_two == 'A' else value_of_card(card_two) + hand_value = card_one_value + card_two_value + return 9 <= hand_value <= 11 diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py new file mode 100644 index 0000000000..7343184f97 --- /dev/null +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -0,0 +1,51 @@ +def value_of_card(card): + """ + + :param card: str - given card. + :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). + """ + + pass + + +def value_of_ace(hand_value): + """ + + :param hand_value: int - current hand value. + :return: int - value of the upcoming ace card (either 1 or 11). + """ + + pass + + +def is_blackjack(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand is a blackjack (two cards worth 21). + """ + + pass + + +def can_split_pairs(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). + """ + + pass + + +def can_double_down(card_one, card_two): + """ + + :param card_one: str - first card in hand. + :param card_two: str - second card in hand. + :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). + """ + + pass diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py new file mode 100644 index 0000000000..e69de29bb2 From 8694216eee47f30a878985c68e69dedf8fd4d310 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 7 Oct 2021 21:02:36 +0200 Subject: [PATCH 089/128] Write introduction --- .../plane-tickets/.docs/introduction.md | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index e69de29bb2..7afa7f9785 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -0,0 +1,80 @@ +# Instructions + +A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). + +_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. + +## Constructing a generator + +Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). + +Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: + +```python +>>> def squares(list_of_numbers): +>>> for number in list_of_numbers: +>>> yield number ** 2 +``` + +## Using a generator + +It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. + +For example, if you want to use the `squares()` generator we just constructed, we simply use: + +```python +>>> list_of_numbers = [1, 2, 3, 4] + +>>> for square in squares(list_of_numbers): +>>> print(square) + 1 +4 +9 +16 +``` + +You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. + +```python +square_generator = squares([1, 2]) + +>>> next(square_generator) +1 +>>> next(square_generator) +4 +``` + +When the `generator` has no more values to return it throws a `StopIteration` error. + +```python +>>> next(square_generator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +## The yield expression + +The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. + +When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. + +`yield` expressions are _prohibited_ to be used outside of functions. + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. + +You can also use it to generate complicated or infinite sequences, like this: + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. From fabe7c4ed76ea192008e9e84f72f597afa1a9e0c Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:23:01 +0200 Subject: [PATCH 090/128] Start on instructions --- .../plane-tickets/.docs/instructions.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index e69de29bb2..89a297bee8 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -0,0 +1,22 @@ +# Instructions + +Conda airlines has over 10.000 flights a day, but they need to automate. They are currently assigning all seats to passengers by hand. + +They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. + +Conda's airplanes have up to _6 seats_ in each row and can have many more rows. + +While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being `seat 1` in its row. + +You can use this _table_ as a guide: + +| x | 1 | 2 | +| :----: | :----: | :----:| +| Row | 5 | 21 | +| Seat number | 1 | 4 | +| Seat letter | A | D | +| Result | 5A | 21D | + +## 1. Generate seats + +... From 96bee107a618eea6a94b61149976fc160aa3b171 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:32:01 +0200 Subject: [PATCH 091/128] Removed template --- .../concept/plane-tickets/plane_tickets.py | 50 ------------------- 1 file changed, 50 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 7343184f97..8b13789179 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1,51 +1 @@ -def value_of_card(card): - """ - :param card: str - given card. - :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). - """ - - pass - - -def value_of_ace(hand_value): - """ - - :param hand_value: int - current hand value. - :return: int - value of the upcoming ace card (either 1 or 11). - """ - - pass - - -def is_blackjack(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand is a blackjack (two cards worth 21). - """ - - pass - - -def can_split_pairs(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). - """ - - pass - - -def can_double_down(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). - """ - - pass From ca217606312dbd1e206d5f01abb60001b0a9247f Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 14 Oct 2021 09:53:17 +0200 Subject: [PATCH 092/128] Task 1 --- .../plane-tickets/.docs/instructions.md | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 89a297bee8..90712e49d9 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -4,7 +4,7 @@ Conda airlines has over 10.000 flights a day, but they need to automate. They ar They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _6 seats_ in each row and can have many more rows. +Conda's airplanes have up to _6 seats_ in each row, and each airplane can have many rows. While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being `seat 1` in its row. @@ -17,6 +17,20 @@ You can use this _table_ as a guide: | Seat letter | A | D | | Result | 5A | 21D | -## 1. Generate seats +## 1. Generate an amount of seats + +Implement the `generate_seats()` function that returns an iterable of seats given the following variable: + +`amount`: The amount of seats to be generated. + +_Note: The returned seats should be ordered, so: 1A 1B 1C._ + +```python +>>> seats = generate_seats(10) + +>>> next(seats) +1A +``` + +## 2. Assign seats to people -... From 9b554a4c4909adf2d91d3bf31e344ff352c14d71 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Wed, 3 Nov 2021 18:10:26 +0100 Subject: [PATCH 093/128] sync with PCs --- .../concept/plane-tickets/.docs/hints.md | 11 +++++++ .../plane-tickets/.docs/instructions.md | 30 +++++++++++++------ .../plane-tickets/.docs/introduction.md | 18 ++++++++++- .../concept/plane-tickets/plane_tickets.py | 24 +++++++++++++++ 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md index e69de29bb2..72d814b22a 100644 --- a/exercises/concept/plane-tickets/.docs/hints.md +++ b/exercises/concept/plane-tickets/.docs/hints.md @@ -0,0 +1,11 @@ +# Hints + +## 1. Generate an amount of seats + +- The returned value should be of _type_ `generator`. +- Row `13` should be skipped, so go from `12` to `14`. +- Keep in mind that the returned values should be ordered from low to high. `1A, 1B, 2A, ...` + +## 2. Assign seats to passengers + +- Make sure your seat numbers do not have any space in them. diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 90712e49d9..a278a693b2 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -4,33 +4,45 @@ Conda airlines has over 10.000 flights a day, but they need to automate. They ar They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _6 seats_ in each row, and each airplane can have many rows. +Conda's airplanes have up to _4 seats_ in each row, and each airplane can have many more rows. -While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being `seat 1` in its row. +While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being the first _seat_ in the row. -You can use this _table_ as a guide: +You can use this table as a guide: | x | 1 | 2 | | :----: | :----: | :----:| | Row | 5 | 21 | -| Seat number | 1 | 4 | | Seat letter | A | D | | Result | 5A | 21D | ## 1. Generate an amount of seats -Implement the `generate_seats()` function that returns an iterable of seats given the following variable: +Implement the `generate_seats()` function that returns an _iterable_ of seats given the following variable: `amount`: The amount of seats to be generated. -_Note: The returned seats should be ordered, so: 1A 1B 1C._ +Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Make sure you also _don't_ assign seats to _row_ number 13. + +_Note: The returned seats should be ordered, like: 1A 1B 1C._ ```python >>> seats = generate_seats(10) - >>> next(seats) -1A +"1A" +>>> next(seats) +"1B" ``` -## 2. Assign seats to people +## 2. Assign seats to passengers + +Implement the `assign_seats()` function that returns a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. Given is the following _list_: +`passengers`: A list containing passenger names. + +```python +>>> passengers = ["Jerimiah", "Eric", "Bethaney"] + +>>> assign_seats(passengers) +{"Jerimiah" : "1A", "Eric" : "1B", "Bethaney" : "1C"} +``` diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 7afa7f9785..6395f408a3 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -59,7 +59,23 @@ The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yi When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. -`yield` expressions are _prohibited_ to be used outside of functions. +Note: _`yield` expressions are prohibited to be used outside of functions._ + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 + +>>> lets_try = yield_expression() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` ## Why generators? diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 8b13789179..0402a1519c 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1 +1,25 @@ +""" Plane Tickets Exercise """ +def generate_seats(amount): + + """ Generate a series of seat numbers for airline boarding. + + :param amount: Amount of seats to be generated. (int) + :return: Iterable that generates seat numbers. (generator) + + Seat numbers are generated with each row having 4 seats. + These should count up from low to high. + + Example: 3C, 3D, 4A, 4B + + """ + + pass + +def assign_seats(passengers): + + """ Assign seats to passenger. + + :param passengers: + + """ \ No newline at end of file From c0867672f8d353199c1db142336396c7613ae917 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Wed, 3 Nov 2021 22:11:37 +0100 Subject: [PATCH 094/128] Sync --- .../concept/plane-tickets/.meta/design.md | 89 +++++++++++---- .../concept/plane-tickets/.meta/exemplar.py | 107 ++++++------------ .../concept/plane-tickets/plane_tickets.py | 11 +- .../plane-tickets/plane_tickets_test.py | 19 ++++ 4 files changed, 126 insertions(+), 100 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md index 1bf4f13582..58542aab87 100644 --- a/exercises/concept/plane-tickets/.meta/design.md +++ b/exercises/concept/plane-tickets/.meta/design.md @@ -1,24 +1,19 @@ +This issue describes how to implement the `generators` concept exercise for the Python track. + ## Goal -The goal of this exercise is to teach the syntax and use of generators in Python. +The goal of this exercise is to teach the syntax and use of `generators` in Python. ## Learning objectives -- Understand what generators are and how/when to use them -- Understand how generators relate to `loops` and `iterators` -- Understand how to use the `yield` keyword -- Understand the `__next__()` method -- Create a generator - -## Out of scope +- Understand what generators are and how/when to use them +- Understand how generators relate to `loops` and `iterators` +- Understand how to use the `yield` keyword +- Understand the `__next__()` method +- Create a generator -- rich comparison with `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__` -- understanding (_and using the concept_) that the `==` operator calls the dunder method `__eq__()` on a specific object, and uses that object's implementation for comparison. Where no implementation is present, the default `__eq__()` from generic `object` is used. -- overloading the default implementation of the `__eq__()` dunder method on a specific object to customize comparison behavior. -- `set operations` -- performance considerations -## Concepts +## Out of scope - Memory and performance characteristics and optimizations - `throw(type, value=None, traceback=None)` @@ -27,24 +22,68 @@ The goal of this exercise is to teach the syntax and use of generators in Python - `yield from` - `generators` used as coroutines + +## Concepts covered + +- `generators` +- `yield` +- `__next__()` +- `iterators` + + ## Prerequisites -- `conditionals` -- `dicts` -- `functions` -- `higher-order-functions` -- `lists` -- `loops` -- `iteration` -- `iterators` -- `sequences` +- `conditionals` +- `dicts` +- `functions` +- `higher-order-functions` +- `lists` +- `loops` +- `iteration` +- `iterators` +- `sequences` + -## Resources -- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) +## Resources to refer to + +- [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators) - [generator (Python official docs glossary)](https://docs.python.org/3/glossary.html#term-generator) - [The yield statement (Python official docs)](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement) - [Yield expressions (Python official docs)](https://docs.python.org/3/reference/expressions.html#yieldexpr) - [Iterators(Python official docs)](https://docs.python.org/3/howto/functional.html?#iterators) - [Generator-iterator methods (Python official docs)](https://docs.python.org/3/reference/expressions.html#generator-iterator-methods) - [How to Use Generators and yield in Python (Real Python)](https://realpython.com/introduction-to-python-generators/) + +### Hints + +- Referring to one or more of the resources linked above, or analogous resources from a trusted source. +- `Generators` section of the Python Docs Functional How to tutorial: [Generators](https://docs.python.org/3/howto/functional.html#generators) + + +## Concept Description + +(_a variant of this can be used for the `v3/languages/python/concepts//about.md` doc and this exercises `introduction.md` doc._) + +_**Concept Description Needs to Be Filled In Here/Written**_ + +_Some "extras" that we might want to include as notes in the concept description, or as links in `links.json`:_ + +- Additional `Generator-iterator methods`, such as `generator.send()` and `generator.throw()` +- `generator expressions` +- Asynchronous generator functions +- `generators` used as coroutines + +## Implementing + +The general Python track concept exercise implantation guide can be found [here](https://github.com/exercism/v3/blob/master/languages/python/reference/implementing-a-concept-exercise.md). + +Tests should be written using `unittest.TestCase` and the test file named `generators_test.py`. + +Code in the `.meta/example.py` file should **only use syntax & concepts introduced in this exercise or one of its prerequisites.** Please do not use comprehensions, generator expressions, or other syntax not previously covered. Please also follow [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines. + +## Help + +If you have any questions while implementing the exercise, please post the questions as comments in this issue, or contact one of the maintainers on our Slack channel. + + diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 0b6c7b3208..c262c9d2a2 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,72 +1,35 @@ -def value_of_card(card): - """ - - :param card: str - given card. - :return: int - value of a given card (J, Q, K = 10, numerical value otherwise). - """ - - if card == 'J' or card == 'Q' or card == 'K': - value = 10 - else: - value = int(card) - return value - - -def value_of_ace(hand_value): - """ - - :param hand_value: int - current hand value. - :return: int - value of the upcoming ace card (either 1 or 11). - """ - - if hand_value + 11 > 21: - value = 1 - else: - value = 11 - return value - - -def is_blackjack(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand is a blackjack (two cards worth 21). - """ - - if card_one == 'A' and card_two != 'A': - blackjack = value_of_card(card_two) == 10 - elif card_one != 'A' and card_two == 'A': - blackjack = value_of_card(card_one) == 10 - else: - blackjack = False - return blackjack - - -def can_split_pairs(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be split into two pairs (i.e. cards are of the same value). - """ - - if card_one == 'A' or card_two == 'A': - split_pairs = card_one == card_two - else: - split_pairs = value_of_card(card_one) == value_of_card(card_two) - return split_pairs - - -def can_double_down(card_one, card_two): - """ - - :param card_one: str - first card in hand. - :param card_two: str - second card in hand. - :return: bool - if the hand can be doubled down (i.e. totals 9, 10 or 11 points). - """ - - card_one_value = 1 if card_one == 'A' else value_of_card(card_one) - card_two_value = 1 if card_two == 'A' else value_of_card(card_two) - hand_value = card_one_value + card_two_value - return 9 <= hand_value <= 11 +""" Plane Tickets Exercise """ + +def generate_seats(amount): + + """ Generate a series of seat numbers for airline boarding. + + :param amount: Amount of seats to be generated. (int) + :return: Iterable that generates seat numbers. (generator) + + Seat numbers are generated with each row having 4 seats. + These should be sorted from low to high. + + Example: 3C, 3D, 4A, 4B + + """ + + for seat_index in range(1, amount+1): + yield f"{-(-5 // 4)}{['A','B','C','D'][seat_index % 4]}" + +def assign_seats(passengers): + + """ Assign seats to passenger. + + :param passengers: A list of strings containing names of passengers. (list[str]) + :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. + + Example output: {"Foo": "1A", "Bar": "1B"} + + """ + + pass + +if __name__ == "__main__": + for x in generate_seats(10): + print(x) \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 0402a1519c..8f8f2bdaab 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -8,7 +8,7 @@ def generate_seats(amount): :return: Iterable that generates seat numbers. (generator) Seat numbers are generated with each row having 4 seats. - These should count up from low to high. + These should be sorted from low to high. Example: 3C, 3D, 4A, 4B @@ -20,6 +20,11 @@ def assign_seats(passengers): """ Assign seats to passenger. - :param passengers: + :param passengers: A list of strings containing names of passengers. (list[str]) + :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. + + Example output: {"Foo": "1A", "Bar": "1B"} - """ \ No newline at end of file + """ + + pass \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index e69de29bb2..75a242aafd 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -0,0 +1,19 @@ +from types import GeneratorType +import unittest +import pytest + +from plane_tickets import ( + generate_seats, + assign_seats +) + +class PlaneTicketsTest(unittest.TestCase): + + @pytest.mark.task(taskno=1) + def test_task_1_type(self): + input = [5] + output = ["1A"] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + generator = generate_seats(input) + self.assertEqual(generator.__next__(), output) From 191b57c589b310c0b3f5beda332b0298f75782f4 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 11:35:47 +0100 Subject: [PATCH 095/128] Task 1 Exemplar --- exercises/concept/plane-tickets/.meta/exemplar.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index c262c9d2a2..7f91abaa30 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -14,8 +14,16 @@ def generate_seats(amount): """ - for seat_index in range(1, amount+1): - yield f"{-(-5 // 4)}{['A','B','C','D'][seat_index % 4]}" + # Could also be solved in two lines: + # for seat_index in range(amount): + # yield f"{-(-(seat_index+1) // 4)}{['A','B','C','D'][seat_index % 4]}" + + SEATS_IN_ROW = ["A", "B", "C", "D"] + + for seat in range(amount): + seat_letter = SEATS_IN_ROW[seat % 4] + row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? + yield str(row_number)+str(seat_letter) def assign_seats(passengers): From a38f8f3e4328d2cbb9073156e3054e925a4e8179 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 11:59:19 +0100 Subject: [PATCH 096/128] Exemplar now also skips row 13 --- .../concept/plane-tickets/.meta/exemplar.py | 19 ++++++++++--------- .../plane-tickets/plane_tickets_test.py | 10 +++++++++- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 7f91abaa30..9c09e42cfa 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -14,16 +14,15 @@ def generate_seats(amount): """ - # Could also be solved in two lines: - # for seat_index in range(amount): - # yield f"{-(-(seat_index+1) // 4)}{['A','B','C','D'][seat_index % 4]}" - SEATS_IN_ROW = ["A", "B", "C", "D"] + amount = amount+1 if amount >= 13 else amount + for seat in range(amount): - seat_letter = SEATS_IN_ROW[seat % 4] row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? - yield str(row_number)+str(seat_letter) + if row_number != 13: + seat_letter = SEATS_IN_ROW[seat % 4] + yield str(row_number)+str(seat_letter) def assign_seats(passengers): @@ -36,8 +35,10 @@ def assign_seats(passengers): """ - pass + amount = len(passengers) + output = {} + for passenger, seat_number in zip(passengers, generate_seats(amount)): + output[passenger] = seat_number if __name__ == "__main__": - for x in generate_seats(10): - print(x) \ No newline at end of file + print([seat for seat in generate_seats(13*4+3+1)]) \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 75a242aafd..be54278dd2 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -10,10 +10,18 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_task_1_type(self): + def test_task_1_type(self): # * Tests if [Task 1] actually returns a generator. input = [5] output = ["1A"] for variant, (input, output) in enumerate(zip(input, output), start=1): with self.subTest(f"variation #{variant}", input_data=input, output_data=output): generator = generate_seats(input) self.assertEqual(generator.__next__(), output) + + @pytest.mark.task(taskno=1) + def test_task_1_output(self): + input = [1, 2, 3, 4, 5] + output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + self.assertEqual([seat for seat in generate_seats(input)], output) \ No newline at end of file From e329bff8b2eddad784787aa7e0674b0586b4fc97 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:06:04 +0100 Subject: [PATCH 097/128] Fixed (skip row 13) --- exercises/concept/plane-tickets/.meta/exemplar.py | 5 +++-- exercises/concept/plane-tickets/plane_tickets.py | 2 ++ .../concept/plane-tickets/plane_tickets_test.py | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 9c09e42cfa..51c85cedf1 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -16,7 +16,7 @@ def generate_seats(amount): SEATS_IN_ROW = ["A", "B", "C", "D"] - amount = amount+1 if amount >= 13 else amount + amount = amount+4 if amount >= 13 else amount for seat in range(amount): row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? @@ -41,4 +41,5 @@ def assign_seats(passengers): output[passenger] = seat_number if __name__ == "__main__": - print([seat for seat in generate_seats(13*4+3+1)]) \ No newline at end of file + print([seat for seat in generate_seats(14*4)]) + print(len([seat for seat in generate_seats(5)])) \ No newline at end of file diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 8f8f2bdaab..d725d78334 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -7,6 +7,8 @@ def generate_seats(amount): :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) + There should be no row 13 + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index be54278dd2..904164e108 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -10,7 +10,7 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_task_1_type(self): # * Tests if [Task 1] actually returns a generator. + def test_task1_type(self): # * Tests if [Task 1] actually returns a generator. input = [5] output = ["1A"] for variant, (input, output) in enumerate(zip(input, output), start=1): @@ -19,9 +19,18 @@ def test_task_1_type(self): # * Tests if [Task 1] actually returns a generator. self.assertEqual(generator.__next__(), output) @pytest.mark.task(taskno=1) - def test_task_1_output(self): + def test_task1_basic(self): input = [1, 2, 3, 4, 5] output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] for variant, (input, output) in enumerate(zip(input, output), start=1): with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual([seat for seat in generate_seats(input)], output) \ No newline at end of file + self.assertEqual([seat for seat in generate_seats(input)], output) + + @pytest.mark.task(taskno=1) + def test_task1_skips_row_13(self): + input = [14*4] + output = [['1A', '1B', '1C', '1D', '2A', '2B', '2C', '2D', '3A', '3B', '3C', '3D', '4A', '4B', '4C', '4D', '5A', '5B', '5C', '5D', '6A', '6B', '6C', '6D', '7A', '7B', '7C', '7D', '8A', '8B', '8C', '8D', '9A', '9B', '9C', '9D', '10A', '10B', '10C', '10D', '11A', '11B', '11C', '11D', '12A', '12B', '12C', '12D', '14A', '14B', '14C', '14D', '15A', '15B', '15C', '15D']] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + self.assertEqual([seat for seat in generate_seats(input)], output) + \ No newline at end of file From 31592153f0c5f894859756b1257874783c91233a Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:28:25 +0100 Subject: [PATCH 098/128] Finished Task 1 & 2 --- .../concept/plane-tickets/.docs/introduction.md | 2 +- exercises/concept/plane-tickets/.meta/exemplar.py | 7 +++---- exercises/concept/plane-tickets/plane_tickets.py | 2 +- .../concept/plane-tickets/plane_tickets_test.py | 13 ++++++++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 6395f408a3..8c5dc214bc 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -27,7 +27,7 @@ For example, if you want to use the `squares()` generator we just constructed, w >>> for square in squares(list_of_numbers): >>> print(square) - 1 +1 4 9 16 diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 51c85cedf1..299b695401 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -7,6 +7,8 @@ def generate_seats(amount): :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) + There should be no row 13 + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. @@ -39,7 +41,4 @@ def assign_seats(passengers): output = {} for passenger, seat_number in zip(passengers, generate_seats(amount)): output[passenger] = seat_number - -if __name__ == "__main__": - print([seat for seat in generate_seats(14*4)]) - print(len([seat for seat in generate_seats(5)])) \ No newline at end of file + return output diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index d725d78334..93823180ac 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -29,4 +29,4 @@ def assign_seats(passengers): """ - pass \ No newline at end of file + pass diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 904164e108..634aa1fa56 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -10,7 +10,7 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) - def test_task1_type(self): # * Tests if [Task 1] actually returns a generator. + def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. input = [5] output = ["1A"] for variant, (input, output) in enumerate(zip(input, output), start=1): @@ -19,7 +19,7 @@ def test_task1_type(self): # * Tests if [Task 1] actually returns a generator. self.assertEqual(generator.__next__(), output) @pytest.mark.task(taskno=1) - def test_task1_basic(self): + def test_task1_output(self): input = [1, 2, 3, 4, 5] output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] for variant, (input, output) in enumerate(zip(input, output), start=1): @@ -33,4 +33,11 @@ def test_task1_skips_row_13(self): for variant, (input, output) in enumerate(zip(input, output), start=1): with self.subTest(f"variation #{variant}", input_data=input, output_data=output): self.assertEqual([seat for seat in generate_seats(input)], output) - \ No newline at end of file + + @pytest.mark.task(taskno=2) + def test_task2(self): + input = [['Passenger1', 'Passenger2', 'Passenger3', 'Passenger4', 'Passenger5'], ['TicketNo=5644', 'TicketNo=2273', 'TicketNo=493', 'TicketNo=5411', 'TicketNo=824']] + output = [{'Passenger1': '1A', 'Passenger2': '1B', 'Passenger3': '1C', 'Passenger4': '1D', 'Passenger5': '2A'}, {'TicketNo=5644': '1A', 'TicketNo=2273': '1B', 'TicketNo=493': '1C', 'TicketNo=5411': '1D', 'TicketNo=824': '2A'}] + for variant, (input, output) in enumerate(zip(input, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input, output_data=output): + self.assertEqual(assign_seats(input), output) From 9a60e002c4a4d284f3b9811d5e7ff2719b53171f Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:28:44 +0100 Subject: [PATCH 099/128] Remove Typing --- exercises/concept/plane-tickets/plane_tickets_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 634aa1fa56..a54f53ca73 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -1,4 +1,3 @@ -from types import GeneratorType import unittest import pytest From 5ea95dff1bf52a9b7b4c9801d7c98e5bf7a906c5 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 20 Sep 2021 15:05:25 +0200 Subject: [PATCH 100/128] Update TESTS.md --- docs/TESTS.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 9b0825a680..4687bc72f9 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -48,7 +48,6 @@ Successfully installed pytest-6.2.5 ... ```bash $ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint Successfully installed pytest-6.2.5 ... - ``` To check if the installation was successful: @@ -112,7 +111,8 @@ If you really want to be specific about what pytest returns on your screen, here #### Stop After First Failure [`-x`] -Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests after the first failed test. This will help when you want to debug a single failure at a time. +Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests after the first failed test. +This will help when you want to debug a single failure at a time. ```bash $ python -m pytest -x example_test.py @@ -128,7 +128,8 @@ FAILED example_test.py::ExampleTest::example_test_foo #### Failed Tests First [`--ff`] -`pytest-cache` remembers which tests failed last time you ran `pytest`, running `pytest --ff {exercise_test.py}` will run those previously failed tests first, then it will continue with the rest of the tests. This might speed up your testing if you are making a lot of smaller fixes. +`pytest-cache` remembers which tests failed last time you ran `pytest`, running `pytest --ff {exercise_test.py}` will run those previously failed tests first, then it will continue with the rest of the tests. +This might speed up your testing if you are making a lot of smaller fixes. ```bash $ python -m pytest --ff bob_test.py @@ -162,7 +163,8 @@ $ python3 -m pytest --pdb bob_test.py =============== 4 passed in 0.15s =============== ``` -When a test fails, `PDB` allows you to look at variables and how your code responds. If you want to learn how to use the `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. +When a test fails, `PDB` allows you to look at variables and how your code responds. +If you want to learn how to use the `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. ## Extending your IDE @@ -174,7 +176,9 @@ If you'd like to extend your IDE with some tools that will help you with testing **Note:** If you are running a [virtual environment](./tools.md) you do not need to _add to path_ as it should work fine. -Typing `python3 -m` every time you want to run a module can get a little annoying. You can add the `Scripts` folder of your Python installation to your path. If you do not know where you have installed Python, run the following command in your terminal: +Typing `python3 -m` every time you want to run a module can get a little annoying. +You can add the `Scripts` folder of your Python installation to your path. +If you do not know where you have installed Python, run the following command in your terminal: ```bash $ python3 -c "import os, sys; print(os.path.dirname(sys.executable))" From 46923d85413afb0e4d8250044540aabe2a270b3a Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 21 Sep 2021 10:14:00 +0200 Subject: [PATCH 101/128] Update TESTS.md --- docs/TESTS.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 4687bc72f9..6f9932d4c5 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -26,12 +26,14 @@ Pylint can be a bit much, so this [tutorial from pycqa.orgl](https://pylint.pycq --- + ## Pytest _Official pytest documentation can be found on the [pytest Wiki](https://pytest.org/en/latest/) page._ Pytest lets you test your solutions using our provided tests, and is what we use to validate your solutions on the website. + ### Installing pytest Pytest can be installed and updated using the built-in Python utility `pip`. @@ -152,17 +154,14 @@ Then, run the tests together with the previously explained arguments `-x` and`-- pytest -x -ff bob_test.py ``` -This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you run the test again, `pytest` will first test that failed test, then continue with the rest. +This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. +When you run the test again, `pytest` will first test that failed test, then continue with the rest. + #### Using PDB, the Python Debugger, with pytest If you want to truly debug like a pro, use the `--pdb` argument after the `pytest` command. -```bash -$ python3 -m pytest --pdb bob_test.py -=============== 4 passed in 0.15s =============== -``` - When a test fails, `PDB` allows you to look at variables and how your code responds. If you want to learn how to use the `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. From ccee77f662d108abd333f6c1880a1ecc32eb65a9 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Wed, 22 Sep 2021 18:31:17 +0200 Subject: [PATCH 102/128] Update TESTS.md --- docs/TESTS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/TESTS.md b/docs/TESTS.md index 6f9932d4c5..22c92d5368 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -217,3 +217,4 @@ markers = Whenever you run your tests, make sure that this file is in your _root_ or _working_ directory for Exercism exercises. _More information on customizing pytest can be found in the [PyTest docs](https://docs.pytest.org/en/6.2.x/customize.html#pytest-ini)_ + From be697adb85a776605a1cbc7c5504609f9734484e Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Fri, 24 Sep 2021 11:43:40 +0200 Subject: [PATCH 103/128] Start of TOOLS.md --- docs/TOOLS.md | 2 +- .../VSCode-EXT-Python-TestingConfiguration.png | Bin 0 -> 17203 bytes docs/img/VSCode-EXT-Python-TestsStatuses.png | Bin 0 -> 13707 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/img/VSCode-EXT-Python-TestingConfiguration.png create mode 100644 docs/img/VSCode-EXT-Python-TestsStatuses.png diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 80eb5e5a8b..fbe51ae742 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -181,7 +181,7 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma ### JupyterLab -[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. +[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. Jupyter comes pre-packaged with Anaconda. You can also install it in a **virtual environment** using `pip` or `conda`. Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists. diff --git a/docs/img/VSCode-EXT-Python-TestingConfiguration.png b/docs/img/VSCode-EXT-Python-TestingConfiguration.png new file mode 100644 index 0000000000000000000000000000000000000000..23235f235b4f776250b09cf7ebfc5d0d26ad05a2 GIT binary patch literal 17203 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 F{1;CCG1CA5 literal 0 HcmV?d00001 diff --git a/docs/img/VSCode-EXT-Python-TestsStatuses.png b/docs/img/VSCode-EXT-Python-TestsStatuses.png new file mode 100644 index 0000000000000000000000000000000000000000..923d8ab0ae38fd4c6ba0642daedcefb0d928a737 GIT binary patch literal 13707 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 Date: Mon, 27 Sep 2021 20:53:46 +0200 Subject: [PATCH 104/128] Finished writing VS Code part --- docs/TOOLS.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index fbe51ae742..4147c063ed 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -4,7 +4,9 @@ A list of tools, IDEs and editors that can help you write and debug your _Python *Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* -Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/Python/installation). +Before you can start coding, make sure that you have the proper version of Python installed. +Exercism currently supports `Python 3.8` and above. +For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/Python/installation). --- @@ -97,7 +99,9 @@ The Python extension from Microsoft is extremely useful because of its range of ##### Selecting the interpreter -The Python extensions supports the switching between multiple `interpreters`. This allows you to use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you can find more about in the [environments section](#environments). +The Python extensions supports the switching between multiple `interpreters`. +This allows you to use different Python environments for different projects. +This is also useful for when you are using `venv` or `conda`, which you can find more about in the [environments section](#environments). Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. From 6db7b176cc42f08b38d9e6e2bd82c007b0b8110b Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 27 Sep 2021 22:22:15 +0200 Subject: [PATCH 105/128] Added Pycharm docs & relative img links --- docs/TOOLS.md | 13 +++++++++---- .../VSCode-EXT-Python-TestingConfiguration.png | Bin 17203 -> 0 bytes docs/img/VSCode-EXT-Python-TestsStatuses.png | Bin 13707 -> 0 bytes 3 files changed, 9 insertions(+), 4 deletions(-) delete mode 100644 docs/img/VSCode-EXT-Python-TestingConfiguration.png delete mode 100644 docs/img/VSCode-EXT-Python-TestsStatuses.png diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 4147c063ed..9faf5c56bb 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -4,8 +4,8 @@ A list of tools, IDEs and editors that can help you write and debug your _Python *Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* -Before you can start coding, make sure that you have the proper version of Python installed. -Exercism currently supports `Python 3.8` and above. +Before you can start coding, make sure that you have the proper version of Python installed. +Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/Python/installation). --- @@ -187,8 +187,13 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma [Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. -Jupyter comes pre-packaged with Anaconda. You can also install it in a **virtual environment** using `pip` or `conda`. Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists. +Jupyter comes pre-packaged with Anaconda. +You can also install it in a **virtual environment** using `pip` or `conda`. +Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". +It is commonly used among data-scientists. #### Notable extensions -The built-in tools for Jupyter Lab are sufficient for almost all exercises on Exercism. Most extensions to Jupyter focus more on data-science applications and not general-purpose programming. Explore extensions at your own pleasure. +The built-in tools for Jupyter Lab are sufficient for almost all exercises on Exercism. +Most extensions to Jupyter focus more on data-science applications and not general-purpose programming. +Explore extensions at your own pleasure. diff --git a/docs/img/VSCode-EXT-Python-TestingConfiguration.png b/docs/img/VSCode-EXT-Python-TestingConfiguration.png deleted file mode 100644 index 23235f235b4f776250b09cf7ebfc5d0d26ad05a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17203 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 F{1;CCG1CA5 diff --git a/docs/img/VSCode-EXT-Python-TestsStatuses.png b/docs/img/VSCode-EXT-Python-TestsStatuses.png deleted file mode 100644 index 923d8ab0ae38fd4c6ba0642daedcefb0d928a737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13707 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 Date: Mon, 27 Sep 2021 23:54:10 +0200 Subject: [PATCH 106/128] Update TOOLS.md --- docs/TOOLS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 9faf5c56bb..308873e24c 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -185,7 +185,8 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma ### JupyterLab -[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. +[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. +Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. Jupyter comes pre-packaged with Anaconda. You can also install it in a **virtual environment** using `pip` or `conda`. From 1675034d24f4948ad1db0de94c89f58cf08f4f04 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 10:50:45 +0200 Subject: [PATCH 107/128] Added VENV docs --- docs/TOOLS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index 308873e24c..f7b9f6d77e 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -20,7 +20,7 @@ For more information, please refer to [Installing Python locally](https://exerci - [Sublime text](#sublime-text) - [JupyterLab](#jupyterlab) ---- + ## Environments From 64002630bb87a34e17102877ff1d178354d90468 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 18:53:22 +0200 Subject: [PATCH 108/128] Wrote Windows tutorial for adding to path --- docs/TESTS.md | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/TESTS.md b/docs/TESTS.md index 22c92d5368..9b0825a680 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -26,14 +26,12 @@ Pylint can be a bit much, so this [tutorial from pycqa.orgl](https://pylint.pycq --- - ## Pytest _Official pytest documentation can be found on the [pytest Wiki](https://pytest.org/en/latest/) page._ Pytest lets you test your solutions using our provided tests, and is what we use to validate your solutions on the website. - ### Installing pytest Pytest can be installed and updated using the built-in Python utility `pip`. @@ -50,6 +48,7 @@ Successfully installed pytest-6.2.5 ... ```bash $ python3 -m pip install pytest pytest-cache pytest-subtests pytest-pylint Successfully installed pytest-6.2.5 ... + ``` To check if the installation was successful: @@ -113,8 +112,7 @@ If you really want to be specific about what pytest returns on your screen, here #### Stop After First Failure [`-x`] -Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests after the first failed test. -This will help when you want to debug a single failure at a time. +Running the `pytest -x {exercise_test.py}` command, will run the tests like normal, but will stop the tests after the first failed test. This will help when you want to debug a single failure at a time. ```bash $ python -m pytest -x example_test.py @@ -130,8 +128,7 @@ FAILED example_test.py::ExampleTest::example_test_foo #### Failed Tests First [`--ff`] -`pytest-cache` remembers which tests failed last time you ran `pytest`, running `pytest --ff {exercise_test.py}` will run those previously failed tests first, then it will continue with the rest of the tests. -This might speed up your testing if you are making a lot of smaller fixes. +`pytest-cache` remembers which tests failed last time you ran `pytest`, running `pytest --ff {exercise_test.py}` will run those previously failed tests first, then it will continue with the rest of the tests. This might speed up your testing if you are making a lot of smaller fixes. ```bash $ python -m pytest --ff bob_test.py @@ -154,16 +151,18 @@ Then, run the tests together with the previously explained arguments `-x` and`-- pytest -x -ff bob_test.py ``` -This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. -When you run the test again, `pytest` will first test that failed test, then continue with the rest. - +This will test your solution. When `pytest` encounters a failed test, the program will stop and tell you which test failed. When you run the test again, `pytest` will first test that failed test, then continue with the rest. #### Using PDB, the Python Debugger, with pytest If you want to truly debug like a pro, use the `--pdb` argument after the `pytest` command. -When a test fails, `PDB` allows you to look at variables and how your code responds. -If you want to learn how to use the `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. +```bash +$ python3 -m pytest --pdb bob_test.py +=============== 4 passed in 0.15s =============== +``` + +When a test fails, `PDB` allows you to look at variables and how your code responds. If you want to learn how to use the `PDB` module, have a look at the [Python Docs](https://docs.python.org/3/library/pdb.html#module-pdb) or [this](https://realpython.com/python-debugging-pdb/) Real Python article. ## Extending your IDE @@ -175,9 +174,7 @@ If you'd like to extend your IDE with some tools that will help you with testing **Note:** If you are running a [virtual environment](./tools.md) you do not need to _add to path_ as it should work fine. -Typing `python3 -m` every time you want to run a module can get a little annoying. -You can add the `Scripts` folder of your Python installation to your path. -If you do not know where you have installed Python, run the following command in your terminal: +Typing `python3 -m` every time you want to run a module can get a little annoying. You can add the `Scripts` folder of your Python installation to your path. If you do not know where you have installed Python, run the following command in your terminal: ```bash $ python3 -c "import os, sys; print(os.path.dirname(sys.executable))" @@ -217,4 +214,3 @@ markers = Whenever you run your tests, make sure that this file is in your _root_ or _working_ directory for Exercism exercises. _More information on customizing pytest can be found in the [PyTest docs](https://docs.pytest.org/en/6.2.x/customize.html#pytest-ini)_ - From a3ba686e3de237c715e5b144396bffdb56ee1712 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Tue, 28 Sep 2021 20:51:09 +0200 Subject: [PATCH 109/128] Improved shared test file and added disclaimer to TOOLS.md --- docs/TOOLS.md | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index f7b9f6d77e..fbe51ae742 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -4,9 +4,7 @@ A list of tools, IDEs and editors that can help you write and debug your _Python *Disclaimer: This is a collection of tools that are popular in our community. We do not have any financial affiliation with any of the tools listed below. We think these tools do their job and there are most certainly other tools that can do those jobs as well.* -Before you can start coding, make sure that you have the proper version of Python installed. -Exercism currently supports `Python 3.8` and above. -For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/Python/installation). +Before you can start coding, make sure that you have the proper version of Python installed. Exercism currently supports `Python 3.8` and above. For more information, please refer to [Installing Python locally](https://exercism.org/docs/tracks/Python/installation). --- @@ -20,7 +18,7 @@ For more information, please refer to [Installing Python locally](https://exerci - [Sublime text](#sublime-text) - [JupyterLab](#jupyterlab) - +--- ## Environments @@ -99,9 +97,7 @@ The Python extension from Microsoft is extremely useful because of its range of ##### Selecting the interpreter -The Python extensions supports the switching between multiple `interpreters`. -This allows you to use different Python environments for different projects. -This is also useful for when you are using `venv` or `conda`, which you can find more about in the [environments section](#environments). +The Python extensions supports the switching between multiple `interpreters`. This allows you to use different Python environments for different projects. This is also useful for when you are using `venv` or `conda`, which you can find more about in the [environments section](#environments). Click on the "Select interpreter" button in the lower left-hand of your window, another window should pop up where you can select the interpreter you want to use. @@ -185,16 +181,10 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma ### JupyterLab -[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. -Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. +[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text. -Jupyter comes pre-packaged with Anaconda. -You can also install it in a **virtual environment** using `pip` or `conda`. -Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". -It is commonly used among data-scientists. +Jupyter comes pre-packaged with Anaconda. You can also install it in a **virtual environment** using `pip` or `conda`. Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists. #### Notable extensions -The built-in tools for Jupyter Lab are sufficient for almost all exercises on Exercism. -Most extensions to Jupyter focus more on data-science applications and not general-purpose programming. -Explore extensions at your own pleasure. +The built-in tools for Jupyter Lab are sufficient for almost all exercises on Exercism. Most extensions to Jupyter focus more on data-science applications and not general-purpose programming. Explore extensions at your own pleasure. From f619980036c779aafe78412509a8cd9e3b9c76c3 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 14 Oct 2021 09:13:13 +0200 Subject: [PATCH 110/128] Update docs/TOOLS.md Co-authored-by: BethanyG --- docs/TOOLS.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/TOOLS.md b/docs/TOOLS.md index fbe51ae742..a5673875b9 100644 --- a/docs/TOOLS.md +++ b/docs/TOOLS.md @@ -37,7 +37,6 @@ To create a virtual environment, `cd` to the directory you want to store your en ```bash $ python3 -m venv {name_of_virtualenv} created virtual environment ... in 8568ms -``` #### Activating your virtual environment From 823737fac61ad567c99f03600a8f5af53eba92ac Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 12:51:22 +0100 Subject: [PATCH 111/128] Cleanup --- .../plane-tickets/plane_tickets_test.py | 54 +++++++++++-------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index a54f53ca73..410d0d850b 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -8,35 +8,43 @@ class PlaneTicketsTest(unittest.TestCase): + @pytest.mark.task(taskno=1) - def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. - input = [5] + def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. + input_vars = [5] output = ["1A"] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - generator = generate_seats(input) - self.assertEqual(generator.__next__(), output) + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(generate_seats(input_var).__next__(), output) @pytest.mark.task(taskno=1) def test_task1_output(self): - input = [1, 2, 3, 4, 5] - output = [['1A'], ['1A', '1B'], ['1A', '1B', '1C'] ,['1A', '1B', '1C', '1D'], ['1A', '1B', '1C', '1D', '2A']] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual([seat for seat in generate_seats(input)], output) - + input_vars = [1, 2, 3, 4, 5] + output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(list(generate_seats(input_var)), output) + @pytest.mark.task(taskno=1) def test_task1_skips_row_13(self): - input = [14*4] - output = [['1A', '1B', '1C', '1D', '2A', '2B', '2C', '2D', '3A', '3B', '3C', '3D', '4A', '4B', '4C', '4D', '5A', '5B', '5C', '5D', '6A', '6B', '6C', '6D', '7A', '7B', '7C', '7D', '8A', '8B', '8C', '8D', '9A', '9B', '9C', '9D', '10A', '10B', '10C', '10D', '11A', '11B', '11C', '11D', '12A', '12B', '12C', '12D', '14A', '14B', '14C', '14D', '15A', '15B', '15C', '15D']] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual([seat for seat in generate_seats(input)], output) - + input_vars = [14 * 4] + output = [["1A", "1B", "1C", "1D", "2A", "2B", "2C", "2D", + "3A", "3B", "3C", "3D", "4A", "4B", "4C", "4D", + "5A", "5B", "5C", "5D", "6A", "6B", "6C", "6D", + "7A", "7B", "7C", "7D", "8A", "8B", "8C", "8D", + "9A", "9B", "9C", "9D", "10A", "10B", "10C", "10D", + "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D", + "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(list(generate_seats(input_var)), output) + @pytest.mark.task(taskno=2) def test_task2(self): - input = [['Passenger1', 'Passenger2', 'Passenger3', 'Passenger4', 'Passenger5'], ['TicketNo=5644', 'TicketNo=2273', 'TicketNo=493', 'TicketNo=5411', 'TicketNo=824']] - output = [{'Passenger1': '1A', 'Passenger2': '1B', 'Passenger3': '1C', 'Passenger4': '1D', 'Passenger5': '2A'}, {'TicketNo=5644': '1A', 'TicketNo=2273': '1B', 'TicketNo=493': '1C', 'TicketNo=5411': '1D', 'TicketNo=824': '2A'}] - for variant, (input, output) in enumerate(zip(input, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input, output_data=output): - self.assertEqual(assign_seats(input), output) + input_vars = [["Passenger1", "Passenger2", "Passenger3", "Passenger4", "Passenger5"], + ["TicketNo=5644", "TicketNo=2273", "TicketNo=493", "TicketNo=5411", "TicketNo=824"]] + output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, + {"TicketNo=5644": "1A", "TicketNo=2273": "1B", "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + self.assertEqual(assign_seats(input_var), output) From 48f0a79a3f73a20e1f99e763020e47f25edd34f9 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 13:09:45 +0100 Subject: [PATCH 112/128] Add some data in ./concepts --- concepts/generators/.meta/config.json | 4 +- concepts/generators/introduction.md | 96 ++++++++++++++++++++++++++- concepts/generators/links.json | 16 ++--- 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json index 9b9e8da5a9..63b59e1ead 100644 --- a/concepts/generators/.meta/config.json +++ b/concepts/generators/.meta/config.json @@ -1,5 +1,5 @@ { - "blurb": "TODO: add blurb for this concept", - "authors": ["bethanyg", "cmccandless"], + "blurb": "Learn about generators by assigning seats to passengers.", + "authors": ["bethanyg", "cmccandless", "J08K"], "contributors": [] } diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index fcde74642c..ca48239c2d 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -1,2 +1,96 @@ -#TODO: Add introduction for this concept. +# Instructions +A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). + +_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. + +## Constructing a generator + +Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). + +Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: + +```python +>>> def squares(list_of_numbers): +>>> for number in list_of_numbers: +>>> yield number ** 2 +``` + +## Using a generator + +It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. + +For example, if you want to use the `squares()` generator we just constructed, we simply use: + +```python +>>> list_of_numbers = [1, 2, 3, 4] + +>>> for square in squares(list_of_numbers): +>>> print(square) +1 +4 +9 +16 +``` + +You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. + +```python +square_generator = squares([1, 2]) + +>>> next(square_generator) +1 +>>> next(square_generator) +4 +``` + +When the `generator` has no more values to return it throws a `StopIteration` error. + +```python +>>> next(square_generator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +## The yield expression + +The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. + +When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. + +Note: _`yield` expressions are prohibited to be used outside of functions._ + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 + +>>> lets_try = yield_expression() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. + +You can also use it to generate complicated or infinite sequences, like this: + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/concepts/generators/links.json b/concepts/generators/links.json index eb5fb7c38a..b8ae2f7b64 100644 --- a/concepts/generators/links.json +++ b/concepts/generators/links.json @@ -1,18 +1,10 @@ [ { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://docs.python.org/3.8/reference/expressions.html#yield-expressions", + "description": "Official Python 3.8 docs for the yield expression." }, { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." - }, - { - "url": "http://example.com/", - "description": "TODO: add new link (above) and write a short description here of the resource." + "url": "https://en.wikipedia.org/wiki/Lazy_evaluation", + "description": "Wikipedia page about lazy evaluation" } ] From f658f95041d7551950f3d4d2f61d660a3454f0d4 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 14:02:14 +0100 Subject: [PATCH 113/128] Add exercise to config.json --- config.json | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/config.json b/config.json index 25b5653c3f..a2db51c000 100644 --- a/config.json +++ b/config.json @@ -181,8 +181,36 @@ "name": "Ellen's Alien Game", "uuid": "3550ec07-f6c6-48bd-b2b4-086e75faf9e7", "concepts": ["classes"], - "prerequisites": ["basics", "bools", "comparisons", "loops", "dicts", "lists", "numbers", "sets", "strings", "tuples"], + "prerequisites": ["basics", + "bools", + "comparisons", + "loops", + "dicts", + "lists", + "numbers", + "sets", + "strings", + "tuples" + ], "status": "beta" + }, + { + "slug": "plane-tickets", + "name": "Plane Tickets", + "uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810", + "concepts": ["generators"], + "prerequisites": [ + "conditionals", + "dicts", + "functions", + "higher-order-functions", + "lists", + "loops", + "iteration", + "iterators", + "sequences" + ], + "status": "wip" } ], "practice": [ From d4a5d54b11033da4f7ff00025e1454ccc1463188 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 4 Nov 2021 14:15:03 +0100 Subject: [PATCH 114/128] fixing --- config.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/config.json b/config.json index a2db51c000..10af6cfd60 100644 --- a/config.json +++ b/config.json @@ -202,13 +202,8 @@ "prerequisites": [ "conditionals", "dicts", - "functions", - "higher-order-functions", "lists", - "loops", - "iteration", - "iterators", - "sequences" + "loops" ], "status": "wip" } From 7c420d06e24ce68554545b76041c3422888e13d7 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Fri, 5 Nov 2021 10:33:03 +0100 Subject: [PATCH 115/128] Fixed all pylint warnings in Exemplar --- .../concept/plane-tickets/.meta/exemplar.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 299b695401..8fe0ddbda6 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,25 +1,25 @@ """ Plane Tickets Exercise """ +SEATS_IN_ROW = ["A", "B", "C", "D"] + def generate_seats(amount): - + """ Generate a series of seat numbers for airline boarding. - + :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) - + There should be no row 13 - + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. - + Example: 3C, 3D, 4A, 4B - + """ - - SEATS_IN_ROW = ["A", "B", "C", "D"] - + amount = amount+4 if amount >= 13 else amount - + for seat in range(amount): row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? if row_number != 13: @@ -27,14 +27,14 @@ def generate_seats(amount): yield str(row_number)+str(seat_letter) def assign_seats(passengers): - + """ Assign seats to passenger. - + :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. Example output: {"Foo": "1A", "Bar": "1B"} - + """ amount = len(passengers) From ca6047de48494d96aa7a40044f167aec03a3d863 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 15:33:51 +0100 Subject: [PATCH 116/128] Update concepts/generators/.meta/config.json Co-authored-by: BethanyG --- concepts/generators/.meta/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json index 63b59e1ead..2204a700df 100644 --- a/concepts/generators/.meta/config.json +++ b/concepts/generators/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "Learn about generators by assigning seats to passengers.", - "authors": ["bethanyg", "cmccandless", "J08K"], + "authors": ["J08K"], "contributors": [] } From fdf30cbab5e0ef0943eb13e1e63e24dd0d7cc407 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:17:54 +0100 Subject: [PATCH 117/128] Update concepts/generators/introduction.md --- concepts/generators/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index ca48239c2d..576136c830 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -1,4 +1,4 @@ -# Instructions +# Introduction A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). From a992d8cfed49d1423a899dc96180c7a89abd5594 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:18:41 +0100 Subject: [PATCH 118/128] Update exercises/concept/plane-tickets/.docs/instructions.md Co-authored-by: BethanyG --- exercises/concept/plane-tickets/.docs/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index a278a693b2..711665ab27 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -4,7 +4,7 @@ Conda airlines has over 10.000 flights a day, but they need to automate. They ar They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. -Conda's airplanes have up to _4 seats_ in each row, and each airplane can have many more rows. +Conda's airplanes have up to _4 seats_ in each row, and each airplane has many rows. While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being the first _seat_ in the row. From 29fd11b5211009dd975ba00fbf4a11eab81962c6 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Thu, 4 Nov 2021 20:26:11 +0100 Subject: [PATCH 119/128] Update exercises/concept/plane-tickets/.docs/introduction.md Co-authored-by: BethanyG --- exercises/concept/plane-tickets/.docs/introduction.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 8c5dc214bc..6bb14722c5 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -6,7 +6,7 @@ _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key ## Constructing a generator -Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). +Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: From 5291c3a9435227c340d39b21a938305402b84e12 Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Fri, 5 Nov 2021 10:12:34 +0100 Subject: [PATCH 120/128] Update concepts/generators/introduction.md Co-authored-by: Isaac Good --- concepts/generators/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index 576136c830..1049680fed 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -20,7 +20,8 @@ Lets say you want to construct a `generator` that generates all the _squares_ fr It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. -For example, if you want to use the `squares()` generator we just constructed, we simply use: +For example, if you want to use the `squares()` generator we just constructed, we simply write: + ```python >>> list_of_numbers = [1, 2, 3, 4] From 09f6e2952314ec139a72a26482fe2568e99fb3eb Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Fri, 5 Nov 2021 11:28:26 +0100 Subject: [PATCH 121/128] Update exercises/concept/plane-tickets/.meta/exemplar.py Co-authored-by: Isaac Good --- exercises/concept/plane-tickets/.meta/exemplar.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 8fe0ddbda6..08c759c288 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -18,7 +18,7 @@ def generate_seats(amount): """ - amount = amount+4 if amount >= 13 else amount + amount = amount + 4 if amount >= 13 else amount for seat in range(amount): row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? From 6e3f4bf25d37dc913bed611bdde929ea45122f20 Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Thu, 9 Dec 2021 13:07:07 +0100 Subject: [PATCH 122/128] Committed suggestions and Added new task 3 --- concepts/generators/about.md | 119 +++++++++++++++++- concepts/generators/introduction.md | 92 -------------- .../plane-tickets/.docs/instructions.md | 30 ++++- .../plane-tickets/.docs/introduction.md | 93 +------------- .../concept/plane-tickets/.meta/exemplar.py | 30 +++-- .../concept/plane-tickets/plane_tickets.py | 32 +++-- 6 files changed, 190 insertions(+), 206 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index c628150d56..df41e9f014 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -1,2 +1,119 @@ -#TODO: Add about for this concept. +# About +## Constructing a generator + +Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. + +An example is a function that returns the _squares_ from a given list of numbers. As currently written, all input must be processed before any values can be returned: + +```python +>>> def squares(list_of_numbers): +>>> squares = [] +>>> for number in list_of_numbers: +>>> squares.append(number ** 2) +>>> return squares +``` + +You can convert that function into a generator like this: + +```python +def squares(list_of_numbers): + for number in list_of_number: + yield number ** 2 +``` + +The rationale behind this is that you use a generator when you do not need all the values _at once_. + +This would safe you memory, since you store just the value you are _currently_ working on. + +## Using a generator + +Generators can usually be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. + +For example, if you want to use the `squares()` generator we just constructed, we simply use: + +```python +>>> squared_numbers = squares([1, 2, 3, 4]) + +>>> for square in squared_numbers: +>>> print(square) +1 +4 +9 +16 +``` + +Values within a generator can also be produced/accessed via the `next()` function. `next()` calls the `__next__()` attribute of a generator, which "advances" or evaluates the generator code to the `yield` expression, returning the "next" value. + +```python +square_generator = squares([1, 2]) + +>>> next(square_generator) +1 +>>> next(square_generator) +4 +``` + +When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error. + +```python +>>> next(square_generator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +### Difference between iterables and generators + +Generators and (`iterables`)[https://wiki.python.org/moin/Iterator] act very similar, but there are some important differences to note: + +- Generators are _one-way_; no backing up to a previous value. +- Iterating over generators consume the returned values; no resetting. +- Generators are not sortable and can not be reversed. +- Generators do _not_ have `indexes`, so you can't reference a previous or future value. +- Generators do _not_ implement the `len()` function. +- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator. + +## The yield expression + +The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. + +_Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested. + +When `yield` is evaluated, it pauses the execution of the enclosing function and returns any values of the function _at that point in time_. + +The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again. + +Note: _Using `yield` expressions is prohibited outside of functions._ + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 + +>>> lets_try = infinite_sequence() +>>> lets_try.__next__() +0 +>>> lets_try.__next__() +1 +``` + +## Why generators? + +Generators are useful in a lot of applications. + +When working with a large collection, you might not want to put all of its values into `memory`. A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. + +Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_: + +```python +>>> def infinite_sequence(): +>>> current_number = 0 +>>> while True: +>>> yield current_number +>>> current_number += 1 +``` + +Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md index 1049680fed..2c14837133 100644 --- a/concepts/generators/introduction.md +++ b/concepts/generators/introduction.md @@ -3,95 +3,3 @@ A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. - -## Constructing a generator - -Constructing a `generator` is a bit different than a normal `function`. You will need the `yield` expression, which we will go into depth with [later](#the-yield-expression). - -Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: - -```python ->>> def squares(list_of_numbers): ->>> for number in list_of_numbers: ->>> yield number ** 2 -``` - -## Using a generator - -It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. - -For example, if you want to use the `squares()` generator we just constructed, we simply write: - - -```python ->>> list_of_numbers = [1, 2, 3, 4] - ->>> for square in squares(list_of_numbers): ->>> print(square) -1 -4 -9 -16 -``` - -You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. - -```python -square_generator = squares([1, 2]) - ->>> next(square_generator) -1 ->>> next(square_generator) -4 -``` - -When the `generator` has no more values to return it throws a `StopIteration` error. - -```python ->>> next(square_generator) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -## The yield expression - -The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. - -When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. - -Note: _`yield` expressions are prohibited to be used outside of functions._ - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 - ->>> lets_try = yield_expression() ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -1 -``` - -## Why generators? - -Generators are useful in a lot of applications. - -When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. - -You can also use it to generate complicated or infinite sequences, like this: - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 -``` - -Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 711665ab27..dc7a79a77f 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Conda airlines has over 10.000 flights a day, but they need to automate. They are currently assigning all seats to passengers by hand. +Conda airlines is the programming-world's biggest airline, with over 10.000 flights a day! + +They are currently assigning all seats to passengers by hand, this will need to automated. They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant. @@ -41,8 +43,30 @@ Implement the `assign_seats()` function that returns a _dictionary_ of `passenge `passengers`: A list containing passenger names. ```python ->>> passengers = ["Jerimiah", "Eric", "Bethaney"] +>>> passengers = ['Jerimiah', 'Eric', 'Bethaney', 'Byte', 'SqueekyBoots', 'Bob'] >>> assign_seats(passengers) -{"Jerimiah" : "1A", "Eric" : "1B", "Bethaney" : "1C"} +{'Jerimiah': '1A', 'Eric': '1B', 'Bethaney': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'} +``` + +## 3. Ticket codes + +Each ticket has a _12_ character long string code for identification. + +This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`. + +Implement a `generator` that yields a `ticket_number` given the following arguments: + +`seat_numbers`: A _list_ of *seat_numbers*. +`flight_id`: A string containing the flight identification. + +```python +>>> seat_numbers = ['1A', '17D'] +>>> flight_id = 'CO1234' +>>> ticket_ids = generate_codes(seat_numbers, flight_id) + +>>> next(ticket_ids) +'1ACO12340000' +>>> next(ticket_ids) +'17DCO1234000' ``` diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index 6bb14722c5..fe531872ea 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -1,96 +1,5 @@ -# Instructions +# Introduction A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). _Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. - -## Constructing a generator - -Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. - -Lets say you want to construct a `generator` that generates all the _squares_ from a list of numbers. You would construct that function like this: - -```python ->>> def squares(list_of_numbers): ->>> for number in list_of_numbers: ->>> yield number ** 2 -``` - -## Using a generator - -It is possible to use any Python _function_ or _object_ that requires an `iterator` as an argument. - -For example, if you want to use the `squares()` generator we just constructed, we simply use: - -```python ->>> list_of_numbers = [1, 2, 3, 4] - ->>> for square in squares(list_of_numbers): ->>> print(square) -1 -4 -9 -16 -``` - -You can also get access to the values of a `generator` by using the `next()` function. The `next()` function calls the `__next__()` attribute of a generator. - -```python -square_generator = squares([1, 2]) - ->>> next(square_generator) -1 ->>> next(square_generator) -4 -``` - -When the `generator` has no more values to return it throws a `StopIteration` error. - -```python ->>> next(square_generator) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -## The yield expression - -The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression. Unlike the `return` expression, `yield` returns a _generator object_ to the caller. - -When `yield` is called, it pauses the execution of the function it is in and returns a value. When `__next__()` is called, it resumes the execution of the function. - -Note: _`yield` expressions are prohibited to be used outside of functions._ - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 - ->>> lets_try = yield_expression() ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -0 ->>> lets_try.__next__() -1 -``` - -## Why generators? - -Generators are useful in a lot of applications. - -When working with a large collection, you might not want to put all of that into `memory`. You can use generators to work on data piece-by-piece, this saves memory and improves performance. - -You can also use it to generate complicated or infinite sequences, like this: - -```python ->>> def infinite_sequence(): ->>> current_number = 0 ->>> while True: ->>> yield current_number ->>> current_number += 1 -``` - -Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1. diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py index 08c759c288..fa8927d759 100644 --- a/exercises/concept/plane-tickets/.meta/exemplar.py +++ b/exercises/concept/plane-tickets/.meta/exemplar.py @@ -1,13 +1,15 @@ -""" Plane Tickets Exercise """ +"""Plane Tickets Exercise""" -SEATS_IN_ROW = ["A", "B", "C", "D"] +import math + +SEATS_IN_ROW = ['A', 'B', 'C', 'D'] def generate_seats(amount): - """ Generate a series of seat numbers for airline boarding. + """Generate a series of seat numbers for airline boarding. :param amount: Amount of seats to be generated. (int) - :return: Iterable that generates seat numbers. (generator) + :return: Generator that generates seat numbers. (generator) There should be no row 13 @@ -21,14 +23,14 @@ def generate_seats(amount): amount = amount + 4 if amount >= 13 else amount for seat in range(amount): - row_number = -(-(seat+1) // 4) # ? Ceiling division; might be too advanced for students? + row_number = math.ceil((seat+1) / 4) if row_number != 13: seat_letter = SEATS_IN_ROW[seat % 4] - yield str(row_number)+str(seat_letter) + yield f'{str(row_number)}{seat_letter}' def assign_seats(passengers): - """ Assign seats to passenger. + """Assign seats to passenger. :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. @@ -42,3 +44,17 @@ def assign_seats(passengers): for passenger, seat_number in zip(passengers, generate_seats(amount)): output[passenger] = seat_number return output + +def generate_codes(seat_numbers, flight_id): + + """Generate codes for a ticket. + + :param seat_numbers: A list of seat numbers. (list[str]) + :param flight_id: A string containing the flight identification. (str) + :return: Generator that generates 12 character long strings. (generator[str]) + + """ + + for seat in seat_numbers: + base_string = f'{seat}{flight_id}' + yield base_string + '0' * (12 - len(base_string)) diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 93823180ac..10184a0850 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -1,32 +1,42 @@ -""" Plane Tickets Exercise """ +"""Plane Tickets Exercise""" def generate_seats(amount): - + """ Generate a series of seat numbers for airline boarding. - + :param amount: Amount of seats to be generated. (int) :return: Iterable that generates seat numbers. (generator) - + There should be no row 13 - + Seat numbers are generated with each row having 4 seats. These should be sorted from low to high. - + Example: 3C, 3D, 4A, 4B - + """ - + pass def assign_seats(passengers): - + """ Assign seats to passenger. - + :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. Example output: {"Foo": "1A", "Bar": "1B"} - + """ pass + +def generate_codes(seat_numbers, flight_id): + + """Generate codes for a ticket. + + :param seat_numbers: A list of seat numbers. (list[str]) + :param flight_id: A string containing the flight identification. (str) + :return: Generator that generates 12 character long strings. (generator[str]) + + """ From 17c25b1cc119d6107efaa03ccb279c9662ad4685 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Fri, 10 Dec 2021 14:49:09 -0800 Subject: [PATCH 123/128] Apply suggestions from code review Co-authored-by: Victor Goff --- concepts/generators/about.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index df41e9f014..82aafd928c 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -28,7 +28,7 @@ This would safe you memory, since you store just the value you are _currently_ w ## Using a generator -Generators can usually be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. +Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. For example, if you want to use the `squares()` generator we just constructed, we simply use: @@ -104,7 +104,8 @@ Note: _Using `yield` expressions is prohibited outside of functions._ Generators are useful in a lot of applications. -When working with a large collection, you might not want to put all of its values into `memory`. A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. +When working with a large collection, you might not want to put all of its values into `memory`. +A generator can be used to work on larger data piece-by-piece, saving memory and improving performance. Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_: From 35744f1c27f060e8da8a95fbf702a60e0b040e1d Mon Sep 17 00:00:00 2001 From: Job van der Wal <48634934+J08K@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:55:22 +0100 Subject: [PATCH 124/128] Apply suggestions from code review WHOOOOP Co-authored-by: BethanyG Co-authored-by: Victor Goff --- concepts/generators/about.md | 32 +++++++++++++------ .../plane-tickets/.docs/instructions.md | 3 +- .../plane-tickets/.docs/introduction.md | 2 +- .../concept/plane-tickets/.meta/config.json | 2 +- .../concept/plane-tickets/plane_tickets.py | 4 +-- 5 files changed, 28 insertions(+), 15 deletions(-) diff --git a/concepts/generators/about.md b/concepts/generators/about.md index 82aafd928c..f1b97291e4 100644 --- a/concepts/generators/about.md +++ b/concepts/generators/about.md @@ -2,9 +2,12 @@ ## Constructing a generator -Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. +Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later. + + +An example is a function that returns the _squares_ from a given list of numbers. +As currently written, all input must be processed before any values can be returned: -An example is a function that returns the _squares_ from a given list of numbers. As currently written, all input must be processed before any values can be returned: ```python >>> def squares(list_of_numbers): @@ -24,13 +27,14 @@ def squares(list_of_numbers): The rationale behind this is that you use a generator when you do not need all the values _at once_. -This would safe you memory, since you store just the value you are _currently_ working on. +This saves memory and processing power, since only the value you are _currently working on_ is calculated. + ## Using a generator Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument. -For example, if you want to use the `squares()` generator we just constructed, we simply use: +To use the `squares()` generator: ```python >>> squared_numbers = squares([1, 2, 3, 4]) @@ -43,7 +47,8 @@ For example, if you want to use the `squares()` generator we just constructed, w 16 ``` -Values within a generator can also be produced/accessed via the `next()` function. `next()` calls the `__next__()` attribute of a generator, which "advances" or evaluates the generator code to the `yield` expression, returning the "next" value. +Values within a generator can also be produced/accessed via the `next()` function. +`next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value. ```python square_generator = squares([1, 2]) @@ -65,13 +70,20 @@ StopIteration ### Difference between iterables and generators -Generators and (`iterables`)[https://wiki.python.org/moin/Iterator] act very similar, but there are some important differences to note: +Generators are a special sub-set of _iterators_. +`Iterators` are the mechanism/protocol that enables looping over _iterables_. +Generators and and the iterators returned by common Python (`iterables`)[https://wiki.python.org/moin/Iterator] act very similarly, but there are some important differences to note: + + +- Generators are _one-way_; there is no "backing up" to a previous value. -- Generators are _one-way_; no backing up to a previous value. - Iterating over generators consume the returned values; no resetting. -- Generators are not sortable and can not be reversed. -- Generators do _not_ have `indexes`, so you can't reference a previous or future value. -- Generators do _not_ implement the `len()` function. +- Generators (_being lazily evaluated_) are not sortable and can not be reversed. + +- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction. + +- Generators cannot be used with the `len()` function. + - Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator. ## The yield expression diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index dc7a79a77f..9ed19e38f5 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -24,7 +24,8 @@ Implement the `generate_seats()` function that returns an _iterable_ of seats gi `amount`: The amount of seats to be generated. -Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Make sure you also _don't_ assign seats to _row_ number 13. +Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. +Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. _Note: The returned seats should be ordered, like: 1A 1B 1C._ diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md index fe531872ea..1b557b447f 100644 --- a/exercises/concept/plane-tickets/.docs/introduction.md +++ b/exercises/concept/plane-tickets/.docs/introduction.md @@ -2,4 +2,4 @@ A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation). -_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed. +_Lazy iterators_ are similar to iterables such as `lists`, and other types of `iterators` in Python -- but with one key difference: `generators` do not store their `values` in memory, but _generate_ their values as needed or when called. diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json index 18a44a57c0..96559aa24c 100644 --- a/exercises/concept/plane-tickets/.meta/config.json +++ b/exercises/concept/plane-tickets/.meta/config.json @@ -2,7 +2,7 @@ "blurb": "Learn about generators by assigning seats to passengers.", "authors": ["J08K"], "icon": "poker", - "contributors": [], + "contributors": ["BethanyG"], "files": { "solution": ["plane_tickets.py"], "test": ["plane_tickets_test.py"], diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py index 10184a0850..44d685535b 100644 --- a/exercises/concept/plane-tickets/plane_tickets.py +++ b/exercises/concept/plane-tickets/plane_tickets.py @@ -5,7 +5,7 @@ def generate_seats(amount): """ Generate a series of seat numbers for airline boarding. :param amount: Amount of seats to be generated. (int) - :return: Iterable that generates seat numbers. (generator) + :return: Generator that yields seat numbers. There should be no row 13 @@ -20,7 +20,7 @@ def generate_seats(amount): def assign_seats(passengers): - """ Assign seats to passenger. + """ Assign seats to passengers. :param passengers: A list of strings containing names of passengers. (list[str]) :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values. From 07f3535fa5c14bac342dd9cb63f28c030625824a Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 13 Dec 2021 14:18:04 +0100 Subject: [PATCH 125/128] Add tests for task 3 --- .../concept/plane-tickets/.docs/instructions.md | 2 +- .../concept/plane-tickets/plane_tickets_test.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md index 9ed19e38f5..290aad2ebb 100644 --- a/exercises/concept/plane-tickets/.docs/instructions.md +++ b/exercises/concept/plane-tickets/.docs/instructions.md @@ -24,7 +24,7 @@ Implement the `generate_seats()` function that returns an _iterable_ of seats gi `amount`: The amount of seats to be generated. -Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. +Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers. Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13. _Note: The returned seats should be ordered, like: 1A 1B 1C._ diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 410d0d850b..0b50c0422e 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -3,7 +3,8 @@ from plane_tickets import ( generate_seats, - assign_seats + assign_seats, + generate_codes ) class PlaneTicketsTest(unittest.TestCase): @@ -48,3 +49,14 @@ def test_task2(self): for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): self.assertEqual(assign_seats(input_var), output) + + @pytest.mark.task(taskno=3) + def test_task3(self): + input_vars = [(["12A", "38B", "69C", "102B"],"KL1022"), + (["22C", "88B", "33A", "44B"], "DL1002")] + output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], + ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']] + for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): + seats, flight_nr = input_var + self.assertEqual(list(generate_codes(seats, flight_nr)), output) \ No newline at end of file From 85cfc47df9f4281ba4e6d31844a617b636aa53bd Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 13 Dec 2021 14:32:11 +0100 Subject: [PATCH 126/128] Better test case 1; add test for type task 3 --- exercises/concept/plane-tickets/plane_tickets_test.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index 0b50c0422e..f51b9fa1c1 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -1,3 +1,4 @@ +from typing import Generator import unittest import pytest @@ -12,11 +13,9 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. - input_vars = [5] - output = ["1A"] - for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): - with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(generate_seats(input_var).__next__(), output) + input_var = 5 + # Output technically not needed here, since we are testing for type. + self.assertIsInstance(generate_seats(input_var), Generator) @pytest.mark.task(taskno=1) def test_task1_output(self): From 6ee6cccaa7179c6e81110ba981ee05a20545da3f Mon Sep 17 00:00:00 2001 From: Job van der Wal Date: Mon, 13 Dec 2021 16:12:06 +0100 Subject: [PATCH 127/128] Don't you love pytests? --- .../plane-tickets/plane_tickets_test.py | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py index f51b9fa1c1..f4255c43af 100644 --- a/exercises/concept/plane-tickets/plane_tickets_test.py +++ b/exercises/concept/plane-tickets/plane_tickets_test.py @@ -14,16 +14,18 @@ class PlaneTicketsTest(unittest.TestCase): @pytest.mark.task(taskno=1) def test_task1_is_generator(self): # * Tests if [Task 1] actually returns a generator. input_var = 5 - # Output technically not needed here, since we are testing for type. - self.assertIsInstance(generate_seats(input_var), Generator) + output_type = Generator + error_message = f"Expected: {str(output_type)} type, but got a different type." + self.assertIsInstance(generate_seats(input_var), output_type, msg=error_message) @pytest.mark.task(taskno=1) def test_task1_output(self): input_vars = [1, 2, 3, 4, 5] output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seats(input_var)), output) + self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) @pytest.mark.task(taskno=1) def test_task1_skips_row_13(self): @@ -36,8 +38,9 @@ def test_task1_skips_row_13(self): "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D", "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(list(generate_seats(input_var)), output) + self.assertEqual(list(generate_seats(input_var)), output, msg=error_message) @pytest.mark.task(taskno=2) def test_task2(self): @@ -46,8 +49,16 @@ def test_task2(self): output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, {"TicketNo=5644": "1A", "TicketNo=2273": "1B", "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {output}, but something went wrong while assigning seats to passengers {input_var}." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - self.assertEqual(assign_seats(input_var), output) + self.assertEqual(assign_seats(input_var), output, msg=error_message) + + @pytest.mark.task(taskno=3) + def test_task3_is_generator(self): + input_var = ("11B", "HA80085") + output_type = Generator + error_message = f"Expected: {str(output_type)} type, but got a different type." + self.assertIsInstance(generate_codes(input_var[0], input_var[1]), output_type, msg=error_message) @pytest.mark.task(taskno=3) def test_task3(self): @@ -56,6 +67,6 @@ def test_task3(self): output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'], ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']] for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1): + error_message = f"Expected: {input_var}, but something went wrong while generating ticket numbers." with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output): - seats, flight_nr = input_var - self.assertEqual(list(generate_codes(seats, flight_nr)), output) \ No newline at end of file + self.assertEqual(list(generate_codes(input_var[0], input_var[1])), output, msg=error_message) \ No newline at end of file From abf417fe40337564bb499755de5f3334b327df01 Mon Sep 17 00:00:00 2001 From: BethanyG Date: Wed, 9 Mar 2022 17:50:39 -0800 Subject: [PATCH 128/128] Update config.json --- config.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/config.json b/config.json index 2ca275a0b4..31a38abdd8 100644 --- a/config.json +++ b/config.json @@ -208,19 +208,6 @@ "tuples" ], "status": "beta" - }, - { - "slug": "plane-tickets", - "name": "Plane Tickets", - "uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810", - "concepts": ["generators"], - "prerequisites": [ - "conditionals", - "dicts", - "lists", - "loops" - ], - "status": "wip" } ], "practice": [