├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── incorrect-solution.md │ └── new-challenge-suggestion.md └── README.md ├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── 100_essentials ├── circle │ ├── README.md │ ├── circle.py │ └── test_circle.py ├── coordinates │ ├── README.md │ ├── coordinates.py │ └── test_coordinates.py ├── greetings │ ├── README.md │ ├── greetings.py │ └── test_greetings.py ├── line │ ├── README.md │ ├── line.py │ └── test_line.py ├── log_transfer │ ├── README.md │ ├── log_transfer.py │ └── test_log_transfer.py ├── messages │ ├── README.md │ ├── messages.py │ └── test_messages.py └── secrets │ ├── README.md │ ├── secrets.py │ └── test_secrets.py ├── 200_digging_deeper ├── add │ ├── README.md │ ├── add.py │ └── test_add.py ├── compact │ ├── README.md │ ├── compact.py │ └── test_compact.py ├── format_ranges │ ├── README.md │ ├── format_ranges.py │ └── test_format_ranges.py ├── js-dict │ ├── README.md │ ├── js_dict.py │ └── test_js_dict.py └── tail │ ├── README.md │ ├── tail.py │ └── test_tail.py ├── 300_peculiarities └── .gitkeep ├── 400_paradigm_pathways └── .gitkeep ├── 500_the_standard_library └── fix_csv │ ├── README.md │ ├── fix_csv.py │ └── test_fix_csv.py ├── bin └── setup.py └── misc ├── alphabet_soup ├── alphabet_soup.py └── test_alphabet_soup.py ├── capitalization_and_mutability ├── capitalization_and_mutability.py └── test_capitalization_and_mutability.py ├── convert_mins_to_seconds ├── convert_minutes_to_seconds.py └── test_convert_minutes_to_seconds.py ├── perimeter_of_rectangle ├── find_perimeter_of_rectangle.py └── test_find_perimeter_of_rectangle.py ├── return_next_number ├── return_next_number.py └── test_return_next_number.py ├── return_sum_of_two_numbers ├── return_sum_of_two_numbers.py └── test_return_sum_of_two_numbers.py └── topological_sort ├── __pycache__ ├── test_topological_sort.cpython-38-pytest-5.4.3.pyc └── topological_sort.cpython-38.pyc ├── test_topological_sort.py └── topological_sort.py /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/incorrect-solution.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Incorrect solution 3 | about: Let us know that one of our challenges is incorrect 4 | title: "[ERRATA] -> (enter your issue title here)" 5 | labels: bug 6 | assignees: @py-lambdas/code-challenges 7 | 8 | --- 9 | 10 | ### Which specific challenge are you working on? 11 | 12 | e.g. The `Circle` challenge in the 100-level set of challenges. 13 | 14 | ### Tell us where we went wrong 15 | 16 | Take a paragraph or two and graciously explain what we've missed 17 | 18 | ### Actually correct solution 19 | 20 | Could you tell us a bit more about what the correct solution should be since it seems to have escaped us? 21 | 22 | ### Screenshots (optional) 23 | 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | ### System information:** 27 | - OS: [e.g. macOS] 28 | - Editor: [e.g. VS Code, PyCharm] 29 | - Python version: [e.g. 3.8.5] 30 | 31 | 32 | ### References 33 | 34 | If possible, please also include a list of links to any resources that demonstrate how the solution we currently require is incorrect. (i.e. a link to the official Python documentation) 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-challenge-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New challenge suggestion 3 | about: Tell us about a topic you'd like us to create a challenge for 4 | title: "[(change this to the topic or module you want to learn)] -> (change this to 5 | the title of your issue)" 6 | labels: challenge:new-addition 7 | assignees: @py-lambdas/code-challenges 8 | 9 | --- 10 | 11 | Erase the text from here to the start of the `References` heading replace it with as detailed a request as possible. Some great things to include would be: 12 | 13 | - The specific python topic or standard library module you're trying to learn more about 14 | - Reasons you've found it difficult to learn currently 15 | - What level you think the challenge would fall under (i.e. 100, 200, etc.) 16 | 17 | ### References 18 | 19 | Replace the text in this section with a list of links that illustrate the thing you're trying to learn. We'd especially like: 20 | 21 | - Links to the official Python documentation 22 | - Links to specific block of code in a code base that you found confusing 23 | - Links to medium articles, blog posts, or other media that talks about this module/topic but that you've found difficult to grasp. 24 | 25 | Remember, this repository is _specifically_ about Python and its standard library, if the challenge would require you to pip install anything, it's likely not something we're going to want to put in this particular repository. 26 | -------------------------------------------------------------------------------- /.github/README.md: -------------------------------------------------------------------------------- 1 | # Py-Lambdas Code Challenges 2 | 3 | > The best way to learn is by doing it! 4 | 5 | Welcome to the Py-Lambdas Code Challenges Page. 6 | 7 | These are code challenges and mini coding projects created by our Py-Lambdas community. 8 | 9 | ⚠️ Note: Construction ahead!! This is a work in progress and will be continually updated. 10 | 11 | ## Q&A 12 | 13 | ### Why would I use these challenges and not something like LeetCode? 14 | 15 | We think that sites like LeetCode, Exercism, HackerRank, etc. have their place; and the thing they're very good at in particular is preparing you for a whiteboarding interview. We're very decidedly _**not**_ providing that service. 16 | 17 | We see these challenges as the best way to deepen your Python knowledge specifically, and as a result focus on topics and skills specifically related to being a better _Python_ developer. 18 | 19 | If that sounds like something you're interested in, welcome! 20 | 21 | ### How do I get started? 22 | 23 | ***NOTE**: If you're an experienced developer and/or have opinions about your preferred text editor and environment management tool, then feel free to skip this section, fork & clone this repository, and get going however you'd like. However, if you're newer to Python, already use VS Code as your preferred editor, and/or just like an automated setup, keep reading.* 24 | 25 | We have an automated setup script that will get you started with our recommended environment. It will work on macOS, Windows, and Linux as long as the you've met the following prerequisites: 26 | 27 | - You have Python 3 installed on your system and available somewhere in your `$PATH` (e.g. if you run `python --version` in your terminal of choice you get `Python 3.x.x` or something similar) 28 | - You have VS Code installed and the associated `code` command available somewhere in your `$PATH` (i.e. if you run `code --version` you get output of some kind rather than an error). 29 | 30 | If you meet both of the above prerequesites, then the process for getting started looks like this: 31 | 32 | 1. [Fork this repository into your own account](https://github.com//Py-Lambdas/code-challenges/fork). This allows you to commit your solutions and push them to your own repository so you can show off your progress. 33 | 2. [Clone the repository to your local computer](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository). Generally this is done with `git clone https://github.com/Py-lambdas/code-challenges`, but you can also use the [GitHub CLI](https://cli.github.com/) by running `gh repo:clone Py-lambdas/code-challenges` 34 | 3. Navigate your terminal into the `code-challenges` directory. If you're using a *nix shell like bash, zsh, or fish, you'll use the `cd` command to do this (e.g. `cd code-challenges` or `cd path/to/code-challenges`) 35 | 4. Run `python3 bin/setup.py` in order to run the installer 36 | 5. Open the `code-challenges` repository in VS Code by running `code .` once step 4 completes successfully. 37 | 38 | Once you've done this setup process, you can just skip to step 5 every time you want to complete a challenge from then on. 39 | 40 | ### How do I check my code? 41 | 42 | There are a couple ways at your disposal, depending on who you are as a person. 43 | 44 | **t3rm1n4l is lyf3 and I think nice things make me weak**: 45 | 46 | Within the directory of the specific code challenge you're using, run the following command: 47 | 48 | ```shell 49 | python -m unittest 50 | ``` 51 | 52 | **I'm ✨fancy✨ and have ✨exquisite✨ taste**: 53 | 54 | We have a `launch.json` script that will run python's debugger inside of VS Code. 55 | 56 | There are two separate strategies for running it. The first is to create a `if __name__ == '__main__'` block in your python module that tests the code, and then use `breakpoint()` when you'd like to check something. For example if you're creating an `add` function that will add two numbers together, and you want the debugger to pause so you can check what your parameters are every time it runs, you would write the module like so: 57 | 58 | ```python 59 | 60 | def add(x, y): 61 | breakpoint() 62 | return x + y 63 | 64 | if __name__ == '__main__': 65 | add(2, 2) 66 | add(2, 4) 67 | add('one', 3) 68 | ``` 69 | 70 | To run this in debug mode, you'll go to the debug pane in VS Code (`CMD+SHIFT+D` on macOS, `CTRL+SHIFT+D` on windows), and then select the `Debug module` option in the drop down before clicking the play button. 71 | 72 | Alternatively, if you want to run the script in the context of the test inputs, you can add a breakpoint anywhere in your code and then skip to running the debugger _without_ adding a `if __name__ == '__main__'` block by running the `Debug with tests` option in the dropdown. 73 | 74 | More information on using VS Code's debugger can be found at the link below: 75 | https://code.visualstudio.com/docs/editor/debugging 76 | 77 | ### Can I help you out with code reviewing? 78 | 79 | Absolutely! All challenges have associated unit tests to check if the code is correct or not. However, we would love any help in manual code review when possible! We want this to be a _community effort_. 80 | 81 | ### Can I add code challenges? 82 | 83 | Yes! Please take a look through [our currently open `challenge:new-addition`) issues](https://github.com/Py-Lambdas/code-challenges/labels/challenge%3Anew-addition) and pick one that you think sounds interesting to work on. 84 | 85 | An example of a good challenge to use as a template for now would be the [Circle challenge](https://github.com/Py-Lambdas/code-challenges/tree/master/Circle). Specifically, your new challenge should have: 86 | 87 | - An informational `README.md` containing: 88 | - information about the challenge, and examples of the behavior you'll be testing for. 89 | - (Optionally) a bonus or two that stretch people to think more pythonically 90 | - A "Hints" section which links to documentation, articles, videos, and/or particularly informative Stack Overflow answers which provide the knowledge and background necessary to complete the challenge 91 | - A test file 92 | - tests should be written with `unittest` and not `pytest`. No one should have to pip install anything to complete or test a challenge 93 | - tests should be documented with docstrings so that people can look over the test file and know which requirement of the challenge each test is checking 94 | - Optional bonus challenges should have their tests decorated with the `@unittest.expectedFailure` decorator, and a comment telling people know to comment that decorator out if they'd like to attempt the bonus 95 | - The test file should be named `test_{CHALLENGE_NAME}.py` where `{CHALLENGE_NAME}` is the name of the challenge you're creating. 96 | - The test file should import the required classes/functions/etc. from the stub file (see below) 97 | - A stub file for the challenge 98 | - The stub file should be named `{CHALLENGE_NAME}.py` where `{CHALLENGE_NAME}` is the name of the challenge you're creating. 99 | - The stub file should only contain the absolute minimum and required objects necessary. For example, if your challenge is to create an `add` function that takes two numbers and returns their sum, your stub file should contain `def add(): pass` and the person completing the challenge should be responsible for all other implementation details, including function parameters. 100 | - If your challenge/your tests do not rely on any required function / class names, your stub file should be empty. 101 | 102 | Once you've created a challenge that meets the above structure and performs the way you'd like, make a Pull Request to the [challenges](https://github.com/Py-Lambdas/code-challenges/tree/challenges) branch with any challenges you want to add. 103 | 104 | Prior to submitting, please run your code through [black](https://github.com/psf/black) to standardize formatting. 105 | 106 | ### Can I get help if I am stuck? 107 | 108 | Yes! Reach out on Discord for any help you may need! 109 | 110 | ### What if I find an issue/bug in a challenge? 111 | 112 | Submit all issues [here](https://github.com/Py-Lambdas/code-challenges/issues). Issue submissions should include specific filename and the problem. If you have a solution for the bug, include that as well! 😀 113 | 114 | ## Challenges 🐍 115 | 116 | Challenges are organized from Very Easy -> Expert. Within each category, they are in no particular order. 117 | 118 | ### 100-level: Essentials 119 | 120 | This series of challenges is meant to ensure you understand the basics of Python. You may complete them in order, or jump around if there's a particular topic you'd like to challenge yourself on. 121 | 122 | | name | topics | author | 123 | | :------------------------------------------------------: | :----------------------------------------: | :--------------: | 124 | | [Coordinates](../100_essentials/coordinates/README.md) | `variables`, `numbers`, `types` | @duckeverlasting | 125 | | [Log Transfer](../100_essentials/log_transfer/README.md) | `variables`, `numbers`, `strings`, `types` | @duckeverlasting | 126 | | [Messages](../100_essentials/messages/README.md) | `strings`, `control flow` | @brannanc | 127 | | [Secrets](../100_essentials/secrets/README.md) | `loops`, `strings` | @brannanc | 128 | | [Greetings](../100_essentials/greetings/README.md) | `functions`, `parameters`, `annotations` | @brannanc | 129 | | [Line](../100_essentials/line/README.md) | `classes`, `methods`, `dunder methods` | @duckeverlasting | 130 | | [Circle](../100_essentials/Circle/README.md) | `classes`, `properties`, `dunder methods` | @nickcannariato | 131 | 132 | ### 200-level: Digging Deeper 133 | 134 | Now that you've got the essentials of Python under your belt, these challenges are meant to take you on a tour of some of the more commonly seen yet Python-specific features of the language. 135 | 136 | | name | topics | author | 137 | | :------------------------------------------------: | :--------------------------------: | :-------------: | 138 | | [add](../200_digging_deeper/add/README.md) | `comprehensions` | @nickcannariato | 139 | | [compact](../200_digging_deeper/compact/README.md) | `comprehensions`, `generators` | @nickcannariato | 140 | | [tail](../200_digging_deeper/tail/README.md) | `sequences`, `iterators`, `queues` | @nickcannariato | 141 | 142 | ### 300-level: Peculiarities 143 | 144 | Most programming language have opinions that are particular or peculiar, and Python is no exception. Static types vs dynamic types, pass by value or pass by reference, How you should raise and react to errors, etc. These challenges are meant to expose you to those peculiarities and help you think pythonically 145 | 146 | | name | topics | author | 147 | | :---------: | :-----------: | :----------: | 148 | | PLACEHOLDER | `PLACEHOLDER` | @PLACEHOLDER | 149 | 150 | ### 400-level: Paradigm pathways 151 | 152 | Python allows for both Object-oriented programming and Functional programming, and these challenges are designed to help you learn how it thinks about both of of those paradigms. 153 | 154 | | name | topics | author | 155 | | :---------: | :-----------: | :----------: | 156 | | PLACEHOLDER | `PLACEHOLDER` | @PLACEHOLDER | 157 | 158 | ### 500-level: The Standard Library 159 | 160 | Now that you've got a really good handle on Python's language features, we're gonna give you a detailed tour of Python's Standard Library. 161 | 162 | The particular module(s) used by a given challenge will be listed in the `modules` column, which you'll notice has replaced the `topics` column above. 163 | 164 | | name | modules | author | 165 | | :------------------------------------------------------: | :---------------: | :-------------: | 166 | | [fix_csv](../500_the_standard_library/fix_csv/README.md) | `csv`, `argparse` | @nickcannariato | 167 | 168 | ### Misc 169 | 170 | This `misc` folder of includes more traditional whiteboarding-focused challenges. 171 | 172 | As mentioned earlier in this README, this is _not_ the focus of this repository or the challenges we're currently interested in accepting, but we wanted to include them and potentially add to them in the future. 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/database,jupyternotebooks,linux,macos,pycharm+all,python,venv,virtualenv,visualstudiocode,windows 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=database,jupyternotebooks,linux,macos,pycharm+all,python,venv,virtualenv,visualstudiocode,windows 4 | 5 | ### Database ### 6 | *.accdb 7 | *.db 8 | *.dbf 9 | *.mdb 10 | *.pdb 11 | *.sqlite3 12 | 13 | ### JupyterNotebooks ### 14 | # gitignore template for Jupyter Notebooks 15 | # website: http://jupyter.org/ 16 | 17 | .ipynb_checkpoints 18 | */.ipynb_checkpoints/* 19 | 20 | # IPython 21 | profile_default/ 22 | ipython_config.py 23 | 24 | # Remove previous ipynb_checkpoints 25 | # git rm -r .ipynb_checkpoints/ 26 | 27 | ### Linux ### 28 | *~ 29 | 30 | # temporary files which can be created if a process still has a handle open of a deleted file 31 | .fuse_hidden* 32 | 33 | # KDE directory preferences 34 | .directory 35 | 36 | # Linux trash folder which might appear on any partition or disk 37 | .Trash-* 38 | 39 | # .nfs files are created when an open file is removed but is still being accessed 40 | .nfs* 41 | 42 | ### macOS ### 43 | # General 44 | .DS_Store 45 | .AppleDouble 46 | .LSOverride 47 | 48 | # Icon must end with two \r 49 | Icon 50 | 51 | # Thumbnails 52 | ._* 53 | 54 | # Files that might appear in the root of a volume 55 | .DocumentRevisions-V100 56 | .fseventsd 57 | .Spotlight-V100 58 | .TemporaryItems 59 | .Trashes 60 | .VolumeIcon.icns 61 | .com.apple.timemachine.donotpresent 62 | 63 | # Directories potentially created on remote AFP share 64 | .AppleDB 65 | .AppleDesktop 66 | Network Trash Folder 67 | Temporary Items 68 | .apdisk 69 | 70 | ### PyCharm+all ### 71 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 72 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 73 | 74 | # User-specific stuff 75 | .idea/**/workspace.xml 76 | .idea/**/tasks.xml 77 | .idea/**/usage.statistics.xml 78 | .idea/**/dictionaries 79 | .idea/**/shelf 80 | 81 | # Generated files 82 | .idea/**/contentModel.xml 83 | 84 | # Sensitive or high-churn files 85 | .idea/**/dataSources/ 86 | .idea/**/dataSources.ids 87 | .idea/**/dataSources.local.xml 88 | .idea/**/sqlDataSources.xml 89 | .idea/**/dynamic.xml 90 | .idea/**/uiDesigner.xml 91 | .idea/**/dbnavigator.xml 92 | 93 | # Gradle 94 | .idea/**/gradle.xml 95 | .idea/**/libraries 96 | 97 | # Gradle and Maven with auto-import 98 | # When using Gradle or Maven with auto-import, you should exclude module files, 99 | # since they will be recreated, and may cause churn. Uncomment if using 100 | # auto-import. 101 | # .idea/artifacts 102 | # .idea/compiler.xml 103 | # .idea/jarRepositories.xml 104 | # .idea/modules.xml 105 | # .idea/*.iml 106 | # .idea/modules 107 | # *.iml 108 | # *.ipr 109 | 110 | # CMake 111 | cmake-build-*/ 112 | 113 | # Mongo Explorer plugin 114 | .idea/**/mongoSettings.xml 115 | 116 | # File-based project format 117 | *.iws 118 | 119 | # IntelliJ 120 | out/ 121 | 122 | # mpeltonen/sbt-idea plugin 123 | .idea_modules/ 124 | 125 | # JIRA plugin 126 | atlassian-ide-plugin.xml 127 | 128 | # Cursive Clojure plugin 129 | .idea/replstate.xml 130 | 131 | # Crashlytics plugin (for Android Studio and IntelliJ) 132 | com_crashlytics_export_strings.xml 133 | crashlytics.properties 134 | crashlytics-build.properties 135 | fabric.properties 136 | 137 | # Editor-based Rest Client 138 | .idea/httpRequests 139 | 140 | # Android studio 3.1+ serialized cache file 141 | .idea/caches/build_file_checksums.ser 142 | 143 | ### PyCharm+all Patch ### 144 | # Ignores the whole .idea folder and all .iml files 145 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 146 | 147 | .idea/ 148 | 149 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 150 | 151 | *.iml 152 | modules.xml 153 | .idea/misc.xml 154 | *.ipr 155 | 156 | # Sonarlint plugin 157 | .idea/sonarlint 158 | 159 | ### Python ### 160 | # Byte-compiled / optimized / DLL files 161 | __pycache__/ 162 | *.py[cod] 163 | *$py.class 164 | 165 | # C extensions 166 | *.so 167 | 168 | # Distribution / packaging 169 | .Python 170 | build/ 171 | develop-eggs/ 172 | dist/ 173 | downloads/ 174 | eggs/ 175 | .eggs/ 176 | lib/ 177 | lib64/ 178 | parts/ 179 | sdist/ 180 | var/ 181 | wheels/ 182 | pip-wheel-metadata/ 183 | share/python-wheels/ 184 | *.egg-info/ 185 | .installed.cfg 186 | *.egg 187 | MANIFEST 188 | 189 | # PyInstaller 190 | # Usually these files are written by a python script from a template 191 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 192 | *.manifest 193 | *.spec 194 | 195 | # Installer logs 196 | pip-log.txt 197 | pip-delete-this-directory.txt 198 | 199 | # Unit test / coverage reports 200 | htmlcov/ 201 | .tox/ 202 | .nox/ 203 | .coverage 204 | .coverage.* 205 | .cache 206 | nosetests.xml 207 | coverage.xml 208 | *.cover 209 | *.py,cover 210 | .hypothesis/ 211 | .pytest_cache/ 212 | pytestdebug.log 213 | 214 | # Translations 215 | *.mo 216 | *.pot 217 | 218 | # Django stuff: 219 | *.log 220 | local_settings.py 221 | db.sqlite3 222 | db.sqlite3-journal 223 | 224 | # Flask stuff: 225 | instance/ 226 | .webassets-cache 227 | 228 | # Scrapy stuff: 229 | .scrapy 230 | 231 | # Sphinx documentation 232 | docs/_build/ 233 | doc/_build/ 234 | 235 | # PyBuilder 236 | target/ 237 | 238 | # Jupyter Notebook 239 | 240 | # IPython 241 | 242 | # pyenv 243 | .python-version 244 | 245 | # pipenv 246 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 247 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 248 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 249 | # install all needed dependencies. 250 | #Pipfile.lock 251 | 252 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 253 | __pypackages__/ 254 | 255 | # Celery stuff 256 | celerybeat-schedule 257 | celerybeat.pid 258 | 259 | # SageMath parsed files 260 | *.sage.py 261 | 262 | # Environments 263 | .env 264 | .venv 265 | env/ 266 | venv/ 267 | ENV/ 268 | env.bak/ 269 | venv.bak/ 270 | 271 | # Spyder project settings 272 | .spyderproject 273 | .spyproject 274 | 275 | # Rope project settings 276 | .ropeproject 277 | 278 | # mkdocs documentation 279 | /site 280 | 281 | # mypy 282 | .mypy_cache/ 283 | .dmypy.json 284 | dmypy.json 285 | 286 | # Pyre type checker 287 | .pyre/ 288 | 289 | # pytype static type analyzer 290 | .pytype/ 291 | 292 | ### VirtualEnv ### 293 | # Virtualenv 294 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 295 | 296 | ### VisualStudioCode ### 297 | .vscode/* 298 | *.code-workspace 299 | 300 | ### VisualStudioCode Patch ### 301 | # Ignore all local history of files 302 | .history 303 | 304 | ### Windows ### 305 | # Windows thumbnail cache files 306 | Thumbs.db 307 | Thumbs.db:encryptable 308 | ehthumbs.db 309 | ehthumbs_vista.db 310 | 311 | # Dump file 312 | *.stackdump 313 | 314 | # Folder config file 315 | [Dd]esktop.ini 316 | 317 | # Recycle Bin used on file shares 318 | $RECYCLE.BIN/ 319 | 320 | # Windows Installer files 321 | *.cab 322 | *.msi 323 | *.msix 324 | *.msm 325 | *.msp 326 | 327 | # Windows shortcuts 328 | *.lnk 329 | 330 | # End of https://www.toptal.com/developers/gitignore/api/database,jupyternotebooks,linux,macos,pycharm+all,python,venv,virtualenv,visualstudiocode,windows 331 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug module", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "internalConsole" 10 | }, 11 | { 12 | "name": "Debug with tests", 13 | "type": "python", 14 | "request": "launch", 15 | "program": "${fileDirname}${pathSeparator}test_${fileBasename}", 16 | "console": "internalConsole" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.diagnosticPublishDelay": 1000, 3 | "python.analysis.disabled": [], 4 | "python.analysis.errors": [ 5 | "inherit-non-class", 6 | "no-cls-argument", 7 | "no-self-argument", 8 | "parameter-already-specified", 9 | "parameter-missing", 10 | "positional-argument-after-keyword", 11 | "positional-only-named", 12 | "return-in-init", 13 | "typing-generic-arguments", 14 | "typing-typevar-arguments", 15 | "typing-newtype-arguments", 16 | "unresolved-import", 17 | "undefined-variable" 18 | ], 19 | "python.analysis.warnings": [ 20 | "unknown-parameter-name", 21 | "variable-not-defined-globally", 22 | "variable-not-defined-nonlocal" 23 | ], 24 | "python.analysis.information": [ 25 | "too-many-function-arguments", 26 | "too-many-positional-arguments-before-star", 27 | "no-method-argument" 28 | ], 29 | "python.autoComplete.addBrackets": false, 30 | "python.formatting.provider": "black", 31 | "python.languageServer": "Pylance", 32 | "python.linting.banditEnabled": false, 33 | "python.linting.flake8Enabled": false, 34 | "python.linting.flake8Args": ["--max-line-length", "88"], 35 | "python.linting.mypyEnabled": true, 36 | "python.sortImports.path": "isort", 37 | "python.sortImports.args": ["--profile", "black"], 38 | "python.venvPath": "${workspaceFolder}", 39 | "[python]": { 40 | "editor.insertSpaces": true, 41 | "editor.tabSize": 4, 42 | "editor.codeActionsOnSave": { 43 | "source.organizeImports": true 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /100_essentials/circle/README.md: -------------------------------------------------------------------------------- 1 | # Circle 2 | 3 | Create a class that represents a circle. 4 | 5 | The circle should have a radius, a diameter, and an area. It should also have a nice string representation. 6 | 7 | For example: 8 | 9 | ```python 10 | >>> c = Circle(5) 11 | >>> c 12 | Circle(5) 13 | >>> c.radius 14 | 5 15 | >>> c.diameter 16 | 10 17 | >>> c.area 18 | 78.53981633974483 19 | ``` 20 | 21 | Additionally the radius should default to 1 if no radius is specified when you create your circle: 22 | 23 | ```python 24 | >>> c = Circle() 25 | >>> c.radius 26 | 1 27 | >>> c.diameter 28 | 2 29 | ``` 30 | 31 | Make sure when the radius of your class changes that the diameter and area both automatically update: 32 | 33 | ```python 34 | >>> c = Circle(2) 35 | >>> c.radius = 1 36 | >>> c.diameter 37 | 2 38 | >>> c.area 39 | 3.141592653589793 40 | >>> c 41 | Circle(1) 42 | ``` 43 | 44 | You should also be able to set the diameter attribute in your class and have the radius update accordingly. 45 | 46 | However, attempting to update the area should raise an `AttributeError`: 47 | 48 | ```python 49 | >>> c = Circle(1) 50 | >>> c.diameter = 4 51 | >>> c.radius 52 | 2.0 53 | >>> c.area = 45.678 54 | Traceback (most recent call last): 55 | File "", line 1, in 56 | File "circle.py", line 16, in radius 57 | AttributeError: can't set attribute 58 | ``` 59 | 60 | Finally, a Circle's radius cannot be set to a negative number. If it's attempted, you should raise a `ValueError` exception with the error message `"Radius cannot be negative"`. 61 | 62 | ```python 63 | >>> c = Circle(5) 64 | >>> c.radius = 3 65 | >>> c.radius = -2 66 | Traceback (most recent call last): 67 | File "", line 1, in 68 | File "circle.py", line 27, in radius 69 | raise ValueError("Radius cannot be negative") 70 | ValueError: Radius cannot be negative 71 | >>> c = Circle (-10) 72 | Traceback (most recent call last): 73 | File "", line 1, in 74 | File "circle.py", line 27, in radius 75 | raise ValueError("Radius cannot be negative") 76 | ValueError: Radius cannot be negative 77 | ``` 78 | 79 | This means that diameter cannot be negative either (and setting diameter to a negative number should also raise a ValueError). 80 | 81 | ## Hints 82 | 83 | - [Formula and background on computing the area of a circle](https://en.wikipedia.org/wiki/Area_of_a_circle) 84 | - [Classes and Instances in Python](https://www.youtube.com/watch?v=ZDa-Z5JzLYM/) 85 | - [Understanding `__str__` and `__repr__`](https://medium.com/swlh/string-representations-in-python-understand-repr-vs-str-12f046986eb5) 86 | - [Python's `math` module: `math.pi`](https://docs.python.org/3/library/math.html#math.pi) 87 | - [Making an auto-updating attribute](https://www.youtube.com/watch?v=jCzT9XFZ5bw) 88 | - [Raising exceptions](https://stackoverflow.com/questions/2052390/manually-raising-throwing-an-exception-in-python) 89 | 90 | ## Tests 91 | 92 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 93 | 94 | Feel free to reference the tests if you have any confusion about the expected behavior of the Circle class. 95 | 96 | Good luck! 97 | -------------------------------------------------------------------------------- /100_essentials/circle/circle.py: -------------------------------------------------------------------------------- 1 | class Circle: 2 | # **Write your solution below** 3 | pass 4 | -------------------------------------------------------------------------------- /100_essentials/circle/test_circle.py: -------------------------------------------------------------------------------- 1 | import math 2 | import unittest 3 | 4 | from circle import Circle 5 | 6 | 7 | class CircleTests(unittest.TestCase): 8 | def test_radius(self): 9 | """ 10 | A circle should take a radius as an argument, and that radius 11 | sholud be accessible as an attribute. 12 | """ 13 | circle = Circle(5) 14 | self.assertEqual(circle.radius, 5) 15 | 16 | def test_default_radius(self): 17 | """ 18 | If no radius is provided, the radius should default to `1`. 19 | """ 20 | circle = Circle() 21 | self.assertEqual(circle.radius, 1) 22 | 23 | def test_diameter(self): 24 | """ 25 | The diameter of a circle should always be the radius of the circle times 2. 26 | """ 27 | circle = Circle(2) 28 | self.assertEqual(circle.diameter, 4) 29 | 30 | def test_area(self): 31 | """ 32 | The area of a circle should always be `π * diameter` 33 | """ 34 | circle = Circle(2) 35 | self.assertEqual(circle.area, math.pi * 4) 36 | circle = Circle(1) 37 | self.assertEqual(circle.area, math.pi) 38 | 39 | def test_string_representation(self): 40 | """ 41 | Circle should have a both a `str` representation and a `repr` 42 | representation equal to `Circle(radius)` 43 | """ 44 | circle = Circle(2) 45 | self.assertEqual(str(circle), "Circle(2)") 46 | self.assertEqual(repr(circle), "Circle(2)") 47 | circle.radius = 1 48 | self.assertEqual(repr(circle), "Circle(1)") 49 | 50 | def test_diameter_and_area_change_based_on_radius(self): 51 | """ 52 | Both the area and the diameter of the circle should update 53 | automatically when the radius changes. 54 | """ 55 | circle = Circle(2) 56 | self.assertEqual(circle.diameter, 4) 57 | circle.radius = 3 58 | self.assertEqual(circle.diameter, 6) 59 | self.assertEqual(circle.area, math.pi * 9) 60 | 61 | def test_diameter_changeable_but_area_not(self): 62 | """ 63 | The value of diameter should *also* be changable, and should update 64 | the radius automatically, but the area should raise an error when a 65 | caller attempts to change the value 66 | """ 67 | circle = Circle(2) 68 | self.assertEqual(circle.diameter, 4) 69 | self.assertEqual(circle.area, math.pi * 4) 70 | circle.diameter = 3 71 | self.assertEqual(circle.radius, 1.5) 72 | with self.assertRaises(AttributeError): 73 | circle.area = 3 74 | 75 | def test_no_negative_radius(self): 76 | """ 77 | A `ValueError` exception with the message 'radius cannot be negative' 78 | should be raised if a negative number is supplied for the radius. 79 | """ 80 | with self.assertRaises(ValueError) as context: 81 | circle = Circle(-2) 82 | self.assertEqual( 83 | str(context.exception).lower(), "radius cannot be negative", 84 | ) 85 | circle = Circle(2) 86 | with self.assertRaises(ValueError) as context: 87 | circle.radius = -10 88 | self.assertEqual( 89 | str(context.exception).lower(), "radius cannot be negative", 90 | ) 91 | with self.assertRaises(ValueError): 92 | circle.diameter = -20 93 | self.assertEqual(circle.radius, 2) 94 | 95 | 96 | if __name__ == "__main__": 97 | unittest.main(verbosity=2) 98 | -------------------------------------------------------------------------------- /100_essentials/coordinates/README.md: -------------------------------------------------------------------------------- 1 | # COORDINATES 2 | 3 | Find the slope of two coordinate pairs, and then find a third coordinate on the same slope. 4 | 5 | A company is building a series of sunglass bodegas in town and they have determined that the bodegas should be in a roughly straight line, for some reason. The town is divided into blocks, each of which can be definined by a set of two integers, `x` and `y`. Find the slope between two already existing bodegas, and use it to find the best location for a third, given the `x` coordinate. 6 | 7 | Declare a series of integers `x1`, `y1`, `x2`, and `y2`. 8 | 9 | - `x1` should equal 0 10 | - `y1` should equal 4 11 | - `x2` should equal 6 12 | - `y2` should equal 11 13 | 14 | Declare a variable `slope`. Slope should equal the slope from (x1, y1) to (x2, y2). 15 | 16 | Declare a third pair of coordinates, `x3` and `y3`. 17 | 18 | - `x3` should equal 14 19 | - `y3` should equal the *closest integer* along the line formed by the first two pairs of coordinates. 20 | 21 | ## Hints 22 | 23 | - [Numeric Types in Python](https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex) 24 | - The slope of a line given two pairs of coordinates is `(y2 - y1) / (x2 - x1)`. 25 | - The equation of a line is `y = slope * x + y_intercept` (The y_intercept is the value of y when x is equal to 0). 26 | - [Linear Equations](https://en.wikipedia.org/wiki/Linear_equation) 27 | - [Rounding in Python](https://docs.python.org/3/library/functions.html#round) 28 | 29 | ## Tests 30 | 31 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 32 | 33 | Good luck! 34 | -------------------------------------------------------------------------------- /100_essentials/coordinates/coordinates.py: -------------------------------------------------------------------------------- 1 | # Write your code here 2 | -------------------------------------------------------------------------------- /100_essentials/coordinates/test_coordinates.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import coordinates 4 | 5 | 6 | class coordinatesTests(unittest.TestCase): 7 | def test_integers(self): 8 | self.assertIsInstance(coordinates.x1, int) 9 | self.assertEqual(coordinates.x1, 0) 10 | self.assertIsInstance(coordinates.y1, int) 11 | self.assertEqual(coordinates.y1, 4) 12 | self.assertIsInstance(coordinates.x2, int) 13 | self.assertEqual(coordinates.x2, 6) 14 | self.assertIsInstance(coordinates.y2, int) 15 | self.assertEqual(coordinates.y2, 11) 16 | 17 | def test_slope(self): 18 | self.assertIsInstance(coordinates.slope, float) 19 | slope = (11 - 4) / (6 - 0) 20 | self.assertEqual(coordinates.slope, slope) 21 | self.assertTrue(slope == coordinates.slope) 22 | with open("./coordinates.py", "r") as file: 23 | source_code = file.read() 24 | self.assertNotIn( 25 | "1.16", 26 | source_code, 27 | msg="No, that's cheating. Code the equation for slope.", 28 | ) 29 | 30 | def test_third_coords(self): 31 | self.assertIsInstance(coordinates.x3, int) 32 | self.assertEqual(coordinates.x3, 14) 33 | self.assertIsInstance(coordinates.y3, int) 34 | self.assertEqual(coordinates.y3, 20) 35 | with open("./coordinates.py", "r") as file: 36 | source_code = file.read() 37 | self.assertNotIn( 38 | "20", 39 | source_code, 40 | msg="No, that's cheating. Don't hard code the answer. Hint: round(slope * x3 + y1)", 41 | ) 42 | 43 | 44 | if __name__ == "__main__": 45 | unittest.main(verbosity=2) 46 | -------------------------------------------------------------------------------- /100_essentials/greetings/README.md: -------------------------------------------------------------------------------- 1 | # Greetings 2 | 3 | Everything you need to complete this challenge can be found [here](https://docs.python.org/3.8/tutorial/controlflow.html#defining-functions). 4 | 5 | --- 6 | 7 | Define a function named `greetings` that takes a required positional argument `name` and a required [key word only](https://docs.python.org/3/glossary.html#term-parameter) argument `punctuation` and returns a greeting in the following format: 8 | 9 | ```python 10 | >>> greetings('Dave', punctuation='!') 11 | 'Hello, Dave!' 12 | ``` 13 | 14 | `greetings` should also take an optional key word argument `msg` that defaults to 'Hello'. 15 | 16 | ```python 17 | >>> greetings('Mark', msg='Oh, hi', punctuation='!') 18 | 'Oh, hi, Mark!' 19 | ``` 20 | 21 | Additionally, `greetings` should take an arbitrary amount of positional arguments to be added as titles in the following format. 22 | 23 | ```python 24 | >>> greetings('Bob', 'builder', 'tinker', 'tailor', 'soldier', 'spy', msg='Hi', punctuation='.') 25 | 'Hi, Bob, the builder, the tinker, the tailor, the soldier, the spy.' 26 | ``` 27 | 28 | `greetings` should also take an arbitrary amount of key word arguments. Each additional key word should be added to the message as ` says "".`. 29 | 30 | ```python 31 | >>> greetings('Dave', msg='Hi', punctuation='!', Mark='Howdy', Jim='You owe me money') 32 | 'Hi, Dave! Mark says "Howdy". Jim says "You owe me money".' 33 | ``` 34 | 35 | Add a doc string and annotations to `greetings`. A good doc string might describe what the function does and any inputs and outputs. For parameter and return annotations write a short description for each parameter or use [type hinting](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html). 36 | 37 | ## Hints 38 | 39 | - [Parameters in Python](https://docs.python.org/3/glossary.html#term-parameter) 40 | - [Python Docs tutorial section on fuctions](https://docs.python.org/3.8/tutorial/controlflow.html#defining-functions) 41 | - [Variable length arguments in python](https://www.youtube.com/watch?v=kB829ciAXo4) 42 | - [Documenting Python Code](https://realpython.com/documenting-python-code/) 43 | - [Type hint cheat sheet](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html) 44 | 45 | ## Tests 46 | 47 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 48 | 49 | Good luck! 50 | -------------------------------------------------------------------------------- /100_essentials/greetings/greetings.py: -------------------------------------------------------------------------------- 1 | def greetings(): 2 | # write your code here 3 | pass 4 | -------------------------------------------------------------------------------- /100_essentials/greetings/test_greetings.py: -------------------------------------------------------------------------------- 1 | from inspect import getfullargspec, signature 2 | import unittest 3 | 4 | from greetings import greetings 5 | 6 | 7 | class GreetingsTest(unittest.TestCase): 8 | def test_required_args(self): 9 | argsspec = getfullargspec(greetings) 10 | self.assertEqual(len(argsspec.args), 1) 11 | self.assertIsNotNone(argsspec.kwonlyargs) 12 | self.assertEqual(greetings('Dave', punctuation='!'), 'Hello, Dave!') 13 | 14 | def test_default_kw(self): 15 | argsspec = getfullargspec(greetings) 16 | self.assertIsNotNone(argsspec.kwonlydefaults) 17 | self.assertEqual(len(argsspec.kwonlydefaults), 1) 18 | self.assertIn('msg', argsspec.kwonlydefaults) 19 | self.assertEqual(argsspec.kwonlydefaults['msg'], 'Hello') 20 | self.assertEqual(greetings('Mark', msg='Oh, hi', 21 | punctuation='.'), 'Oh, hi, Mark.') 22 | 23 | def test_stargs(self): 24 | argsspec = getfullargspec(greetings) 25 | self.assertIsNotNone(argsspec.varargs) 26 | res = greetings('Bob', 'builder', 'tinker', 'tailor', 27 | 'soldier', 'spy', msg='Hi', punctuation='.') 28 | test = 'Hi, Bob, the builder, the tinker, the tailor, the soldier, the spy.' 29 | self.assertEqual(res, test) 30 | 31 | def test_kwargs(self): 32 | argsspec = getfullargspec(greetings) 33 | self.assertIsNotNone(argsspec.varkw) 34 | res = greetings('Dave', msg='Hi', punctuation='!', 35 | Mark='Howdy', Jim='You owe me money') 36 | test = 'Hi, Dave! Mark says "Howdy". Jim says "You owe me money".' 37 | self.assertEqual(res, test) 38 | 39 | def test_annotations(self): 40 | self.assertIsNotNone(greetings.__doc__, 41 | msg="Add a doc string to greetings function.") 42 | sig = signature(greetings) 43 | for p in sig.parameters: 44 | self.assertNotEqual( 45 | sig.parameters[p].annotation, sig.parameters[p].empty) 46 | print(sig.return_annotation) 47 | self.assertNotEqual( 48 | sig.return_annotation, 49 | sig.parameters[p].empty, 50 | msg="greetings function should have a return annotation", 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | unittest.main(verbosity=2) 56 | -------------------------------------------------------------------------------- /100_essentials/line/README.md: -------------------------------------------------------------------------------- 1 | # Line 2 | 3 | Create a class that represents a straight line. The class will take in two points as parameters, `point_a` and `point_b`. Points are tuples (x, y) with x and y representing location on a 2d plane. 4 | 5 | In Python you can change the string representation of a class. Change the string representation of class `Line` to make it more useful. For instance: 6 | 7 | ```python 8 | >>> l = Line((4, 8), (-2, 10)) 9 | >>> str(l) 10 | 'a: (4, 8), b: (-2, 10), distance: 6.32' 11 | ``` 12 | 13 | The class should have methods `translate_x` and `translate_y`. Each method should shift the line a given amount in the x or y direction. 14 | 15 | ```python 16 | >>> l = Line((4, 8), (-2, 10)) 17 | >>> l.translate_x(5) 18 | >>> str(l) 19 | 'a: (9, 8), b: (3, 10), distance: 6.32' 20 | ``` 21 | 22 | `line_1` is considered equal to `line_2` if the distance between `line_1`'s points is the same as the distance between `line_2`'s points. Comparisons to anything other than a line should return `False`. 23 | 24 | ```python 25 | >>> l1 = Line((1, 3), (-2, -4)) 26 | >>> l2 = Line((2, 4), (-1, -3)) 27 | >>> l1 == l2 28 | True 29 | >>> l3 = Line((4, 3), (1, 5)) 30 | >>> l1 == l3 31 | False 32 | ``` 33 | 34 | `line_1` is considered greater than `line_2` if the distance between `line_1`'s points is more than the distance between `line_2`'s points. 35 | 36 | ```python 37 | >>> l1 = Line((0, 0), (3, 4)) 38 | >>> l2 = Line((-1, -1), (4, 5)) 39 | >>> l1 < l2 40 | True 41 | ``` 42 | 43 | ## Bonus 44 | 45 | As a bonus, `Line` should have a `distance` property. 46 | 47 | ```python 48 | >>> l = Line((4, 8), (-2, 10)) 49 | >>> l.distance 50 | 6.324555320336759 51 | ``` 52 | 53 | 54 | ## Hints 55 | 56 | - [Classes in python](https://docs.python.org/3/tutorial/classes.html) 57 | - [Classes and Instances in Python](https://www.youtube.com/watch?v=ZDa-Z5JzLYM/) 58 | - [More on classes](https://realpython.com/python3-object-oriented-programming) 59 | - [Special method names (aka dunder methods)](https://docs.python.org/3/reference/datamodel.html#special-method-names) 60 | - [Understanding `__str__` and `__repr__`](https://medium.com/swlh/string-representations-in-python-understand-repr-vs-str-12f046986eb5) 61 | - [Measuring distance between two points](https://en.wikipedia.org/wiki/Distance#Mathematics) 62 | - [Operator behavior in classes](https://www.tutorialspoint.com/How-to-implement-Python-lt-gt-custom-overloaded-operators) 63 | 64 | - [Bonus: property decorators](https://docs.python.org/3/library/functions.html#property) 65 | - [Bonus: making an auto-updating attribute](https://www.youtube.com/watch?v=jCzT9XFZ5bw) 66 | 67 | ## Tests 68 | 69 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 70 | 71 | Good luck! 72 | 73 | -------------------------------------------------------------------------------- /100_essentials/line/line.py: -------------------------------------------------------------------------------- 1 | class Line: 2 | # **Write your solution below** 3 | pass -------------------------------------------------------------------------------- /100_essentials/line/test_line.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from line import Line 4 | 5 | 6 | class LineTests(unittest.TestCase): 7 | def test_init(self): 8 | line = Line((3, 10), (6, -4)) 9 | self.assertTrue(hasattr(line, "point_a")) 10 | self.assertTrue(hasattr(line, "point_b")) 11 | self.assertEqual(line.point_a, (3, 10)) 12 | self.assertEqual(line.point_b, (6, -4)) 13 | 14 | def test_str(self): 15 | line = Line((3, 10), (6, -4)) 16 | self.assertFalse(str(line).startswith(" 7) 45 | 46 | # To test the Bonus part of this exercise, comment out the following line 47 | # @unittest.expectedFailure 48 | def test_distance(self): 49 | line = Line((10, 6), (6, -1)) 50 | self.assertEqual(line.distance, 8.06225774829855) 51 | line.point_a = (9, 6) 52 | self.assertEqual(line.distance, 7.615773105863909) 53 | 54 | 55 | if __name__ == "__main__": 56 | unittest.main(verbosity=2) 57 | -------------------------------------------------------------------------------- /100_essentials/log_transfer/README.md: -------------------------------------------------------------------------------- 1 | # LOG TRANSFER 2 | 3 | You are working on a program to evaluate bank transactions, and you have been asked to write a function that checks whether an item is a deposit (positive), a withdrawl (negative), or neither. The function should take in a single parameter, `x`, and should return a string decribing the action: `Deposited $[number]`, `Withdrew $[number]`, or `Balance unchanged`. 4 | 5 | If `x` is an integer, the number should be kept as is: 6 | 7 | ```python 8 | >>> log_transfer(24) 9 | "Deposited $24" 10 | >>> log_transfer(-13) 11 | "Withdrew $13" 12 | >>> log_transfer(0) 13 | "Balance unchanged" 14 | ``` 15 | 16 | If `x` is a float, the number should be rounded to two decimal places before it is checked and returned. There's also a specific data type in the standard library that makes this easy and is more commonly used for currency because [floats are funky](https://www.youtube.com/watch?v=PZRI1IfStY0&ab_channel=Computerphile). 17 | 18 | ```python 19 | >>> log_transfer(499.2543) 20 | "Deposited $499.25" 21 | >>> log_transfer(-43.0387) 22 | "Withdrew $43.04" 23 | ``` 24 | 25 | If `x` is a string, it should be converted to a float, then handled as above. 26 | 27 | ```python 28 | >>> log_transfer("56.24142") 29 | "Deposited $56.24" 30 | ``` 31 | 32 | ## Bonus 33 | 34 | As a bonus, if `x` is not a string, int or float, raise a TypeError with the message "x must be a string, int, or float". 35 | 36 | If x is an invalid string (doesn't convert to a number), raise a ValueError with a message "x is not a valid number". 37 | 38 | ```python 39 | >>> log_transfer(True) 40 | TypeError: "x must be a string, int, or float" 41 | 42 | >>> log_transfer("a_bunch_of_money") 43 | ValueError: "x is not a valid number" 44 | ``` 45 | 46 | ## Hints 47 | 48 | - [Control Flow](https://docs.python.org/3/tutorial/controlflow.html) 49 | - [Type Function](https://docs.python.org/3/library/functions.html#type) 50 | - [Round Function](https://docs.python.org/3/library/functions.html#round) 51 | - [String Formatting in Python](https://www.w3schools.com/python/ref_string_format.asp) 52 | 53 | - [Bonus: Exceptions](https://docs.python.org/3/library/exceptions.html) 54 | - [Bonus: Raising Exceptions](https://docs.python.org/3/tutorial/errors.html#raising-exceptions) 55 | - [Bonus: Decimals in Python](https://docs.python.org/3/library/decimal.html) 56 | 57 | ## Tests 58 | 59 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 60 | 61 | Good luck! 62 | -------------------------------------------------------------------------------- /100_essentials/log_transfer/log_transfer.py: -------------------------------------------------------------------------------- 1 | def log_transfer(x): 2 | # **Write your solution below** 3 | pass 4 | -------------------------------------------------------------------------------- /100_essentials/log_transfer/test_log_transfer.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from log_transfer import log_transfer 4 | 5 | 6 | class LogTransferTests(unittest.TestCase): 7 | def test_int(self): 8 | self.assertEqual(log_transfer(53), "Deposited $53") 9 | self.assertEqual(log_transfer(-8), "Withdrew $8") 10 | self.assertEqual(log_transfer(0), "Balance unchanged") 11 | 12 | def test_float(self): 13 | self.assertEqual(log_transfer(-243.1263), "Withdrew $243.13") 14 | self.assertEqual(log_transfer(3654.0), "Deposited $3654.00") 15 | self.assertEqual(log_transfer(0.006), "Deposited $0.01") 16 | self.assertEqual(log_transfer(0.0), "Balance unchanged") 17 | self.assertEqual(log_transfer(-0.002), "Balance unchanged") 18 | 19 | def test_string(self): 20 | self.assertEqual(log_transfer("40.066"), "Deposited $40.07") 21 | self.assertEqual(log_transfer("-34"), "Withdrew $34.00") 22 | self.assertEqual(log_transfer("0"), "Balance unchanged") 23 | 24 | # To test the Bonus part of this exercise, comment out the following line 25 | @unittest.expectedFailure 26 | def test_exceptions(self): 27 | with self.assertRaises(TypeError) as context: 28 | log_transfer(True) 29 | self.assertEqual( 30 | str(context.exception).lower(), 31 | "x must be a string, int, or float", 32 | ) 33 | with self.assertRaises(ValueError) as context: 34 | log_transfer("ALLTHEMONIES") 35 | self.assertEqual( 36 | str(context.exception).lower(), 37 | "x is not a valid number", 38 | ) 39 | 40 | 41 | if __name__ == "__main__": 42 | unittest.main(verbosity=2) 43 | -------------------------------------------------------------------------------- /100_essentials/messages/README.md: -------------------------------------------------------------------------------- 1 | # Messages 2 | 3 | You've written a bunch of messages but you failed to realize that your shift key was broken. Write a function that Capitalizes the first letter of a message. 4 | 5 | ```python 6 | >>> fix_message('this is a very serious message xoxo') 7 | 'This is a very serious message xoxo' 8 | ``` 9 | 10 | For some reason you've signed all your messages with `xoxo`. That's embarrassing. Remove all instances of 'xoxo' from the message. 11 | 12 | ```python 13 | >>> fix_message('xoxo this is a very serious message xoxo') 14 | 'This is a very serious message' 15 | ``` 16 | 17 | Additionally, if the message is an empty string return None. 18 | 19 | ```python 20 | >>> fix_message('') 21 | None 22 | ``` 23 | 24 | And if the length of the message is over 50 characters, return a message with only the first 50 characters. 25 | 26 | ```python 27 | >>> msg = "we interrupt this program to annoy you and make things generally more irritating." 28 | >>> fix_message(msg) 29 | 'We interrupt this program to annoy you and make th' 30 | ``` 31 | 32 | ## Hints 33 | 34 | - [Strings introduction](https://docs.python.org/3/tutorial/introduction.html#strings) 35 | - [If, Else and control flow in Python](https://docs.python.org/3/tutorial/controlflow.html) 36 | - [String Methods](https://docs.python.org/3/library/stdtypes.html#string-methods) 37 | 38 | ## Tests 39 | 40 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 41 | 42 | Good luck! 43 | -------------------------------------------------------------------------------- /100_essentials/messages/messages.py: -------------------------------------------------------------------------------- 1 | def fix_message(msg): 2 | # write your code here 3 | return msg 4 | -------------------------------------------------------------------------------- /100_essentials/messages/test_messages.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from messages import fix_message 4 | 5 | 6 | class MessagesTests(unittest.TestCase): 7 | def test_capatalize(self): 8 | self.assertEqual(fix_message("a message"), "A message") 9 | 10 | def test_replace(self): 11 | self.assertEqual(fix_message("xoxo"), "") 12 | self.assertEqual(fix_message("xoxo something xoxo"), " something ") 13 | self.assertEqual( 14 | fix_message("xoxostuff"), 15 | "Stuff", 16 | msg="Remove xoxo first and then capitalize.", 17 | ) 18 | 19 | def test_empty(self): 20 | self.assertIsNone(fix_message("")) 21 | 22 | def test_over_50(self): 23 | msg = "we interrupt this program to annoy you and make things generally more irritating." 24 | res = "We interrupt this program to annoy you and make th" 25 | self.assertEqual(fix_message(msg), res) 26 | 27 | 28 | if __name__ == "__main__": 29 | unittest.main(verbosity=2) 30 | -------------------------------------------------------------------------------- /100_essentials/secrets/README.md: -------------------------------------------------------------------------------- 1 | # Secrets 2 | 3 | You and your friends have come up with a way to write secret messages to each other. Write a function that takes a string and an integer and returns a decoded message. 4 | 5 | Each word in the encoded message is separated by a change in case. Also, each character is "shifted" by a given amount. The returned string should be all lowercase. 6 | 7 | ```python 8 | >>> encoded_message = 'ifmmpXPSME' 9 | >>> secrets(encoded_message, -1) 10 | 'hello world' 11 | ``` 12 | 13 | ## Hints 14 | 15 | - [For statement in Python](https://docs.python.org/3/tutorial/controlflow.html#for-statements) 16 | - [For loops](https://wiki.python.org/moin/ForLoop) 17 | - [ord and chr functions](https://www.askpython.com/python/built-in-methods/python-chr-and-ord-methods) 18 | - [String constants](https://docs.python.org/3.8/library/string.html#module-string) 19 | - [Ascii table](http://www.asciitable.com/) 20 | 21 | ## Tests 22 | 23 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 24 | 25 | Good luck! 26 | -------------------------------------------------------------------------------- /100_essentials/secrets/secrets.py: -------------------------------------------------------------------------------- 1 | def secrets(s, key): 2 | # **Write your solution below** 3 | pass 4 | -------------------------------------------------------------------------------- /100_essentials/secrets/test_secrets.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from secrets import secrets 4 | 5 | 6 | class SecretsTest(unittest.TestCase): 7 | def test_secrets(self): 8 | res = secrets('ifmmpXPSME', -1) 9 | self.assertEqual(res, 'hello world') 10 | res = secrets('mxxKAGDnmeqMDQnqxazsFAge', 40) 11 | self.assertEqual(res, 'all your base are belong to us') 12 | res = secrets('XPSMEifmmp', -1) 13 | self.assertEqual(res, 'world hello') 14 | 15 | 16 | if __name__ == "__main__": 17 | unittest.main(verbosity=2) 18 | -------------------------------------------------------------------------------- /200_digging_deeper/add/README.md: -------------------------------------------------------------------------------- 1 | # Add 2 | 3 | You'll need to write a function that accepts two lists-of-lists of numbers and returns one list-of-lists with each of the corresponding numbers in the two given lists-of-lists added together. 4 | 5 | It should work something like this: 6 | 7 | ```python 8 | >>> matrix1 = [[1, -2], [-3, 4]] 9 | >>> matrix2 = [[2, -1], [0, -1]] 10 | >>> add(matrix1, matrix2) 11 | [[3, -3], [-3, 3]] 12 | >>> matrix1 = [[1, -2, 3], [-4, 5, -6], [7, -8, 9]] 13 | >>> matrix2 = [[1, 1, 0], [1, -2, 3], [-2, 2, -2]] 14 | >>> add(matrix1, matrix2) 15 | [[2, -1, 3], [-3, 3, -3], [5, -6, 7]] 16 | ``` 17 | 18 | You should solve this problem using only the standard library (e.g. without using a third-party package like `pandas`). 19 | 20 | Before attempting any bonuses, I'd like you to put some effort into figuring out the clearest and most idiomatic way to solve this problem. If you're using indexes to loop, take a look at the first hint. 21 | 22 | ## Bonus 1 23 | 24 | Modify your add function to accept and "add" any number of lists-of-lists. ✔️ 25 | 26 | ```python 27 | >>> matrix1 = [[1, 9], [7, 3]] 28 | >>> matrix2 = [[5, -4], [3, 3]] 29 | >>> matrix3 = [[2, 3], [-3, 1]] 30 | >>> add(matrix1, matrix2, matrix3) 31 | [[8, 8], [7, 7]] 32 | ``` 33 | 34 | ## Bonus 2 35 | 36 | If the arguments provided to the `add` function are different shapes, raise a `ValueError`. 37 | 38 | ```python 39 | >>> add([[1, 9], [7, 3]], [[1, 2], [3]]) 40 | Traceback (most recent call last): 41 | File "", line 1, in 42 | File "add.py", line 10, in add 43 | raise ValueError("Given matrices are not the same size.") 44 | ValueError: Given matrices are not the same size. 45 | ``` 46 | 47 | ## Hints 48 | 49 | - [How to loop with and without Indexes in Python](http://treyhunner.com/2016/04/how-to-loop-with-indexes-in-python/) 50 | - [Using tuple unpacking to improve readability](https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/) 51 | - [Python List comprehensions](https://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/) 52 | - [Using `*`'s in Python for packing and unpacking](https://treyhunner.com/2018/10/asterisks-in-python-what-they-are-and-how-to-use-them/#Asterisks_for_packing_arguments_given_to_function) 53 | - [StackOverflow: Raising an exception in Python](https://stackoverflow.com/questions/2052390/manually-raising-throwing-an-exception-in-python) 54 | 55 | ## Tests 56 | 57 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 58 | 59 | If you'd like to try the bonus challenges, you'll want to comment out the noted lines of code in the test file to test them properly. 60 | 61 | Finally, Feel free to reference the tests if you have any confusion about the expected behavior of the add function. 62 | 63 | Good luck! 64 | -------------------------------------------------------------------------------- /200_digging_deeper/add/add.py: -------------------------------------------------------------------------------- 1 | def add(): 2 | # **Write your solution below:** 3 | pass 4 | -------------------------------------------------------------------------------- /200_digging_deeper/add/test_add.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | import unittest 3 | 4 | from add import add 5 | 6 | 7 | class AddTests(unittest.TestCase): 8 | 9 | """Tests for add.""" 10 | 11 | def test_single_items(self): 12 | self.assertEqual(add([[5]], [[-2]]), [[3]]) 13 | 14 | def test_two_by_two_matrixes(self): 15 | m1 = [[6, 6], [3, 1]] 16 | m2 = [[1, 2], [3, 4]] 17 | m3 = [[7, 8], [6, 5]] 18 | self.assertEqual(add(m1, m2), m3) 19 | 20 | def test_two_by_three_matrixes(self): 21 | m1 = [[1, 2, 3], [4, 5, 6]] 22 | m2 = [[-1, -2, -3], [-4, -5, -6]] 23 | m3 = [[0, 0, 0], [0, 0, 0]] 24 | self.assertEqual(add(m1, m2), m3) 25 | 26 | def test_input_unchanged(self): 27 | m1 = [[6, 6], [3, 1]] 28 | m2 = [[1, 2], [3, 4]] 29 | m1_original = deepcopy(m1) 30 | m2_original = deepcopy(m2) 31 | add(m1, m2) 32 | self.assertEqual(m1, m1_original) 33 | self.assertEqual(m2, m2_original) 34 | 35 | # To test the Bonus part of this exercise, comment out the following line 36 | @unittest.expectedFailure 37 | def test_any_number_of_matrixes(self): 38 | m1 = [[6, 6], [3, 1]] 39 | m2 = [[1, 2], [3, 4]] 40 | m3 = [[2, 1], [3, 4]] 41 | m4 = [[9, 9], [9, 9]] 42 | m5 = [[31, 32], [27, 24]] 43 | self.assertEqual(add(m1, m2, m3), m4) 44 | self.assertEqual(add(m2, m3, m1, m1, m2, m4, m1), m5) 45 | 46 | # To test the Bonus part of this exercise, comment out the following line 47 | @unittest.expectedFailure 48 | def test_different_matrix_size(self): 49 | m1 = [[6, 6], [3, 1]] 50 | m2 = [[1, 2], [3, 4], [5, 6]] 51 | m3 = [[6, 6], [3, 1, 2]] 52 | with self.assertRaises(ValueError): 53 | add(m1, m2) 54 | with self.assertRaises(ValueError): 55 | add(m1, m3) 56 | with self.assertRaises(ValueError): 57 | add(m1, m1, m1, m3, m1, m1) 58 | with self.assertRaises(ValueError): 59 | add(m1, m1, m1, m2, m1, m1) 60 | 61 | 62 | if __name__ == "__main__": 63 | unittest.main(verbosity=2) 64 | -------------------------------------------------------------------------------- /200_digging_deeper/compact/README.md: -------------------------------------------------------------------------------- 1 | # Compact 2 | 3 | Write a function that accepts a sequence (a `list` for example) and returns a new iterable (anything you can loop over) with **adjacent** duplicate values removed. 4 | 5 | For example: 6 | 7 | ```python 8 | >>> compact([1, 1, 1]) 9 | [1] 10 | >>> compact([1, 1, 2, 2, 3, 2]) 11 | [1, 2, 3, 2] 12 | >>> compact([]) 13 | [] 14 | ``` 15 | 16 | There are two bonuses for this exercise. 17 | 18 | I recommend solving the exercise without the bonuses first and then attempting each bonus separately. 19 | 20 | ## Bonus 1 21 | 22 | Refactor the `compact` function to accept any iterable, not just a sequence (which means you can't use index look-ups in your answer). 23 | 24 | Here's an example with a generator expression, which is a lazy iterable: 25 | 26 | ```python 27 | >>> compact(n**2 for n in [1, 2, 2]) 28 | [1, 4] 29 | ``` 30 | 31 | ## Bonus 2 32 | 33 | Refactor the `compact` function once more to return an iterator instead of a `list`. 34 | 35 | ```python 36 | >>> c = compact(n**2 for n in [1, 2, 2]) 37 | >>> iter(c) is c 38 | True 39 | ``` 40 | 41 | This should allow your compact function to accept iterables that are theoretically infinite in length (or other lazy iterables). 42 | 43 | ## Hints 44 | 45 | - [The Pythonic way to loop with indexes in Python (i.e. _stop using `range`_)](https://treyhunner.com/2016/04/how-to-loop-with-indexes-in-python/) 46 | - [How to create iterators in Python](https://treyhunner.com/2018/06/how-to-make-an-iterator-in-python/) 47 | - [**Only read this if you've completed the challenge + bonuses**: An interesting solution using the standard library](https://stackoverflow.com/questions/41511555/fast-removal-of-consecutive-duplicates-in-a-list-and-corresponding-items-from-an/41511571#41511571) 48 | 49 | ## Tests 50 | 51 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 52 | 53 | If you'd like to try the bonus challenges, you'll want to comment out the noted lines of code in the test file to test them properly. 54 | 55 | Finally, Feel free to reference the tests if you have any confusion about the expected behavior of the add function. 56 | 57 | Good luck! 58 | -------------------------------------------------------------------------------- /200_digging_deeper/compact/compact.py: -------------------------------------------------------------------------------- 1 | def compact(): 2 | # **Write your solution here** 3 | pass 4 | -------------------------------------------------------------------------------- /200_digging_deeper/compact/test_compact.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | from compact import compact 5 | 6 | 7 | class CompactTests(unittest.TestCase): 8 | def assertIterableEqual(self, iterable1, iterable2): 9 | self.assertEqual(list(iterable1), list(iterable2)) 10 | 11 | def test_no_duplicates(self): 12 | self.assertIterableEqual(compact([1, 2, 3]), [1, 2, 3]) 13 | 14 | def test_adjacent_duplicates(self): 15 | self.assertIterableEqual(compact([1, 1, 2, 2, 3]), [1, 2, 3]) 16 | 17 | def test_non_adjacent_duplicates(self): 18 | self.assertIterableEqual(compact([1, 2, 3, 1, 2]), [1, 2, 3, 1, 2]) 19 | 20 | def test_lots_of_adjacent_duplicates(self): 21 | self.assertIterableEqual(compact([1, 1, 1, 1, 1, 1]), [1]) 22 | 23 | def test_empty_values(self): 24 | self.assertIterableEqual(compact([None, 0, "", []]), [None, 0, "", []]) 25 | 26 | def test_empty_list(self): 27 | self.assertIterableEqual(compact([]), []) 28 | 29 | # To test the Bonus part of this exercise, comment out the following line 30 | @unittest.expectedFailure 31 | def test_accepts_iterator(self): 32 | nums = (n ** 2 for n in [1, 2, 3]) 33 | self.assertIterableEqual(compact(nums), [1, 4, 9]) 34 | 35 | # To test the Bonus part of this exercise, comment out the following line 36 | @unittest.expectedFailure 37 | def test_returns_iterator(self): 38 | nums = (n ** 2 for n in [1, 2, 3]) 39 | output = compact(nums) 40 | 41 | self.assertEqual(iter(output), iter(output)) 42 | self.assertEqual(next(output), 1) 43 | 44 | # The below line tests that the incoming iterator isn't exhausted. 45 | # It may look odd to test the squares input, but this is correct 46 | # because after 1 item has been consumed from the compact 47 | # iterator, nums should only have 1 item consumed as well 48 | self.assertEqual(next(nums), 4) 49 | 50 | # A more extreme example: calling compact with an infinite iterator 51 | from itertools import count 52 | 53 | tens = compact(round(n, -1) for n in count()) 54 | self.assertEqual([next(tens) for _ in range(3)], [0, 10, 20]) 55 | 56 | 57 | if __name__ == "__main__": 58 | unittest.main() 59 | -------------------------------------------------------------------------------- /200_digging_deeper/format_ranges/README.md: -------------------------------------------------------------------------------- 1 | # format_ranges 2 | 3 | Write a function format_ranges, that takes a list of numbers and returns a string that groups ranges of consecutive numbers together: 4 | 5 | ```python 6 | >>> format_ranges([1, 2, 3, 4, 5, 6, 7, 8]) 7 | '1-8' 8 | >>> format_ranges([1, 2, 3, 5, 6, 7, 8, 10, 11]) 9 | '1-3,5-8,10-11' 10 | ``` 11 | 12 | The function should accept other iterables also not just lists: 13 | 14 | ```python 15 | >>> numbers = [3, 4, 15, 16, 17, 19, 20] 16 | >>> format_ranges(n+1 for n in numbers) 17 | '4-5,16-18,20-21' 18 | ``` 19 | 20 | All runs of consecutive numbers will be collapsed into N-M ranges where N is the start of the consecutive range and M is the end. 21 | 22 | This is sort of like the format that printers use for choosing which pages to print. 23 | 24 | At first you can assume that all consecutive ranges of numbers will be at least 2 consecutive numbers long. 25 | 26 | ## Bonus 1 27 | 28 | Update your function to handle ranges of individual numbers by representing them as a single number: 29 | 30 | ```python 31 | >>> format_ranges([4]) 32 | '4' 33 | >>> format_ranges([1, 3, 5, 6, 8]) 34 | '1,3,5-6,8' 35 | ``` 36 | 37 | ## Bonus 2 38 | 39 | Update your function so that it works even if the provided iterable of numbers is unordered: 40 | 41 | ```python 42 | >>> format_ranges([9, 1, 7, 3, 2, 6, 8]) 43 | '1-3,6-9' 44 | ``` 45 | 46 | ## Bonus 3 47 | 48 | Finally, update your function so that it handles duplicate numbers specially. Whenever a number occurs more than once, it should be considered as part of a separate range of numbers. 49 | 50 | ```python 51 | >>> format_ranges([1, 9, 1, 7, 3, 8, 2, 4, 2, 4, 7]) 52 | '1-2,1-4,4,7,7-9' 53 | >>> format_ranges([1, 3, 5, 6, 8]) 54 | '1,3,5-6,8' 55 | ``` 56 | 57 | The ranges should always be ordered by the lowest start number and then shortest range (when the start numbers are the same). 58 | 59 | ## Hints 60 | 61 | - [Identifying groups of consecutive integers](https://stackoverflow.com/a/2154741/2633215) 62 | - [Converting a list of integers into a string](https://stackoverflow.com/a/28883101/2633215) 63 | - [A one-liner for checking a condition](https://stackoverflow.com/a/394814/2633215) 64 | - [Counting the occurrences of numbers](https://stackoverflow.com/a/23241146/2633215) 65 | 66 | ## Tests 67 | 68 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 69 | 70 | Feel free to reference the tests if you have any confusion about the expected behavior. 71 | 72 | Good luck! 73 | -------------------------------------------------------------------------------- /200_digging_deeper/format_ranges/format_ranges.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable 2 | 3 | 4 | def format_ranges(numbers: Iterable[int]) -> str: 5 | """ 6 | Given an iterable of numbers, return a string that groups ranges of consecutive numbers together 7 | 8 | >>> format_ranges([1, 2, 3, 5, 6, 7, 8, 10, 11]) 9 | '1-3,5-8,10-11' 10 | >>> format_ranges(n+1 for n in numbers) 11 | '4-5,16-18,20-21' 12 | """ 13 | pass 14 | -------------------------------------------------------------------------------- /200_digging_deeper/format_ranges/test_format_ranges.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from format_ranges import format_ranges 4 | 5 | 6 | class FormatRangesTests(unittest.TestCase): 7 | 8 | """Tests for format_ranges.""" 9 | 10 | def test_one_group(self): 11 | self.assertEqual(format_ranges([1, 2, 3]), '1-3') 12 | 13 | def test_two_groups(self): 14 | self.assertEqual( 15 | format_ranges([1, 2, 10, 11, 12, 13, 14]), 16 | '1-2,10-14' 17 | ) 18 | 19 | def test_three_groups(self): 20 | self.assertEqual( 21 | format_ranges([4, 5, 16, 17, 18, 20, 21]), 22 | '4-5,16-18,20-21' 23 | ) 24 | 25 | def test_non_sequences(self): 26 | self.assertEqual( 27 | format_ranges(iter([4, 5, 16, 17, 18, 20, 21])), 28 | '4-5,16-18,20-21' 29 | ) 30 | 31 | # To test the Bonus part of this exercise, comment out the following line 32 | @unittest.expectedFailure 33 | def test_lone_numbers(self): 34 | self.assertEqual(format_ranges([1]), '1') 35 | self.assertEqual(format_ranges([4, 16, 18]), '4,16,18') 36 | self.assertEqual(format_ranges([1, 10, 11, 12, 13, 14]), '1,10-14') 37 | 38 | # To test the Bonus part of this exercise, comment out the following line 39 | @unittest.expectedFailure 40 | def test_unordered_numbers(self): 41 | self.assertEqual(format_ranges([10, 20, 12, 3, 11]), '3,10-12,20') 42 | 43 | # To test the Bonus part of this exercise, comment out the following line 44 | @unittest.expectedFailure 45 | def test_duplicate_numbers(self): 46 | self.assertEqual( 47 | format_ranges([10, 20, 10, 12, 11, 20]), 48 | '10,10-12,20,20' 49 | ) 50 | self.assertEqual( 51 | format_ranges([10, 11, 20, 10, 12, 11, 12]), 52 | '10-12,10-12,20' 53 | ) 54 | self.assertEqual( 55 | format_ranges([1, 9, 1, 7, 3, 8, 2, 4, 2, 4, 7]), 56 | '1-2,1-4,4,7,7-9' 57 | ) 58 | 59 | 60 | if __name__ == "__main__": 61 | unittest.main(verbosity=2) -------------------------------------------------------------------------------- /200_digging_deeper/js-dict/README.md: -------------------------------------------------------------------------------- 1 | # JS Dict 2 | 3 | Create a class called JSDict which creates objects that can use key lookups and attribute lookups interchangeably: 4 | 5 | ```python 6 | >>> person = JSDict({'name': "Nick Cannariato", 'location': "Fort Worth"}) 7 | >>> person.name 8 | 'Nick Cannariato' 9 | >>> person['location'] 10 | 'Fort Worth' 11 | ``` 12 | 13 | At first your object should only worry about accessing keys and attributes and accepting a single dictionary as its one optional argument. 14 | 15 | ## Bonus 1 16 | 17 | For the first bonus, make sure key and attribute assignment to works: 18 | 19 | ```python 20 | >>> person = JSDict({'name': "Nick Cannariato", 'location': "Fort Worth"}) 21 | >>> person.location = "Portland" 22 | >>> person['location'] 23 | 'Portland' 24 | >>> person['location'] = "Fort Worth" 25 | >>> person.location 26 | 'Fort Worth' 27 | ``` 28 | 29 | ## Bonus 2 30 | 31 | For the second bonus, update your JSDict class to accept keyword arguments, make JSDict objects comparable to each other via equality, and implement a get method that works sort of like the get method on dictionaries: 32 | 33 | ```python 34 | >>> person = JSDict(name="Nick Cannariato", location="Fort Worth") 35 | >>> person.location 36 | 'Fort Worth' 37 | >>> person == JSDict(name="Nick", location="Fort Worth") 38 | False 39 | >>> person == JSDict(name="Nick Cannariato", location="Fort Worth") 40 | True 41 | >>> person.get('profession') 42 | >>> person.get('profession', 'unknown') 43 | 'unknown' 44 | >>> person.get('name', 'unknown') 45 | 'Nick Cannariato' 46 | ``` 47 | 48 | Bonus 3 49 | 50 | For the third bonus, allow your JSDict class to accept a normalize keyword argument which, if true, will "normalize" the spaces in keys to underscores in attributes: 51 | 52 | ```python 53 | >>> person = JSDict(name="Nick Cannariato", location="Fort Worth", normalize=True) 54 | >>> person['company name'] = "Py Lambdas" 55 | >>> person.company_name 56 | 'Py Lambdas' 57 | >>> person['company name'] 58 | 'Py Lambdas' 59 | ``` 60 | 61 | ## Hints 62 | 63 | Hints for when you get stuck (hover over links to see what they're about): 64 | 65 | - [One way to create a dictionary-like class](https://stackoverflow.com/a/2466232/2633215) 66 | - [How [...] lookups work](https://stackoverflow.com/a/1957793/2633215) 67 | - [Customizing what attribute lookups (obj.thing) do](https://stackoverflow.com/questions/3278077/difference-between-getattr-vs-getattribute) 68 | - [Bonus 1: making `x[key] = value` work](https://gist.github.com/turicas/1510860) 69 | - [Bonus 2: keyword-only arguments](https://treyhunner.com/2018/04/keyword-arguments-in-python/#Keyword-only_arguments_without_positional_arguments) 70 | - [Bonus 2: overriding equality between objects](https://stackoverflow.com/questions/390250/elegant-ways-to-support-equivalence-equality-in-python-classes) 71 | - [Bonus 2: the get method](https://docs.python.org/3/library/stdtypes.html#dict.get) 72 | - [Bonus 3: A helper for making dictionary-like classes](https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/#UserList/UserDict:_lists_and_dictionaries_that_are_actually_extensible) 73 | 74 | ## Tests 75 | 76 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 77 | 78 | If you'd like to try the bonus challenges, you'll want to comment out the noted lines of code in the test file to test them properly. 79 | 80 | Finally, Feel free to reference the tests if you have any confusion about the expected behavior of the `JSDict` function. 81 | 82 | Good luck! 83 | -------------------------------------------------------------------------------- /200_digging_deeper/js-dict/js_dict.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /200_digging_deeper/js-dict/test_js_dict.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | from js_dict import JSDict 5 | 6 | 7 | class JSDictTests(unittest.TestCase): 8 | 9 | """Tests for JSDict.""" 10 | 11 | def test_constructor(self): 12 | JSDict() 13 | JSDict({"a": 2, "b": 3}) 14 | 15 | def test_key_access(self): 16 | d = JSDict({"a": 2, "b": 3}) 17 | self.assertEqual(d["a"], 2) 18 | self.assertEqual(d["b"], 3) 19 | 20 | def test_attribute_access(self): 21 | d = JSDict({"a": 2, "b": 3}) 22 | self.assertEqual(d.a, 2) 23 | self.assertEqual(d.b, 3) 24 | 25 | def test_original_dictionary_unchanged(self): 26 | mapping = {"a": 2, "b": 3} 27 | d = JSDict(mapping) 28 | d.c = 4 29 | self.assertEqual(mapping, {"a": 2, "b": 3}) 30 | 31 | # To test the Bonus part of this exercise, comment out the following line 32 | @unittest.expectedFailure 33 | def test_allow_setting_keys_and_attributes(self): 34 | d = JSDict({"a": 2, "b": 3}) 35 | d["a"] = 4 36 | self.assertEqual(d["a"], 4) 37 | self.assertEqual(d.a, 4) 38 | d.c = 9 39 | self.assertEqual(d["c"], 9) 40 | self.assertEqual(d.c, 9) 41 | self.assertEqual(d["b"], 3) 42 | x = JSDict() 43 | y = JSDict() 44 | x.a = 4 45 | y.a = 5 46 | self.assertEqual(x.a, 4) 47 | 48 | # To test the Bonus part of this exercise, comment out the following line 49 | @unittest.expectedFailure 50 | def test_keyword_arguments_equality_and_get_method(self): 51 | d = JSDict(a=2, b=3, c=4, d=5) 52 | self.assertEqual(d.a, 2) 53 | self.assertEqual(d.b, 3) 54 | self.assertEqual(d["c"], 4) 55 | self.assertEqual(d["d"], 5) 56 | x = JSDict({"a": 2, "b": 3}) 57 | y = JSDict({"a": 2, "b": 4}) 58 | self.assertNotEqual(x, y) 59 | y.b = 3 60 | self.assertEqual(x, y) 61 | x.c = 5 62 | self.assertNotEqual(x, y) 63 | y.c = 5 64 | self.assertEqual(x, y) 65 | self.assertIsNone(y.get("d")) 66 | self.assertEqual(y.get("c"), 5) 67 | self.assertEqual(y.get("d", 5), 5) 68 | 69 | # To test the Bonus part of this exercise, comment out the following line 70 | @unittest.expectedFailure 71 | def test_normalize_arg(self): 72 | d = JSDict({"greeting 1": "hi"}, normalize=True) 73 | self.assertEqual(d["greeting 1"], "hi") 74 | self.assertEqual(d.greeting_1, "hi") 75 | d.greeting_2 = "hello" 76 | self.assertEqual(d["greeting 2"], "hello") 77 | self.assertEqual(d.greeting_2, "hello") 78 | d["greeting 2"] = "hey" 79 | self.assertEqual(d["greeting 2"], "hey") 80 | self.assertEqual(d.get("greeting 2"), "hey") 81 | self.assertEqual(d.greeting_2, "hey") 82 | with self.assertRaises(AttributeError): 83 | d.greeting2 84 | d = JSDict({"greeting 1": "hi"}) 85 | self.assertEqual(d["greeting 1"], "hi") 86 | with self.assertRaises(AttributeError): 87 | d.greeting_1 88 | 89 | 90 | if __name__ == "__main__": 91 | unittest.main(verbosity=2) 92 | -------------------------------------------------------------------------------- /200_digging_deeper/tail/README.md: -------------------------------------------------------------------------------- 1 | # Tail 2 | 3 | Make a function that takes a [sequence](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range) of items (`items`) (like a list, string, or tuple) and a number (`n`) and returns the last n elements from the given sequence, as a list. 4 | 5 | For example: 6 | 7 | ```python 8 | >>> tail([1, 2, 3, 4, 5], 3) 9 | [3, 4, 5] 10 | >>> tail('hello', 2) 11 | ['l', 'o'] 12 | >>> tail('hello', 0) 13 | [] 14 | ``` 15 | 16 | Make sure that your function returns an empty list for negative values of n: 17 | 18 | ```python 19 | >>> tail('hello', -2) 20 | [] 21 | ``` 22 | 23 | ## Bonus 24 | 25 | As a bonus, refactor your function to work with any [iterable](https://docs.python.org/3/glossary.html#term-iterable), not just [sequences](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range). For example: 26 | 27 | ```python 28 | >>> squares = (n**2 for n in range(10)) 29 | >>> tail(squares, 3) 30 | [49, 64, 81] 31 | ``` 32 | 33 | Some particulars about this new implementation: 34 | 35 | - You should make sure you don't loop at all if n is 0 or negative. 36 | - make your function relatively memory efficient (i.e. if you're looping over a very long iterable, don't store the entire thing in memory). 37 | 38 | ## Hints 39 | 40 | - [Getting the last n items from a sequence](https://www.pythonmorsels.com/topics/slicing/) 41 | - [Turning iterables into lists](https://treyhunner.com/2019/05/python-builtins-worth-learning/#list) 42 | - [How to loop over any iterable in Python](https://treyhunner.com/2019/06/loop-better-a-deeper-look-at-iteration-in-python/#Generators_are_iterators) 43 | - [A data structure that could help with the bonus task](https://pymotw.com/3/collections/deque.html#constraining-the-queue-size) 44 | 45 | ## Tests 46 | 47 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 48 | 49 | Feel free to reference the tests if you have any confusion about the expected behavior. 50 | 51 | Good luck! 52 | -------------------------------------------------------------------------------- /200_digging_deeper/tail/tail.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | def tail(items, n): 5 | # Write your solution bellow 6 | pass 7 | -------------------------------------------------------------------------------- /200_digging_deeper/tail/test_tail.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | 4 | from tail import tail 5 | 6 | 7 | class TailTests(unittest.TestCase): 8 | def test_zero(self): 9 | """ 10 | Return an empty list when `n` is 0 11 | """ 12 | self.assertEqual(tail([1, 2], 0), []) 13 | 14 | def test_one(self): 15 | """ 16 | Return the last item in the iterable when `n` is 1 17 | """ 18 | self.assertEqual(tail([1, 2], 1), [2]) 19 | 20 | def test_two(self): 21 | """ 22 | Return the last two items in the iterable when `n` is 2 23 | """ 24 | self.assertEqual(tail([1, 2], 2), [1, 2]) 25 | 26 | def test_n_larger_than_iterable_length(self): 27 | """ 28 | Return an empty list when `n` is greater than the number of items in 29 | the iterable 30 | """ 31 | nums = [1, 2, 3, 4] 32 | self.assertEqual(tail(nums, 5), [1, 2, 3, 4]) 33 | self.assertEqual(tail([], 10), []) 34 | 35 | def test_string(self): 36 | """ 37 | Return a list containing the last `n` characters when the iterable 38 | is a string 39 | """ 40 | self.assertEqual(tail("hello", 2), ["l", "o"]) 41 | 42 | def test_tuple(self): 43 | """ 44 | Return a list containing the last `n` items when the iterable is a 45 | tuple 46 | """ 47 | self.assertEqual(tail((1, 2, 3), 3), [1, 2, 3]) 48 | 49 | def test_negative_n(self): 50 | """ 51 | Return an empty list when `n` is negative 52 | """ 53 | nums = [1, 2, 3, 4] 54 | self.assertEqual(tail(nums, -1), []) 55 | self.assertEqual(tail((), -9), []) 56 | 57 | # To test the Bonus part of this exercise, comment out the following line 58 | @unittest.expectedFailure 59 | def test_iterator(self): 60 | """ 61 | Return a list containing the last `n` items in an iterable of any kind, 62 | not just a sequence type (i.e. a generator and not just a string) 63 | """ 64 | nums = (n ** 2 for n in [1, 2, 3, 4]) 65 | self.assertEqual(tail(nums, -1), []) # Don't loop for negative n 66 | self.assertEqual(tail(nums, 0), []) # Don't loop for n=0 67 | self.assertEqual(tail(nums, 2), [9, 16]) # Consuming the generator 68 | self.assertEqual(list(nums), []) # The nums generator is now empty 69 | self.assertEqual(tail(nums, 1), []) # n=1 with a now empty generator 70 | 71 | 72 | if __name__ == "__main__": 73 | unittest.main(verbosity=2) 74 | -------------------------------------------------------------------------------- /300_peculiarities/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-Lambdas/code-challenges/0a60585b93aca3ee37457e1cec21169808a07625/300_peculiarities/.gitkeep -------------------------------------------------------------------------------- /400_paradigm_pathways/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-Lambdas/code-challenges/0a60585b93aca3ee37457e1cec21169808a07625/400_paradigm_pathways/.gitkeep -------------------------------------------------------------------------------- /500_the_standard_library/fix_csv/README.md: -------------------------------------------------------------------------------- 1 | # Fix CSVs 2 | 3 | You're going to write a basic command line program to normalize CSV files, named `fix_csv.py`. The CLI will turn a pipe-delimited file into a comma-delimited file. You'll see how that will work below. 4 | 5 | An original, pipe-delimited file would look something like this: 6 | 7 | ```csv 8 | Reading|Make|Model|Type|Value 9 | Reading 0|Toyota|Previa|distance|19.83942 10 | Reading 1|Dodge|Intrepid|distance|31.28257 11 | ``` 12 | 13 | And you would run that file through your command-line program typing this in your terminal: 14 | 15 | ```shell 16 | python fix_csv.py cars-original.csv cars.csv 17 | ``` 18 | 19 | As you can see above, `fix_csv.py` takes the path of the pipe-delimited file as its first argument, and the path to the location you'd like your fixed file to be created as the second argument. 20 | 21 | Your fixed file should then look like this: 22 | 23 | ```csv 24 | Reading,Make,Model,Type,Value 25 | Reading 0,Toyota,Previa,distance,19.83942 26 | Reading 1,Dodge,Intrepid,distance,31.28257 27 | ``` 28 | 29 | Some important things to note: 30 | - It's valid for a comma to be in your input data, but you'll need to surround data cells with commas in them by double quotes when writing your output file. 31 | - It's also valid for a quote character to be in your input (you'll need to double up quotes because [that's how CSV escaping works](https://stackoverflow.com/questions/17808511/properly-escape-a-double-quote-in-csv). 32 | 33 | See the hints if you need help working with CSV files in Python. 34 | 35 | ## Bonus 1 36 | 37 | Now that you've passed MVP, refactor your program to allow the input delimiter and quote character (`"`) by default) to be optionally specified. 38 | 39 | For example any of these should work (all specify input delimiter as pipe and the last two additionally specifies the quote character as single quote): 40 | 41 | ```shell 42 | $ python fix_csv.py --in-delimiter="|" cars.csv cars-fixed.csv 43 | $ python fix_csv.py cars.csv cars-fixed.csv --in-delimiter="|" 44 | $ python fix_csv.py --in-delimiter="|" --in-quote="'" cars.csv cars-fixed.csv 45 | $ python fix_csv.py --in-quote="|" --in-delimiter="," cars.csv cars-fixed.csv 46 | ``` 47 | 48 | To do this, you'll need to look into parsing command-line arguments with Python. There are some standard library modules that can help you out with this. There are 3 different solutions in the standard library actually, but only one I'd recommend. 49 | 50 | Once again, avoid `pip install`-ing anything to complete this challenge. These challenges are meant to increase your understanding of Python itself. 51 | 52 | Be sure to really read the docs on [Python's CSV module](https://docs.python.org/3/library/csv.html), or you might end up re-implementing a lot of functionality that Python gives you for free. 53 | 54 | ## Bonus 2 55 | 56 | Try to automatically detect the delimiter if an in-delimiter value isn't supplied (don't assume it's pipe and quote, try to get your program to intelligently figure it out). 57 | 58 | This is a bit trickier and your solution will likely not work correctly for all files. Definitely check the hints on this one if you get stuck. 59 | 60 | ## Hints 61 | 62 | - [Using `sys.argv` to access command line arguments](https://stackoverflow.com/a/35421024/2633215) 63 | - [Example usage of the `csv` module in the standard library](https://pymotw.com/3/csv/index.html) 64 | - [Corey Shafer's short `csv` module tutorial](https://www.youtube.com/watch?v=q5uM4VKywbA) 65 | - [Restricting the number of command line arguments with multiple assignment](https://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/#Multiple_assignment_is_very_strict) 66 | - [Using `argparse` to parse command line arguments and flags](http://zetcode.com/python/argparse/) 67 | - [This could be useful to intelligently figure out how to parse a csv file...](https://docs.python.org/3/library/csv.html#csv.Sniffer) 68 | 69 | ## Tests 70 | 71 | Move your terminal to the directory that contains this README and then run `python -m unittest` to run the tests for this challenge. 72 | 73 | You'll notice that there are no CSV files in this directory, and that's on purpose. If you examine the tests, you'll see that I'm generating files when you run the tests and then cleaning them up afterwards. 74 | 75 | You're welcome to create your own test CSV files if you want to run it yourself with your own data, but the test file itself should be enough to run your solution through its paces. 76 | 77 | Finally, if you'd like to try the bonus challenges, you'll want to comment out the noted lines of code in the test file to test them properly. 78 | 79 | Good luck! 80 | -------------------------------------------------------------------------------- /500_the_standard_library/fix_csv/fix_csv.py: -------------------------------------------------------------------------------- 1 | # **Write your solution below** 2 | -------------------------------------------------------------------------------- /500_the_standard_library/fix_csv/test_fix_csv.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager, redirect_stderr, redirect_stdout 2 | from io import StringIO 3 | from importlib.machinery import SourceFileLoader 4 | import os 5 | import sys 6 | import warnings 7 | import shlex 8 | from textwrap import dedent 9 | from tempfile import NamedTemporaryFile 10 | import unittest 11 | 12 | 13 | class FixCSVTests(unittest.TestCase): 14 | maxDiff = None 15 | 16 | def test_pipe_file_to_csv_file(self): 17 | old_contents = dedent( 18 | """ 19 | 2012|Lexus|LFA 20 | 2009|GMC|Yukon XL 1500 21 | 1965|Ford|Mustang 22 | 2005|Hyundai|Sonata 23 | 1995|Mercedes-Benz|C-Class 24 | """ 25 | ).lstrip() 26 | 27 | expected = dedent( 28 | """ 29 | 2012,Lexus,LFA 30 | 2009,GMC,Yukon XL 1500 31 | 1965,Ford,Mustang 32 | 2005,Hyundai,Sonata 33 | 1995,Mercedes-Benz,C-Class 34 | """ 35 | ).lstrip() 36 | 37 | with make_file(old_contents) as old, make_file("") as new: 38 | output = run_program(f"fix_csv.py {old} {new}") 39 | with open(new) as new_file: 40 | new_contents = new_file.read() 41 | 42 | self.assertEqual(expected, new_contents) 43 | self.assertEqual("", output) 44 | 45 | def test_delimiter_in_output(self): 46 | old_contents = dedent( 47 | """ 48 | 02|Waylon Jennings|Honky Tonk Heroes (Like Me) 49 | 04|Kris Kristofferson|To Beat The Devil 50 | 11|Johnny Cash|Folsom Prison Blues 51 | 13|Billy Joe Shaver|Low Down Freedom 52 | 21|Hank Williams III|Mississippi Mud 53 | 22|David Allan Coe|Willie, Waylon, And Me 54 | 24|Bob Dylan|House Of The Risin' Sun 55 | """ 56 | ).lstrip() 57 | 58 | expected = dedent( 59 | """ 60 | 02,Waylon Jennings,Honky Tonk Heroes (Like Me) 61 | 04,Kris Kristofferson,To Beat The Devil 62 | 11,Johnny Cash,Folsom Prison Blues 63 | 13,Billy Joe Shaver,Low Down Freedom 64 | 21,Hank Williams III,Mississippi Mud 65 | 22,David Allan Coe,"Willie, Waylon, And Me" 66 | 24,Bob Dylan,House Of The Risin' Sun 67 | """ 68 | ).lstrip() 69 | 70 | with make_file(old_contents) as old, make_file("") as new: 71 | output = run_program(f"fix_csv.py {old} {new}") 72 | with open(new) as new_file: 73 | new_contents = new_file.read() 74 | 75 | self.assertEqual(expected, new_contents) 76 | self.assertEqual("", output) 77 | 78 | def test_original_file_is_unchanged(self): 79 | old_contents = dedent( 80 | """ 81 | 2012|Lexus|LFA 82 | 2009|GMC|Yukon XL 1500 83 | """ 84 | ).lstrip() 85 | 86 | with make_file(old_contents) as old, make_file("") as new: 87 | run_program(f"fix_csv.py {old} {new}") 88 | with open(old) as old_file: 89 | contents = old_file.read() 90 | 91 | self.assertEqual(old_contents, contents) 92 | 93 | def test_call_with_too_many_files(self): 94 | with make_file("") as old, make_file("") as new: 95 | with self.assertRaises(BaseException): 96 | run_program(f"fix_csv.py {old} {new} {old}") 97 | 98 | # To test the Bonus part of this exercise, comment out the following line 99 | @unittest.expectedFailure 100 | def test_in_delimiter_and_in_quote(self): 101 | old_contents = dedent( 102 | """ 103 | 2012 Lexus "LFA" 104 | 2009 GMC 'Yukon XL 1500' 105 | 1995 "Mercedes-Benz" C-Class 106 | """ 107 | ).lstrip() 108 | 109 | expected1 = dedent( 110 | """ 111 | 2012,Lexus,LFA 112 | 2009,GMC,'Yukon,XL,1500' 113 | 1995,Mercedes-Benz,C-Class 114 | """ 115 | ).lstrip() 116 | 117 | expected2 = dedent( 118 | ''' 119 | 2012,Lexus,"""LFA""" 120 | 2009,GMC,Yukon XL 1500 121 | 1995,"""Mercedes-Benz""",C-Class 122 | ''' 123 | ).lstrip() 124 | 125 | with make_file(old_contents) as old, make_file("") as new: 126 | run_program(f'fix_csv.py {old} {new} --in-delimiter=" "') 127 | with open(new) as new_file: 128 | self.assertEqual(expected1, new_file.read()) 129 | 130 | run_program(f"""fix_csv.py --in-delimiter=" " --in-quote="'" {old} {new}""") 131 | with open(new) as new_file: 132 | self.assertEqual(expected2, new_file.read()) 133 | 134 | # To test the Bonus part of this exercise, comment out the following line 135 | @unittest.expectedFailure 136 | def test_autodetect_input_format(self): 137 | contents1 = dedent( 138 | """ 139 | '2012' 'Lexus' 'LFA' 140 | '2009' 'GMC' 'Yukon XL 1500' 141 | '1995' 'Mercedes-Benz' 'C-Class' 142 | """ 143 | ).lstrip() 144 | 145 | expected1 = dedent( 146 | """ 147 | 2012,Lexus,LFA 148 | 2009,GMC,Yukon XL 1500 149 | 1995,Mercedes-Benz,C-Class 150 | """ 151 | ).lstrip() 152 | 153 | with make_file(contents1) as old, make_file("") as new: 154 | run_program(f"fix_csv.py {old} {new}") 155 | with open(new) as new_file: 156 | self.assertEqual(expected1, new_file.read()) 157 | 158 | contents2 = dedent( 159 | """ 160 | "02"\t"Waylon Jennings"\t"Honky Tonk Heroes (Like Me)"\t"3:29" 161 | "04"\t"Kris Kristofferson"\t"To Beat The Devil"\t"4:05" 162 | "11"\t"Johnny Cash"\t"Folsom Prison Blues"\t"2:51" 163 | "13"\t"Billy Joe Shaver"\t"Low Down Freedom"\t"2:53" 164 | "21"\t"Hank Williams III"\t"Mississippi Mud"\t"3:32" 165 | "22"\t"David Allan Coe"\t"Willie, Waylon, And Me"\t"3:24" 166 | "24"\t"Bob Dylan"\t"House Of The Risin' Sun"\t"5:20" 167 | """ 168 | ).lstrip() 169 | 170 | expected2 = dedent( 171 | """ 172 | 02,Waylon Jennings,Honky Tonk Heroes (Like Me),3:29 173 | 04,Kris Kristofferson,To Beat The Devil,4:05 174 | 11,Johnny Cash,Folsom Prison Blues,2:51 175 | 13,Billy Joe Shaver,Low Down Freedom,2:53 176 | 21,Hank Williams III,Mississippi Mud,3:32 177 | 22,David Allan Coe,"Willie, Waylon, And Me",3:24 178 | 24,Bob Dylan,House Of The Risin' Sun,5:20 179 | """ 180 | ).lstrip() 181 | 182 | with make_file(contents2) as old, make_file("") as new: 183 | run_program(f"fix_csv.py {old} {new}") 184 | with open(new) as new_file: 185 | self.assertEqual(expected2, new_file.read()) 186 | 187 | 188 | class DummyException(Exception): 189 | """No code will ever raise this exception.""" 190 | 191 | 192 | def run_program(arguments="", raises=DummyException): 193 | """ 194 | Run program at given path with given arguments. 195 | 196 | If raises is specified, ensure the given exception is raised. 197 | """ 198 | arguments = arguments.replace("\\", "\\\\") 199 | path, *args = shlex.split(arguments) 200 | old_args = sys.argv 201 | warnings.simplefilter("ignore", ResourceWarning) 202 | 203 | try: 204 | sys.argv = [path] + args 205 | 206 | try: 207 | if "__main__" in sys.modules: 208 | del sys.modules["__main__"] 209 | 210 | with redirect_stdout(StringIO()) as output: 211 | with redirect_stderr(output): 212 | SourceFileLoader("__main__", path).load_module() 213 | except raises: 214 | return output.getvalue() 215 | except SystemExit as e: 216 | if e.args != (0,): 217 | raise SystemExit(output.getvalue()) from e 218 | finally: 219 | sys.modules.pop("__main__", None) 220 | 221 | if raises is not DummyException: 222 | raise AssertionError("{} not raised".format(raises)) 223 | 224 | return output.getvalue() 225 | finally: 226 | sys.argv = old_args 227 | 228 | 229 | @contextmanager 230 | def make_file(contents=None): 231 | """Context manager providing name of a file containing given contents.""" 232 | with NamedTemporaryFile(mode="wt", encoding="utf-8", delete=False) as f: 233 | if contents: 234 | f.write(contents) 235 | try: 236 | yield f.name 237 | finally: 238 | os.remove(f.name) 239 | 240 | 241 | if __name__ == "__main__": 242 | unittest.main(verbosity=2) 243 | -------------------------------------------------------------------------------- /bin/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import platform 4 | import subprocess as sub 5 | import sys 6 | import venv 7 | import warnings 8 | from pathlib import Path 9 | from shutil import which 10 | from textwrap import dedent 11 | from typing import List 12 | 13 | 14 | class colors: 15 | """ 16 | Terminal color ENUM for use in printed strings 17 | """ 18 | 19 | HEADER = "\033[95m" 20 | BLUE = "\033[94m" 21 | CYAN = "\033[96m" 22 | GREEN = "\033[92m" 23 | WARNING = "\033[93m" 24 | FAIL = "\033[91m" 25 | END = "\033[0m" 26 | BOLD = "\033[1m" 27 | UNDERLINE = "\033[4m" 28 | 29 | 30 | def title(msg: str, color: str = colors.CYAN): 31 | """ 32 | Print a pretty title styled string for the terminal 33 | """ 34 | decorator = "*" * (len(msg) + len(msg) // 2) 35 | print( 36 | dedent( 37 | f""" 38 | {decorator} 39 | * 40 | * {colors.BOLD}{color}{msg}{colors.END} 41 | * 42 | {decorator} 43 | """ 44 | ) 45 | ) 46 | 47 | 48 | def check_user_path(): 49 | """ 50 | Verifies that a user is in the correct path 51 | """ 52 | if Path.cwd().name != "code-challenges": 53 | warnings.warn( 54 | f"{colors.WARNING}Setup script must be called from the root of the " 55 | f"code-challenges repository{colors.END}" 56 | ) 57 | sys.exit(1) 58 | 59 | 60 | def run_command(command: List[str]): 61 | """ 62 | Attempt to run a specific shell command in the users's shell 63 | """ 64 | print(f"{' '.join(str(part) for part in command)}") 65 | try: 66 | sub.run( 67 | command, 68 | check=True, 69 | encoding="utf-8", 70 | ) 71 | except sub.CalledProcessError: 72 | warnings.warn( 73 | f"{colors.FAIL}" 74 | f"Problem encountered when running `{' '.join(command)}`\n\n" 75 | f"Review the output above to manually debug the issue{colors.END}" 76 | ) 77 | sys.exit(1) 78 | 79 | 80 | def create_virtual_environment(): 81 | """ 82 | Generates a virtual environment for the user, including special packages 83 | """ 84 | VENV_PATH = Path.cwd() / ".venv" 85 | PIP_CMD = [ 86 | "-m", 87 | "pip", 88 | "install", 89 | "-U", 90 | "pip", 91 | "setuptools", 92 | "black", 93 | "flake8", 94 | "isort", 95 | "mypy", 96 | ] 97 | WIN_CMD = [VENV_PATH / "Scripts" / "python.exe", *PIP_CMD] 98 | NIX_CMD = [VENV_PATH / "bin" / "python3", *PIP_CMD] 99 | 100 | if not VENV_PATH.exists(): 101 | print( 102 | f"{colors.GREEN}Generating virtual environment in " 103 | f"{VENV_PATH.parent}/{VENV_PATH.name}{colors.END}\n" 104 | ) 105 | venv.create(VENV_PATH, with_pip=True) 106 | else: 107 | print( 108 | f"{colors.GREEN}{VENV_PATH.parent}{VENV_PATH.name} exists! " 109 | f"Installing dependencies{colors.END}\n" 110 | ) 111 | 112 | print(f"{colors.GREEN}Upgrading pip and installing dependencies{colors.END}\n") 113 | run_command(WIN_CMD if platform.system().lower() == "windows" else NIX_CMD) 114 | 115 | 116 | def install_vscode_extensions(): 117 | """ 118 | Install minimum required VS Code extensions to allow settings.json to work as 119 | expected 120 | """ 121 | run_command(["code", "--install-extension", "ms-python.python", "--force"]) 122 | run_command(["code", "--install-extension", "ms-python.vscode-pylance", "--force"]) 123 | 124 | 125 | def main(): 126 | check_user_path() 127 | 128 | title("Configuring Python virtual environment") 129 | create_virtual_environment() 130 | 131 | if which("code"): 132 | title("Installing VS Code extensions") 133 | install_vscode_extensions() 134 | 135 | 136 | if __name__ == "__main__": 137 | main() 138 | -------------------------------------------------------------------------------- /misc/alphabet_soup/alphabet_soup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Challenge: Create a function that takes a string and returns a string with its letters in alphabetical order. 3 | Difficulty: Easy 4 | 5 | Examples: 6 | alphabet_soup("hello") ➞ "ehllo" 7 | alphabet_soup("edabit") ➞ "abdeit" 8 | alphabet_soup("hacker") ➞ "acehkr" 9 | alphabet_soup("geek") ➞ "eegk" 10 | 11 | Author: @joshrutkowski 12 | """ 13 | 14 | 15 | def alphabet_soup(text): 16 | pass # Your code here 17 | -------------------------------------------------------------------------------- /misc/alphabet_soup/test_alphabet_soup.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from alphabet_soup import * 3 | 4 | 5 | def test(): 6 | assert alphabet_soup("hello") == "ehllo" 7 | assert alphabet_soup("edabit") == "abdeit" 8 | assert alphabet_soup("hacker") == "acehkr" 9 | assert alphabet_soup("geek") == "eegk" 10 | assert alphabet_soup("javascript") == "aacijprstv" 11 | assert alphabet_soup("credibility") == "bcdeiiilrty" 12 | assert alphabet_soup("apostrophe") == "aehoopprst" 13 | assert alphabet_soup("determination") == "adeeiimnnortt" 14 | assert alphabet_soup("win") == "inw" 15 | assert alphabet_soup("synthesis") == "ehinsssty" 16 | 17 | -------------------------------------------------------------------------------- /misc/capitalization_and_mutability/capitalization_and_mutability.py: -------------------------------------------------------------------------------- 1 | """ 2 | Challenge: Correct a function that takes a string and returns the first character capitalized in the string. 3 | Difficulty: Easy 4 | 5 | Examples: 6 | capitalize_word("hello") ➞ "Hello" 7 | capitalize_word("i") ➞ "I" 8 | capitalize_word("lambda") ➞ "Lambda" 9 | 10 | Author: @joshrutkowski 11 | """ 12 | 13 | 14 | def capitalize_string(word): 15 | return "".join(char.isupper() for char in word) 16 | 17 | -------------------------------------------------------------------------------- /misc/capitalization_and_mutability/test_capitalization_and_mutability.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from capitalization_and_mutability import * 3 | 4 | 5 | def test_true(): 6 | assert capitalize_string("hello") == "Hello" 7 | assert capitalize_string("python") == "Python" 8 | assert capitalize_string("capital") == "Capital" 9 | 10 | 11 | def test_false(): 12 | assert capitalize_string("hello") != "hello" 13 | assert capitalize_string("python") != "python" 14 | assert capitalize_string("capital") != "capital" 15 | -------------------------------------------------------------------------------- /misc/convert_mins_to_seconds/convert_minutes_to_seconds.py: -------------------------------------------------------------------------------- 1 | """ 2 | Challenge: Write a function that takes an integer minutes and converts it to seconds. 3 | Difficulty: Very Easy 4 | 5 | Examples 6 | convert(5) ➞ 300 7 | convert(3) ➞ 180 8 | convert(2) ➞ 120 9 | 10 | Author: @joshrutkowski 11 | """ 12 | 13 | 14 | def convert(minutes): 15 | pass # Your code here 16 | -------------------------------------------------------------------------------- /misc/convert_mins_to_seconds/test_convert_minutes_to_seconds.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from convert_minutes_to_seconds import * 3 | 4 | 5 | class ConvertMinutesToSeconds(unittest.TestCase): 6 | def test(self): 7 | self.assertEqual(convert(6), 360) 8 | self.assertEqual(convert(4), 240) 9 | self.assertEqual(convert(8), 480) 10 | self.assertEqual(convert(60), 3600) 11 | 12 | 13 | if __name__ == "__main__": 14 | unittest.main() 15 | -------------------------------------------------------------------------------- /misc/perimeter_of_rectangle/find_perimeter_of_rectangle.py: -------------------------------------------------------------------------------- 1 | """ 2 | Challenge: Create a function that takes length and width and finds the perimeter of a rectangle. 3 | Difficulty: Easy 4 | 5 | Examples: 6 | find_perimeter(6, 7) ➞ 26 7 | find_perimeter(20, 10) ➞ 60 8 | find_perimeter(2, 9) ➞ 22 9 | 10 | Author: @joshrutkowski 11 | """ 12 | 13 | 14 | def find_perimeter(length, width): 15 | pass # Your code here 16 | -------------------------------------------------------------------------------- /misc/perimeter_of_rectangle/test_find_perimeter_of_rectangle.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from find_perimeter_of_rectangle import * 3 | 4 | 5 | class PerimeterOfRectangle(unittest.TestCase): 6 | def test_find_perimeter(self): 7 | self.assertEqual(find_perimeter(6, 7), 26) 8 | self.assertEqual(find_perimeter(20, 10), 60) 9 | self.assertEqual(find_perimeter(2, 9), 22) 10 | 11 | 12 | if __name__ == "__main__": 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /misc/return_next_number/return_next_number.py: -------------------------------------------------------------------------------- 1 | """ 2 | Challenge: Create a function that takes a number as an argument, increments the number by +1 and returns the result. 3 | Difficulty: Very Easy 4 | 5 | Examples 6 | addition(0) ➞ 1 7 | addition(9) ➞ 10 8 | addition(-3) ➞ -2 9 | 10 | Author: @joshrutkowski 11 | """ 12 | 13 | 14 | def addition(num): 15 | pass # Your code here 16 | -------------------------------------------------------------------------------- /misc/return_next_number/test_return_next_number.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from return_next_number import * 3 | 4 | 5 | class ReturnNextNumber(unittest.TestCase): 6 | def test(self): 7 | self.assertEqual(addition(2), 3, "2 plus 1 equals 3.") 8 | self.assertEqual(addition(-9), -8, "-9 plus 1 equals -8.") 9 | self.assertEqual(addition(999), 1000, "999 plus 1 equals 1000.") 10 | self.assertEqual(addition(73), 74, "73 plus 1 equals 74.") 11 | 12 | if __name__ == '__main__': 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /misc/return_sum_of_two_numbers/return_sum_of_two_numbers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Challenge: Create a function that takes two numbers as arguments and return their sum. 3 | Difficulty: Very Easy 4 | 5 | Examples: 6 | addition(3, 2) ➞ 5 7 | addition(-3, -6) ➞ -9 8 | addition(7, 3) ➞ 10 9 | 10 | Author: @joshrutkowski 11 | """ 12 | 13 | 14 | def addition(a, b): 15 | pass # Your code here 16 | -------------------------------------------------------------------------------- /misc/return_sum_of_two_numbers/test_return_sum_of_two_numbers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from return_sum_of_two_numbers import * 3 | 4 | 5 | class ReturnSumTwoNumbersTests(unittest.TestCase): 6 | def test(self): 7 | self.assertEqual(addition(2, 3), 5) 8 | self.assertEqual(addition(-3, -6), -9) 9 | self.assertEqual(addition(7, 3), 10) 10 | 11 | 12 | if __name__ == "__main__": 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /misc/topological_sort/__pycache__/test_topological_sort.cpython-38-pytest-5.4.3.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-Lambdas/code-challenges/0a60585b93aca3ee37457e1cec21169808a07625/misc/topological_sort/__pycache__/test_topological_sort.cpython-38-pytest-5.4.3.pyc -------------------------------------------------------------------------------- /misc/topological_sort/__pycache__/topological_sort.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Py-Lambdas/code-challenges/0a60585b93aca3ee37457e1cec21169808a07625/misc/topological_sort/__pycache__/topological_sort.cpython-38.pyc -------------------------------------------------------------------------------- /misc/topological_sort/test_topological_sort.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from topological_sort import * 3 | 4 | 5 | def test_valid_one(): 6 | tasks = [4, 3, 2, 1] 7 | deps = [ 8 | [2, 3], 9 | [2, 4], 10 | [4, 3], 11 | [1, 3], 12 | [1, 4], 13 | ] 14 | ordered = topological_sort(tasks, deps) 15 | assert is_topol_ordered(ordered, tasks, deps) == True 16 | 17 | 18 | def test_valid_two(): 19 | tasks = [1, 2, 3, 4, 5] 20 | deps = [[1, 3], [2, 3], [3, 4], [2, 5], [5, 4]] 21 | ordered = topological_sort(tasks, deps) 22 | assert is_topol_ordered(ordered, tasks, deps) == True 23 | 24 | 25 | def test_valid_three(): 26 | tasks = [1, 2, 3, 4, 5, 6, 7, 8] 27 | deps = [[3, 1], [8, 1], [8, 7], [5, 7], [ 28 | 5, 2], [1, 4], [1, 6], [1, 2], [7, 6]] 29 | ordered = topological_sort(tasks, deps) 30 | assert is_topol_ordered(ordered, tasks, deps) == True 31 | 32 | 33 | def test_invalid(): 34 | tasks = [1, 2, 3, 4, 5, 6, 7, 8] 35 | deps = [[4, 2], [1, 2], [1, 8], [6, 8], [ 36 | 6, 3], [2, 5], [7, 8], [2, 3], [8, 7]] 37 | # No valid order 38 | assert len(topological_sort(tasks, deps)) == 0 39 | 40 | 41 | def is_topol_ordered(ordered, tasks, deps): 42 | deps_graph = {} 43 | if len(ordered) != len(tasks): 44 | return False 45 | oSet = set() 46 | tSet = set() 47 | for i in range(len(tasks)): 48 | oSet.add(ordered[i]) 49 | tSet.add(tasks[i]) 50 | 51 | if len(oSet.difference(tSet)) != 0: 52 | return False 53 | 54 | for d in deps: 55 | if d[1] in deps_graph: 56 | deps_graph[d[1]].append(d[0]) 57 | else: 58 | deps_graph[d[1]] = [d[0]] 59 | 60 | for t in ordered: 61 | if t in deps_graph and len(deps_graph[t]): 62 | return False 63 | for d in deps_graph.values(): 64 | if t in d: 65 | d.remove(t) 66 | return True 67 | -------------------------------------------------------------------------------- /misc/topological_sort/topological_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | Topological Sort 3 | Difficulty: Hard 4 | Given a list of tasks and a list of dependencies return 5 | a list of tasks in valid order. If no such order exists 6 | return an empty array. 7 | 8 | tasks = [1,2,3,4] 9 | deps = [ 10 | [2, 3], 11 | [2, 4], 12 | [4, 3], 13 | [1, 3], 14 | [1, 4], 15 | ] 16 | 17 | topological_sort(tasks, deps) = [1,2,3,4] or [2,1,3,4] 18 | 19 | Author: BrannanC 20 | """ 21 | 22 | 23 | def topological_sort(tasks, deps): 24 | # Write your code here 25 | pass 26 | --------------------------------------------------------------------------------