├── .editorconfig ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── algorithms ├── README.md ├── searching │ ├── binary_search.py │ └── linear_search.py └── sorting │ ├── bubble_sort.py │ ├── insertion_sort.py │ ├── merge_sort.py │ ├── quick_sort.py │ └── selection_sort.py ├── data_structures ├── README.md ├── binary_tree.py ├── doubly_linked_list.py ├── linked_list.py ├── queue.py └── stack.py ├── design_patterns ├── README.md ├── adapter.py ├── builder.py ├── command.py ├── decorator.py ├── factory.py └── singleton.py ├── notes ├── c01_basics │ ├── Chapter-1.1-Basics.md │ ├── README.md │ ├── code │ │ ├── c0101_hello_world.py │ │ └── c0102_comments.py │ └── quiz │ │ ├── README.md │ │ └── solution │ │ ├── q0101.py │ │ └── q0102.py ├── c02_basic_data_types │ ├── Chapter 2.1 Variables.md │ ├── Chapter 2.2 Numeric Data Types.md │ ├── Chapter 2.3 Strings.md │ ├── Chapter 2.4 string formatting.md │ ├── Chapter 2.5 Operations.md │ ├── Chapter 2.6 Typecasting.md │ ├── README.md │ ├── code │ │ ├── c0201_variables.py │ │ ├── c0202_basic_data_types.py │ │ ├── c0203_strings.py │ │ ├── c0204_string_formatting.py │ │ ├── c0205_arithmetic.py │ │ ├── c0206_relational.py │ │ ├── c0207_logical.py │ │ ├── c0208_identity.py │ │ ├── c0209_membership.py │ │ ├── c0210_bitwise.py │ │ ├── c0211_assignment.py │ │ └── c0212_typecasting.py │ └── quiz │ │ ├── README.md │ │ └── solution │ │ ├── q0201.py │ │ ├── q0202.py │ │ ├── q0203.py │ │ ├── q0204.py │ │ └── q0205.py ├── c03_advanced_data_types │ ├── README.md │ ├── chapter 3.1 list.md │ ├── chapter 3.2 tuple.md │ ├── chapter 3.3 dictionary.md │ ├── chapter 3.4 set.md │ ├── chapter 3.5 nesting.md │ ├── code │ │ ├── c0301_lists.py │ │ ├── c0302_tuples.py │ │ ├── c0303_dictionaries.py │ │ ├── c0304_sets.py │ │ └── c0305_type_hinting.py │ └── quiz │ │ ├── README.md │ │ └── solution │ │ ├── q0301.py │ │ ├── q0302.py │ │ └── q0303.py ├── c04_decision_making │ ├── README.md │ ├── code │ │ ├── c0401_if.py │ │ └── c0402_match.py │ └── quiz │ │ └── README.md ├── c05_loops │ ├── Chapter 5.1 while loop.md │ ├── Chapter 5.2 for loop.md │ ├── Chapter 5.3 Pattern Generation.md │ ├── Chapter 5.4 Comprehensions.md │ ├── README.md │ ├── code │ │ ├── c0501_while.py │ │ ├── c0502_for.py │ │ ├── c0503_nested_loops.py │ │ ├── c0504_pattern_generation.py │ │ └── c0505_list_comprehension.py │ └── quiz │ │ └── README.md ├── c06_functions │ ├── Chapter 6.1 function.md │ ├── Chapter 6.2 default arguments.md │ ├── Chapter 6.3 args kwargs.md │ ├── Chapter 6.4 recursive functions.md │ ├── Chapter 6.5 lambda.md │ ├── README.md │ ├── code │ │ ├── c0601_function_basics.py │ │ ├── c0602_default_parameters.py │ │ ├── c0603_args_kwargs.py │ │ ├── c0604_recursive_function.py │ │ └── c0605_lambda.py │ └── quiz │ │ └── README.md ├── c07_oop │ ├── Chapter-7.1-oop.md │ ├── Chapter-7.2-Class-Methods-and-Static-Methods.md │ ├── Chapter-7.3-Operator-Overloading.md │ ├── Chapter-7.4-Encapsulation.md │ ├── Chapter-7.5-Inheritance-and-Polymorphism.md │ ├── README.md │ ├── code │ │ ├── c0701_oop_intro.py │ │ ├── c0702_class.py │ │ ├── c0703_classmethod_staticmethod.py │ │ ├── c0704_encapsulation.py │ │ ├── c0705_property.py │ │ ├── c0706_operator_overloading.py │ │ ├── c0707_inheritance.py │ │ ├── c0708_multiple_inheritance.py │ │ └── c0709_monkey_patching.py │ ├── quiz │ │ ├── README.md │ │ ├── q0701.py │ │ ├── q0702.py │ │ ├── q0703.py │ │ ├── q0704.py │ │ ├── q0705.py │ │ ├── q0706.py │ │ ├── q0707.py │ │ └── q0708.py │ └── quiz_solution │ │ ├── q0701.py │ │ ├── q0702.py │ │ ├── q0703.py │ │ ├── q0704.py │ │ ├── q0705.py │ │ ├── q0706.py │ │ ├── q0707.py │ │ └── q0708.py ├── c08_modules_packages │ ├── README.md │ ├── chapter-8.1-modules.md │ ├── chapter-8.2-packages.md │ ├── chapter-8.3-datetime.md │ ├── chapter-8.4-random.md │ ├── chapter-8.5-json.md │ ├── chapter-8.6-math.md │ ├── chapter-8.7-complex-and-cmath.md │ ├── code │ │ ├── animal.py │ │ ├── c0805_json_module.py │ │ └── c08_01_module_intro.py │ └── quiz │ │ └── README.md ├── c09_file │ ├── README.md │ ├── code │ │ ├── .gitignore │ │ ├── reading_file.py │ │ ├── reading_file_with.py │ │ ├── updating_file.py │ │ └── writing_file.py │ └── quiz │ │ └── README.md ├── c10_exceptions │ ├── README.md │ ├── chapter-10.1-exceptions.md │ ├── chapter-10.2-exception-handling.md │ ├── chapter-10.3-custom-exceptions.md │ ├── code │ │ ├── c1001_custom_exception.py │ │ └── c1002_lentth_exception.py │ └── quiz │ │ └── README.md ├── c11_pip │ ├── README.md │ ├── chapter-11.1-semver.md │ └── chapter-11.2-pip.md ├── c12_virtual_environment │ ├── README.md │ ├── chapter-12.1-virtual-environment-intro.md │ ├── chapter-12.2-venv.md │ ├── chapter-12.3-pipenv.md │ └── chapter-12.4-poetry.md ├── c13_advanced_functions │ ├── README.md │ ├── chapter-13.1-groupby.md │ ├── chapter-13.2-sorted.md │ ├── chapter-13.3-filter.md │ ├── chapter-13.4-map.md │ ├── chapter-13.5-reduce.md │ └── code │ │ ├── c0501_groupby.py │ │ ├── c0502_sorted.py │ │ └── c0503_filter.py ├── c14_regex │ ├── README.md │ ├── chapter-14.1-regular-expressions.md │ ├── chapter-14.2-regex-special-characters.md │ ├── chapter-14.3-the-re-module.md │ └── code │ │ └── ch1400_regex.py ├── c15_type_hinting │ ├── README.md │ └── example │ │ ├── ch1501_basic_type_hinting.py │ │ ├── ch1502_function_hinting.py │ │ ├── ch1503_typing_module.py │ │ └── ch1504_advanced_hinting.py ├── c16_decorators │ ├── README.md │ └── code │ │ ├── ch1401_decorator.py │ │ ├── ch1402_decorator_factory.py │ │ ├── ch1403_class_based_decorator.py │ │ └── ch1404_decorating_class.py ├── c17_mixins │ ├── README.md │ └── example │ │ ├── ch1701_mixin.py │ │ └── ch1702_multiple_mixins.py ├── c18_python_http │ ├── README.md │ └── example │ │ └── ch1801_http_client.py ├── c19_requests │ └── README.md └── c21_testing │ ├── README.md │ ├── chapter-21.1-intro.md │ ├── chapter-21.2-unittest.md │ ├── chapter-21.3-pytest.md │ └── code │ ├── c21_001_intro.py │ ├── c21_002_unittest.py │ └── c21_003_setup_teardown.py ├── poetry.lock ├── problem_solving ├── README.md ├── basic │ ├── gcd.py │ ├── matrix_multiplication.py │ ├── median.py │ ├── practical_number.py │ └── reverse_digits.py └── dp │ ├── coin_change.py │ └── fibonacci.py ├── projects ├── README.md ├── solutions │ ├── hangman │ │ ├── README.md │ │ ├── game_data.py │ │ └── hangman.py │ ├── rock_paper_scissor │ │ ├── README.md │ │ └── game.py │ └── school_manager │ │ ├── README.md │ │ ├── main.py │ │ ├── school.py │ │ └── utils.py ├── srs_hangman.md ├── srs_rock_paper_scissor.md └── srs_school_manager.md ├── pyproject.toml ├── requirements.txt └── uv.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_size = 4 6 | indent_style = space 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | max_line_length = 120 10 | tab_width = 4 11 | charset = utf-8 12 | 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | # SUMMARY 3 | 4 | 5 | 6 | ## Feature Type 7 | 8 | - [ ] New Feature 9 | - [ ] Refactoring 10 | - [ ] Bug Fix 11 | - [ ] Documentation 12 | - [ ] Unit Tests 13 | 14 | 15 | 16 | ## TARGET 17 | 18 | - [ ] Note 19 | - [ ] Quiz 20 | - [ ] Algorithm 21 | - [ ] Project 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | temp*.py 105 | 106 | # Environments 107 | **/.env 108 | **/.venv 109 | **/env/ 110 | **/venv/ 111 | **/ENV/ 112 | **/env.bak/ 113 | **/venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | 133 | # ide settings 134 | .idea 135 | 136 | **/records/ 137 | tmp/ 138 | 139 | # pdf exports of markdown files 140 | course/**/*.pdf 141 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "charliermarsh.ruff", 4 | "ms-python.python", 5 | "aaron-bond.better-comments", 6 | "fill-labs.dependi", 7 | "tamasfe.even-better-toml" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "Python Debugger: Current File", 10 | "type": "debugpy", 11 | "request": "launch", 12 | "program": "${file}", 13 | "console": "integratedTerminal" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | **TABLE OF CONTENTS**: 4 | 5 | - [CONTRIBUTING](#contributing) 6 | - [Getting Started](#getting-started) 7 | - [Code of Conduct](#code-of-conduct) 8 | - [Purpose](#purpose) 9 | - [Expected Behavior](#expected-behavior) 10 | - [Prohibited Behavior](#prohibited-behavior) 11 | - [Additional Notes](#additional-notes) 12 | - [Submitting a Pull Request](#submitting-a-pull-request) 13 | 14 | ## Getting Started 15 | 16 | - Fork the repository on Github 17 | - clone your _forked repository_ to your local machine 18 | - create an appropriate branch to make changes. 19 | - Add proper message to your commits. 20 | - publish changes to the forked repository 21 | - Create a Pull Request (PR) – Go to the original repository, 22 | click "New Pull Request," and describe your changes. 23 | 24 | > **NOTE**: _Instructions on creating pull requests is explained in_ 25 | > _[Submitting a Pull Request](#submitting-a-pull-request) section below._ 26 | 27 | ## Code of Conduct 28 | 29 | ### Purpose 30 | 31 | This Code of Conduct outlines the expected behavior for all participants in the 32 | **_Rust Raid_** community. The goal is to foster a positive, inclusive, and 33 | respectful environment where everyone feels welcome and valued. 34 | 35 | ### Expected Behavior 36 | 37 | - **Respect**: Treat everyone with respect, regardless of their background or 38 | opinions. 39 | - **Inclusiveness**: Promote a welcoming and inclusive environment for all. 40 | - **Constructive** Communication: Engage in constructive and respectful 41 | discussions, avoiding personal attacks or insults. 42 | - **Openness**: Be open to feedback and criticism, and be willing to learn from 43 | others. 44 | - **Collaboration**: Work together towards the common goal of improving the 45 | project. 46 | 47 | ### Prohibited Behavior 48 | 49 | - **Harassment**: Any form of harassment, including but not limited to: 50 | - Sexual harassment 51 | - Discrimination 52 | - Threats 53 | - Bullying 54 | - **Hate Speech**: Language that promotes hatred or discrimination against any group of people. 55 | - **Personal Attacks**: Attacks on a person's character or abilities. 56 | - **Plagiarism**: Claiming credit for work that is not your own. 57 | 58 | ### Additional Notes 59 | 60 | > This Code of Conduct is a living document and may be updated from time to time. 61 | 62 | ## Submitting a Pull Request 63 | 64 | - Navigate to your forked repository on GitHub. 65 | - Click the "Compare & pull request" button. 66 | - Add a descriptive title and provide details about your changes. You can see the PR template at `.github/PULL_REQUEST_TEMPLATE.md` file. 67 | - Submit the pull request. 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sudip Ghimire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /algorithms/README.md: -------------------------------------------------------------------------------- 1 | # Algorithms 2 | 3 | This module contains some common sorting algorithms that can be implemented 4 | using python programming language 5 | 6 | **Note**: 7 | _All examples might not be optimum for production use since we will try to_ 8 | _make it as simpler as possible to understand the basic concept in each topic._ 9 | 10 | 11 | ## Contents 12 | 13 | ### Searching Algorithms 14 | - [Linear Search](searching/linear_search.py) 15 | - [Binary Search](searching/binary_search.py) 16 | 17 | ### Sorting Algorithms 18 | - [Bubble Sort](sorting/bubble_sort.py) 19 | - [Insertion Sort](sorting/insertion_sort.py) 20 | - [Quick Sort](sorting/quick_sort.py) 21 | - [Selection Sort](sorting/selection_sort.py) 22 | - [Merge Sort](sorting/merge_sort.py) 23 | -------------------------------------------------------------------------------- /algorithms/searching/binary_search.py: -------------------------------------------------------------------------------- 1 | from typing import TypeVar 2 | 3 | 4 | def binary_search(sorted_list: list, item: int) -> int | None: 5 | """# Binary Search 6 | Binary Searching algorithm uses divide and conquer method to recursively find 7 | out elements of the item. 8 | 9 | It first compares middle most item of an array and recursively divides the 10 | array into sub-arrays virtually until it finds out the exact element. 11 | 12 | It works recursively and non-recursively, but as recursive functions are not 13 | good for larger arrays, we implemented while loop. 14 | 15 | The Time complexity of a binary search is O(log(n)) 16 | 17 | ## NOTE: 18 | The binary search works only on the sorted array. If an array is not sorted, 19 | we must sort it before we implement this algorithm. 20 | 21 | We can use python's sort() method to sort the list before feeding it to this 22 | function for now. 23 | If you want to learn more about sorting algorithm, you can check 24 | `src/algorithms/sorting/`. 25 | 26 | ## Example, 27 | 28 | we have an array [1, 3, 4, 6, 7, 8, 9] and we want to find out an index of 7 29 | 30 | - step 1: compare the middle most item with 7 [6 < 7] 31 | - step 2: split an array and take the right slice [7 8 9] (take left if the comparison was different) 32 | - step 3: compare the middle most item of [7 8 9], i.e [8 > 7] (take the left slice) 33 | - step 4: compare the middle most item fo [7]: i.e [7 == 7]; return index of that number 34 | """ 35 | # 36 | left = 0 37 | right = sorted_list.__len__() - 1 38 | 39 | while left < right: 40 | mid = (left + right) // 2 41 | mid_item = sorted_list[mid] 42 | print(f"index: {mid}, item:{sorted_list[mid]}") 43 | if mid_item == item: 44 | return mid 45 | elif mid_item < item: 46 | left = mid + 1 47 | else: 48 | right = mid - 1 49 | return None 50 | 51 | 52 | if __name__ == "__main__": 53 | sorted_list = [1, 4, 7, 8, 9, 10, 11, 12, 15, 20] 54 | print(f"list: {sorted_list}") 55 | item = int(input("Enter item to search: ")) 56 | if index := binary_search(sorted_list, item): 57 | print(f'The item "{item}" is at index {index}') 58 | else: 59 | print(f'The item "{item}" does not exist in the list') 60 | -------------------------------------------------------------------------------- /algorithms/searching/linear_search.py: -------------------------------------------------------------------------------- 1 | def linear_search(data, item) -> int | None: 2 | """# Linear Search 3 | 4 | Linear searching algorithm is a sequential searching algorithm, where we 5 | traverse through each item by index and compare every element to the desired 6 | element and return the index of the element. 7 | 8 | For a linear search, we do not need the list/array to be sorted since we 9 | check each item one by one. 10 | 11 | The Time complexity of the linear search is O(n) 12 | 13 | ## Example: 14 | 15 | Given array: [1, 5, 9, 2, 4], we want to search 9. 16 | - step 1: check first item (index = 0), item = 1 , != 9 17 | - step 2: check second item (index = 1), item = 5 , != 9 18 | - step 3: check third item (index = 2), item = 9 , == 9 [return index : 2] 19 | """ 20 | for index, _item in enumerate(data): 21 | if _item == item: 22 | return index 23 | return None 24 | 25 | 26 | if __name__ == "__main__": 27 | data = [1, 4, 7, 8, 9, 10, 11, 12, 15, 20] 28 | print(f"list: {data}") 29 | item = int(input("Enter item to search: ")) 30 | if index := linear_search(data, item): 31 | print(f'The item "{item}" is at index {index}') 32 | else: 33 | print(f'The item "{item}" does not exist in the list') 34 | -------------------------------------------------------------------------------- /algorithms/sorting/bubble_sort.py: -------------------------------------------------------------------------------- 1 | def bubble_sort(unsorted: list): 2 | """ 3 | # Bubble Sorting Algorithm 4 | 5 | Bubble sorting technique compares and swaps value one by one by traversing 6 | through the list. 7 | 8 | If there are no swaps, the sorting is complete. 9 | 10 | ## example: 11 | 12 | ### step 1 13 | - (4 2) 9 0 7 5 1 <- Swap 4 and 2 14 | - 2 (4 9) 0 7 5 1 15 | - 2 4 (9 0) 7 5 1 <- Swap 9 and 0 16 | - 2 4 0 (9 7) 5 1 <- Swap 9 and 7 17 | - 2 4 0 7 (9 5) 1 <- Swap 9 and 5 18 | - 2 4 0 7 5 (9 1) <- Swap 9 and 1 19 | - 2 4 0 7 5 1 [9] <- [9] is sorted 20 | 21 | ### Step 2 : 9 is sorted so sort remaining 22 | 23 | - (2 4) 0 7 5 1 [9] 24 | - 2 (4 0) 7 5 1 [9] <- sort 4 and 0 25 | - ... 26 | - 2 0 4 5 1 [7 9] <- [7, 9] are sorted 27 | 28 | * do this process until every elements are sorted 29 | """ 30 | for i in range(0, unsorted.__len__()): 31 | swapped = False 32 | for j in range(0, unsorted.__len__() - i - 1): 33 | if unsorted[j] > unsorted[j + 1]: 34 | unsorted[j], unsorted[j + 1] = unsorted[j + 1], unsorted[j] 35 | swapped = True 36 | # If no items swapped in the internal loop, then all items are sorted 37 | # and there is no longer need to continue the loop. 38 | if not swapped: 39 | break 40 | 41 | 42 | if __name__ == "__main__": 43 | my_list = [4, 2, 9, 0, 7, 5, 1] 44 | print(f"Unsorted: {my_list}") 45 | bubble_sort(my_list) 46 | print(f"Sorted: {my_list}") 47 | -------------------------------------------------------------------------------- /algorithms/sorting/insertion_sort.py: -------------------------------------------------------------------------------- 1 | def insertion_sort(unsorted: list): 2 | """ 3 | # Insertion Sorting Algorithm 4 | 5 | Bubble sorting technique compares and swaps value one by one by traversing 6 | Insertion Sort is the sorting algorithm, that virtually creates sub-arrays 7 | of sorted and unsorted elements. Values from unsorted part are picked and 8 | inserted at the correct position of the sorted element. 9 | 10 | Initially, first 2 elements of an array are compared and sorted if needed. 11 | after that, second item is compared with the third element and sorted if 12 | necessary (which may need sorting again within the sorted sub-array again) 13 | 14 | ## For example: 15 | 16 | * `[ 4 2 9 0 7 5 1]` 17 | 18 | * [4 2] [9 0 7 5 1] <- Sort 4 and 2 and add it to the sub array 19 | * ^ ^ 20 | * [2 4] [9 0 7 5 1] 21 | * ^ ^ 22 | * [2 4 9] [0 7 5 1] <- Compare 4 and 9 as they do not need swapping, keep at is it 23 | * ^ ^ 24 | * [2 4 0 9] [7 5 1] <- Compare and sort 9 and 0 (the sorted sub-array again needs sorting) 25 | * [2 0 4 9] [7 5 1] <- Compare and sort 4 and 0 (the sorted sub-array again needs sorting) 26 | * [0 2 4 9] [7 5 1] <- Compare and sort 2 and 0 (the sorted sub-array again needs sorting) 27 | * ... 28 | insert and sort until all items in an array is sorted 29 | """ 30 | # start from second index since we need at least 2 items in a sub-array ex: [0, 1] 31 | for index in range(1, unsorted.__len__()): 32 | # 1 new item is added at last to the sorted sub-array to check it so, we 33 | # start checking it from reverse. 34 | # if it is not sorted, we swap item with the previous index 35 | for pointer in range(index, 0, -1): 36 | if unsorted[pointer] > unsorted[pointer - 1]: 37 | break 38 | unsorted[pointer], unsorted[pointer - 1] = ( 39 | unsorted[pointer - 1], 40 | unsorted[pointer], 41 | ) 42 | 43 | 44 | if __name__ == "__main__": 45 | my_list = [4, 2, 9, 0, 7, 5, 1] 46 | print(f"Unsorted: {my_list}") 47 | insertion_sort(my_list) 48 | print(f"Sorted: {my_list}") 49 | -------------------------------------------------------------------------------- /algorithms/sorting/merge_sort.py: -------------------------------------------------------------------------------- 1 | def merge(left: list, right: list): 2 | merged = [] 3 | 4 | while True: 5 | left_exhausted = not left.__len__() 6 | right_exhausted = not right.__len__() 7 | # print("⛔ left: ", left, "right: ", right, "Merged: ", merged) 8 | 9 | if left_exhausted and right_exhausted: 10 | break 11 | 12 | if left_exhausted: 13 | merged.append(right.pop(0)) 14 | 15 | elif right_exhausted: 16 | merged.append(left.pop(0)) 17 | 18 | elif left[0] < right[0]: 19 | merged.append(left.pop(0)) 20 | else: 21 | merged.append(right.pop(0)) 22 | 23 | return merged 24 | 25 | 26 | def merge_sort(data: list): 27 | """ 28 | # Merge Sort 29 | ------------ 30 | 31 | Merge sort is an efficient sorting algorithm, which works in a divide and 32 | conquer method. It first splits the sub-array until single element is left 33 | in each split, after that, it starts merging all one by one. 34 | 35 | Example: 36 | We have an array [ 7, 3 10, 2], we start splitting it in the middle until 37 | we get single element in an array 38 | 39 | * STEP 1(split): [ 7, 3] [10, 2] 40 | * STEP 2(further split): [7] [3] [10] [2] 41 | we have single element, so we start merging arrays by sorting it 42 | At this point, we compare each element and select smallest to put at index 0 43 | for this we start comparing each item at index 0 since it is the smallest in 44 | each array. 45 | after that, we put another element by comparing 46 | 47 | * STEP 3(merge singles): [3, 7] [2, 10] 48 | ... repeat until the whole array is sorted 49 | * STEP 4(merge doubles): [2, 3, 7, 10] 50 | ... 51 | 52 | * Time complexity of Merge sort is O(n log(n) ) 53 | 54 | *Tradeoffs: 55 | - It has more space complexity since it requires storage to store temporary 56 | results 57 | - It requires copying sub arrays from the original array since we can not 58 | directly swap elements in place. 59 | """ 60 | 61 | if data.__len__() <= 1: 62 | return data 63 | 64 | mid_ptr = data.__len__() // 2 65 | # split and merge 66 | left, right = data[:mid_ptr], data[mid_ptr:] 67 | return merge(merge_sort(left), merge_sort(right)) 68 | 69 | 70 | if __name__ == "__main__": 71 | unsorted = [4, 2, 9, 0, 7, 5, 1] 72 | sorted = merge_sort(unsorted) 73 | print("Unsorted Data: ", unsorted) 74 | print("Sorted Data: ", sorted) 75 | -------------------------------------------------------------------------------- /algorithms/sorting/quick_sort.py: -------------------------------------------------------------------------------- 1 | def quick_sort(unsorted: list): 2 | """ 3 | `Quick sort` is an efficient algorithm that follows divide-and-conquer 4 | approach 5 | 6 | Quick sorting process consists of the following steps: 7 | 8 | 1. Pivot selection : Choose pivot point/element from the array 9 | 2. Partitioning : Rearrange the array 10 | 3. Recursion : Split and select pivot point until sub-array contains single element 11 | 4. Combination : Rearrange and combine all single items 12 | """ 13 | if len(unsorted) <= 1: 14 | return unsorted # Base case: already sorted 15 | 16 | # Choose the middle element as a pivot element 17 | pivot = unsorted[len(unsorted) // 2] 18 | 19 | # partitioning 20 | left, middle, right = [], [], [] 21 | 22 | for element in unsorted: 23 | if element < pivot: 24 | # add all elements to the left that are smaller than pivot 25 | left.append(element) 26 | elif element > pivot: 27 | # add all elements to the right that are greater than pivot 28 | right.append(element) 29 | else: 30 | # if element is same as pivot element, add it to the middle 31 | middle.append(pivot) 32 | 33 | # recursively perform sorting and merge all left to right partitions 34 | return [*quick_sort(left), *middle, *quick_sort(right)] 35 | 36 | 37 | if __name__ == "__main__": 38 | my_list = [4, 2, 9, 0, 7, 5, 1] 39 | print(f"Unsorted: {my_list}") 40 | sorted = quick_sort(my_list) 41 | print(f"Sorted: {sorted}") 42 | -------------------------------------------------------------------------------- /algorithms/sorting/selection_sort.py: -------------------------------------------------------------------------------- 1 | def selection_sort(unsorted: list): 2 | """ 3 | # Selection Sort 4 | 5 | Selection sort is another sorting algorithm, which compares smallest of all 6 | values and and put it in the first position. Similarly, after swapping value, 7 | it will find out second most smallest value and swap it with second item in 8 | the array. 9 | 10 | Unlike bubble sort, the sorting will be done from the beginning. 11 | 12 | ## Example: 13 | 14 | - 4 2 9 0 7 5 1 <- Swap 4 and 0 15 | - ^ ^ 16 | - [0] 2 9 4 7 5 1 <- 0 is sorted 17 | - [0] 2 9 4 7 5 1 <- Swap 2 and 1 18 | - ^ ^ 19 | -... 20 | Swap values until all values are sorted 21 | """ 22 | for index in range(0, unsorted.__len__()): 23 | smallest = index 24 | for target in range(index, unsorted.__len__()): 25 | if unsorted[smallest] > unsorted[target]: 26 | smallest = target 27 | if smallest != index: 28 | unsorted[index], unsorted[smallest] = ( 29 | unsorted[smallest], 30 | unsorted[index], 31 | ) 32 | 33 | 34 | if __name__ == "__main__": 35 | my_list = [4, 2, 9, 0, 7, 5, 1] 36 | print(f"Unsorted: {my_list}") 37 | selection_sort(my_list) 38 | print(f"Sorted: {my_list}") 39 | -------------------------------------------------------------------------------- /data_structures/README.md: -------------------------------------------------------------------------------- 1 | # Data Structures 2 | 3 | This module contains some basic and complex Data structures that can be 4 | implemented using python. 5 | 6 | **Note**: 7 | _All examples might not be optimum for production use since we will try to_ 8 | _make it as simpler as possible to understand the basic concept in each topic._ 9 | 10 | ## Contents 11 | 12 | - [Linked List](linked_list.py) 13 | - [Doubly Linked List](doubly_linked_list.py) 14 | - [Binary Tree](binary_tree.py) 15 | - [Queue](queue.py) 16 | - [Stack](stack.py) 17 | -------------------------------------------------------------------------------- /data_structures/binary_tree.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Literal 2 | 3 | TraverseOrder = Literal["pre", "in", "post"] 4 | 5 | 6 | class Node: 7 | value: Any 8 | left: "Node | None" 9 | right: "Node | None" 10 | 11 | def __init__(self, value) -> None: 12 | self.value = value 13 | self.left = None 14 | self.right = None 15 | 16 | @property 17 | def size(self): 18 | size = 1 19 | if self.left: 20 | size += self.left.size 21 | if self.right: 22 | size += self.right.size 23 | return size 24 | 25 | def traverse(self, order: TraverseOrder = "in"): 26 | left = self.left.traverse(order) if self.left else None 27 | right = self.right.traverse(order) if self.right else None 28 | 29 | data = f"{self.value}" 30 | match order: 31 | case "pre": 32 | if left: 33 | data = f"{data} -> ({left})" 34 | if right: 35 | data = f"{data} --> ({right})" 36 | case "post": 37 | if right: 38 | data = f"({right}) <- {data}" 39 | if left: 40 | data = f"({left}) <-- {data}" 41 | case _: 42 | if left: 43 | data = f"({left}) <- {data}" 44 | if right: 45 | data = f"{data} -> ({right})" 46 | return data 47 | 48 | 49 | class BinaryTree: 50 | """ 51 | A Binary tree is a data type in which a parent node can have at most 2 52 | children. Each node contains 3 different items: 53 | 54 | 1. data 55 | 2. Reference to the left child 56 | 2. Reference to the right child 57 | 58 | The basic methods in queue are: 59 | 60 | - length 61 | - enqueue 62 | - dequeue 63 | 64 | """ 65 | 66 | def __init__(self, root: Node) -> None: 67 | self.root = root 68 | 69 | @property 70 | def size(self): 71 | return self.root.size 72 | 73 | def traverse(self, order: TraverseOrder = "in"): 74 | return self.root.traverse(order) 75 | 76 | 77 | """ 78 | Visualization 79 | 5 80 | 81 | 51 52 82 | 83 | 511 512 521 522 84 | 85 | """ 86 | 87 | if __name__ == "__main__": 88 | tree = BinaryTree(Node(5)) 89 | tree.root.left = Node(51) 90 | tree.root.right = Node(52) 91 | tree.root.left.left = Node(511) 92 | tree.root.left.right = Node(512) 93 | tree.root.right.left = Node(521) 94 | tree.root.right.right = Node(522) 95 | 96 | print(f"Size of a tree: {tree.size}") 97 | print(f"tree [in order]: {tree.traverse()}") 98 | print(f"tree [pre order]: {tree.traverse('pre')}") 99 | print(f"tree [post order]: {tree.traverse('post')}") 100 | 101 | # output: 102 | 103 | # Size of a tree: 7 104 | # tree [in order]: ((511) <- 51 -> (512)) <- 5 -> ((521) <- 52 -> (522)) 105 | # tree [pre order]: 5 -> (51 -> (511) --> (512)) --> (52 -> (521) --> (522)) 106 | # tree [post order]: ((511) <-- (512) <- 51) <-- ((521) <-- (522) <- 52) <- 5 107 | -------------------------------------------------------------------------------- /data_structures/linked_list.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | next: "Node" 3 | 4 | def __init__(self, data) -> None: 5 | self.data = data 6 | self.next = None # type: ignore 7 | 8 | def push(self, node: "Node"): 9 | if self.length > 1: 10 | self.next.push(node) 11 | else: 12 | self.next = node 13 | 14 | def pop(self): 15 | match self.length: 16 | case 1: 17 | return None 18 | case 2: 19 | popped = self.next 20 | self.next = None # type: ignore 21 | return popped 22 | case _: 23 | return self.next.pop() 24 | 25 | @property 26 | def length(self): 27 | if self.next is not None: 28 | return 1 + self.next.length 29 | return 1 30 | 31 | def __str__(self) -> str: 32 | if self.length > 1: 33 | return f"Node({self.data}) -> {self.next}" 34 | else: 35 | return f"Node({self.data})" 36 | 37 | def __repr__(self) -> str: 38 | return self.__str__() 39 | 40 | 41 | class LinkedList: 42 | head: Node 43 | 44 | def __init__(self, head: Node) -> None: 45 | self.head = head 46 | 47 | @property 48 | def length(self): 49 | if self.head is not None: 50 | return self.head.length 51 | return 0 52 | 53 | def push(self, node: Node): 54 | if self.head is None: 55 | self.head = node 56 | else: 57 | self.head.push(node) 58 | 59 | def pop(self): 60 | match self.length: 61 | case 0: 62 | raise IndexError("The linked list is Empty") 63 | case 1: 64 | popped = self.head 65 | self.head = None # type: ignore 66 | return popped 67 | case _: 68 | return self.head.pop() 69 | 70 | def __str__(self) -> str: 71 | if self.head is None: 72 | return "LinkedList([])" 73 | return f"LinkedList([ {self.head} ])" 74 | 75 | def __repr__(self) -> str: 76 | return self.__str__() 77 | 78 | 79 | if __name__ == "__main__": 80 | linked = LinkedList(Node(5)) 81 | linked.push(Node(2)) 82 | linked.push(Node(7)) 83 | linked.push(Node(3)) 84 | linked.push(Node(8)) 85 | print("Linked List:", linked) 86 | print(f"Popped: {linked.pop()}") # Node(8) is popped out 87 | print(f"Popped: {linked.pop()}") # Node(3) is popped out 88 | print(f"Popped: {linked.pop()}") # Node(7) is popped out 89 | print(f"Popped: {linked.pop()}") # Node(2) is popped out 90 | print(f"Popped: {linked.pop()}") # Node(5) is popped out 91 | 92 | try: 93 | linked.pop() 94 | except IndexError as e: 95 | print(f"error: {e}") # The linked list is Empty 96 | -------------------------------------------------------------------------------- /data_structures/queue.py: -------------------------------------------------------------------------------- 1 | class Queue: 2 | """ 3 | A queue is a linear data type that follows First-In First-Out (FIFO) pattern. 4 | 5 | There is already a built-in queue data type in python, however for 6 | demonstration purpose, we create a Queue data type in python using a list. 7 | 8 | The basic methods in queue are: 9 | 10 | - length 11 | - enqueue 12 | - dequeue 13 | 14 | """ 15 | 16 | def __init__(self): 17 | self.__data = [] 18 | 19 | def __str__(self) -> str: 20 | return f"Queue({self.__data})" 21 | 22 | @property 23 | def length(self): 24 | return self.__data.__len__() 25 | 26 | def enqueue(self, data): 27 | self.__data.insert(0, data) 28 | 29 | def dequeue(self): 30 | return self.__data.pop() 31 | 32 | 33 | if __name__ == "__main__": 34 | queue = Queue() 35 | queue.enqueue(1) # 1 36 | queue.enqueue(2) # 2, 1 37 | queue.enqueue(3) # 3,2,1 38 | queue.enqueue(4) # 4,3,2,1 39 | 40 | print(f"Queue: {queue}") 41 | print(f"length of queue: {queue.length}") 42 | 43 | print(f"dequeue: {queue.dequeue()}") # 1 is popped 44 | print(f"dequeue: {queue.dequeue()}") # 2 is popped 45 | print(f"dequeue: {queue.dequeue()}") # 3 is popped 46 | -------------------------------------------------------------------------------- /data_structures/stack.py: -------------------------------------------------------------------------------- 1 | class Stack: 2 | """ 3 | Stack is a linear data type that follows Last-In First-Out (LIFO) pattern. 4 | 5 | We can think a stack as a stack of biscuits on a packet or a stack of plates 6 | in the kitchen. The last item to add to the top of the stack will be taken 7 | out first to be used. 8 | 9 | Another good example of stack is a gift box with multiple wraps. The wrap 10 | that we wrapped last is always unwrapped first. 11 | 12 | For demonstration purpose, we create a Stack data type in python using a list. 13 | 14 | The basic methods in stack are: 15 | 16 | - length 17 | - push: add item to the stack 18 | - pop: remove item from the stack 19 | - peek: see the item on the top of the stack 20 | 21 | """ 22 | 23 | def __init__(self): 24 | self.__data = [] 25 | 26 | def __str__(self) -> str: 27 | return f"Stack({self.__data})" 28 | 29 | @property 30 | def length(self): 31 | return self.__data.__len__() 32 | 33 | def peek(self): 34 | return self.__data[-1] 35 | 36 | def push(self, data): 37 | self.__data.append(data) 38 | 39 | def pop(self): 40 | return self.__data.pop() 41 | 42 | 43 | if __name__ == "__main__": 44 | stack = Stack() 45 | stack.push(1) # 1 46 | stack.push(2) # 1, 2 47 | stack.push(3) # 1, 2, 3 48 | stack.push(4) # 1, 2, 3, 4 49 | 50 | print(f"Stack: {stack}") 51 | print(f"Peek: {stack.peek()}") # The last item to push is peek first from the stack 52 | print(f"length of the Stack: {stack.length}") 53 | 54 | # Similarly, the first item to be popped from the stack is the last item to be pushed 55 | print(f"pop: {stack.pop()}") # 4 is popped 56 | print(f"pop: {stack.pop()}") # 3 is popped 57 | print(f"pop: {stack.pop()}") # 2 is popped 58 | -------------------------------------------------------------------------------- /design_patterns/README.md: -------------------------------------------------------------------------------- 1 | # Design patterns 2 | 3 | This module contains Some of the popular design patterns that can be implemented 4 | in Python Programming language. 5 | 6 | **Note**: 7 | _All examples might not be optimum for production use since we will try to_ 8 | _make it as simpler as possible to understand the basic concept in each topic._ 9 | 10 | 11 | ## Contents 12 | 13 | - [Adapter Pattern](adapter.py) 14 | - [Builder Pattern](builder.py) 15 | - [Command Pattern](command.py) 16 | - [Decorator Pattern](decorator.py) 17 | - [Factory Pattern](factory.py) 18 | - Observer Pattern 19 | - [Singleton Pattern](singleton.py) 20 | - Strategy Pattern 21 | -------------------------------------------------------------------------------- /design_patterns/adapter.py: -------------------------------------------------------------------------------- 1 | """ 2 | An adapter design pattern is a structural design pattern that allows different 3 | and incompatible objects to work together 4 | 5 | A good example of an adapter design pattern is using different parsers with 6 | completely incompatible methods and calls. 7 | 8 | The example below uses JsonAdapter and XmlAdapter to adapt the `parse` method. 9 | """ 10 | 11 | from typing import Any 12 | from xml.etree import ElementTree 13 | import json 14 | 15 | 16 | class JsonParser: 17 | def load_json(self, data: str): 18 | return json.loads(data) 19 | 20 | 21 | class XmlParser: 22 | def parse_xml(self, data: str): 23 | root = ElementTree.fromstring(data) 24 | # root = tree.getroot() 25 | return {child.tag: child.text for child in root} 26 | 27 | 28 | # Adapter Interface 29 | class ParserAdapter: 30 | parser: Any 31 | 32 | def parse(self, data: str): 33 | raise NotImplementedError 34 | 35 | 36 | # Adapters for each parsers 37 | class JSONAdapter(ParserAdapter): 38 | def __init__(self, json_parser): 39 | self.parser = json_parser 40 | 41 | def parse(self, data): 42 | return self.parser.load_json(data) 43 | 44 | 45 | class XMLAdapter(ParserAdapter): 46 | def __init__(self, xml_parser): 47 | self.parser = xml_parser 48 | 49 | def parse(self, data): 50 | return self.parser.parse_xml(data) 51 | 52 | 53 | if __name__ == "__main__": 54 | # defining an adapter with different parser 55 | json_parser = JSONAdapter(JsonParser()) 56 | xml_parser = XMLAdapter(XmlParser()) 57 | 58 | json_parsed_dict = json_parser.parse('{"name": "John Doe", "age":20}') 59 | print(json_parsed_dict) # {'name': 'John Doe', 'age': 20} 60 | 61 | xml_parsed_dict = xml_parser.parse("John Doe20") 62 | print(xml_parsed_dict) # {'name': 'John Doe', 'age': '20'} 63 | -------------------------------------------------------------------------------- /design_patterns/builder.py: -------------------------------------------------------------------------------- 1 | """ 2 | Builder Design Pattern 3 | ----------------------- 4 | 5 | A builder design pattern is a creation design pattern that separates the object 6 | construction process in different scenarios. 7 | 8 | You can think a builder pattern as a regular PC builder in which different 9 | components are attached when required. For different user, same brand PC might 10 | have different specifications. 11 | """ 12 | 13 | from typing import Any, Literal 14 | 15 | 16 | class PC: 17 | def __init__(self) -> None: 18 | self.processor: str | None = None 19 | self.memory = 0 20 | self.storage = 0 21 | 22 | def __str__(self) -> str: 23 | return f"PC with {self.processor} processor ({self.memory}GB RAM, {self.storage}GB HDD)" 24 | 25 | 26 | class PCBuilder: 27 | def __init__(self) -> None: 28 | self.pc = PC() 29 | 30 | def set_processor(self, processor: Literal["amd", "intel", "arm"]): 31 | self.pc.processor = processor 32 | return self 33 | 34 | def set_memory(self, value: int): 35 | self.pc.memory = value 36 | return self 37 | 38 | def set_storage(self, value: int): 39 | self.pc.storage = value 40 | return self 41 | 42 | def build(self): 43 | return self.pc 44 | 45 | 46 | if __name__ == "__main__": 47 | builder = PCBuilder() 48 | 49 | gaming_pc = ( 50 | builder.set_processor("amd") # set AMD processor 51 | .set_memory(32) 52 | .set_storage(1024) 53 | .build() 54 | ) 55 | 56 | print("Gaming PC: ", gaming_pc) # PC with amd processor (32GB RAM, 1024GB HDD) 57 | 58 | mobile_pc = ( 59 | builder.set_processor("arm") # set ARM processor 60 | .set_memory(value=8) 61 | .set_storage(256) 62 | .build() 63 | ) 64 | print("Mobile PC: ", mobile_pc) # PC with arm processor (8GB RAM, 256GB HDD) 65 | -------------------------------------------------------------------------------- /design_patterns/decorator.py: -------------------------------------------------------------------------------- 1 | """ 2 | A decorator design pattern is a structural design pattern that allows to 3 | dynamically attach new behavior to the existing object without changing the 4 | original implementation. 5 | 6 | A decorator creates a wrapper around the original implementation so that it can 7 | be reused in another implementation too. 8 | 9 | We can implement decorator pattern where we want some conditional results such 10 | as displaying logs while debugging. 11 | """ 12 | 13 | 14 | class User: 15 | def __init__(self, name, age): 16 | self.name = name 17 | self.age = age 18 | 19 | 20 | class PrivateProfileDecorator: 21 | def __init__(self, user: User) -> None: 22 | self.user = user 23 | 24 | def display(self): 25 | print(f"[Private Profile] Name: {self.user.name}") 26 | 27 | 28 | class PublicProfileDecorator: 29 | def __init__(self, user: User) -> None: 30 | self.user = user 31 | 32 | def display(self): 33 | print(f"[Public Profile] Name: {self.user.name}, age: {self.user.age}") 34 | 35 | 36 | """ 37 | In python, we can also use functional decorators to work on a decorator pattern. 38 | A basic logger with decorator design pattern is as follows: 39 | """ 40 | 41 | 42 | def logger(func): 43 | def __inner(*args, **kwargs): 44 | print(f"[ LOG ] calling function [{func.__name__}]") 45 | return func(*args, **kwargs) 46 | 47 | return __inner 48 | 49 | 50 | @logger 51 | def add(x: int, y: int): 52 | print(f"The sum is: {x+y}") 53 | 54 | 55 | if __name__ == "__main__": 56 | user = User("John Doe", 20) 57 | 58 | private = PrivateProfileDecorator(user) 59 | private.display() 60 | 61 | public = PublicProfileDecorator(user) 62 | public.display() 63 | 64 | # logger decorated add function 65 | add(5, 10) 66 | """ 67 | OUTPUT: 68 | 69 | [Private Profile] Name: John Doe 70 | [Public Profile] Name: John Doe, age: 20 71 | 72 | [ LOG ] calling function [add] 73 | The sum is: 15 74 | """ 75 | -------------------------------------------------------------------------------- /design_patterns/factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Factory Design Pattern is a design pattern that allows user to instantiate 3 | different object based on the constraints. In this design pattern a factory 4 | 5 | The components of Factory pattern are as follows: 6 | 7 | 1. client: part of code that creates object 8 | 2. Factory: interface that defines methods for a client code 9 | 3. Product: it is an instance that factory creates 10 | 11 | """ 12 | 13 | from abc import ABC, abstractmethod 14 | from typing import Literal 15 | 16 | 17 | class PaymentGateway(ABC): 18 | """ 19 | The PaymentGateway class is an abstract class that provides blueprint for 20 | all the payment methods. 21 | """ 22 | 23 | @abstractmethod 24 | def pay(self, amount: float): 25 | raise NotImplementedError() 26 | 27 | 28 | class Paypal(PaymentGateway): 29 | def pay(self, amount: float): 30 | print(f"Paid '${amount}' with paypal") 31 | 32 | 33 | class Stripe(PaymentGateway): 34 | def pay(self, amount: float): 35 | print(f"Paid '${amount}' with stripe") 36 | 37 | 38 | class PaymentGatewayFactory: 39 | """ 40 | This is a factory class that provides the interface to instantiate a paypal 41 | or stripe object depending on the parameter of the `create` method. 42 | 43 | The create method returns the initialized object of the proper 44 | """ 45 | 46 | @classmethod 47 | def create(cls, method: Literal["paypal", "stripe"]): 48 | """ 49 | This method is a client code that initializes the respective object. 50 | """ 51 | match method: 52 | case "paypal": 53 | return Paypal() 54 | case "stripe": 55 | return Stripe() 56 | case _: 57 | raise ValueError("Invalid gateway name") 58 | 59 | 60 | if __name__ == "__main__": 61 | """ 62 | here, `stripe` and `paypal` objects are products that are instantiated by 63 | the PaymentGatewayFactory. 64 | """ 65 | stripe = PaymentGatewayFactory.create("stripe") 66 | stripe.pay(20) 67 | 68 | paypal = PaymentGatewayFactory.create("paypal") 69 | paypal.pay(30) 70 | -------------------------------------------------------------------------------- /design_patterns/singleton.py: -------------------------------------------------------------------------------- 1 | """ 2 | Singleton Design pattern is useful when we want to initialize the object only 3 | once. This design pattern is useful when we need to share the object state 4 | across the whole application. 5 | 6 | If we try to re-instantiate the new object, it always provide the previously 7 | initialized object instead of creating the one. 8 | 9 | The best example of the singleton design pattern is a db connection pool. 10 | In this case, if we want to connection to the database, trying to initialize 11 | the db connection pool always returns the same object. 12 | """ 13 | 14 | from datetime import datetime 15 | from time import sleep 16 | 17 | 18 | class ConnectionPool: 19 | _instance: "ConnectionPool" 20 | created_at: float 21 | name: str 22 | 23 | def __init__(self) -> None: 24 | self.created_at = datetime.now().timestamp() 25 | self.name = "" 26 | 27 | def __new__(cls): 28 | if hasattr(cls, "_instance"): 29 | print("Reusing old 'ConnectionPool' instance") 30 | return cls._instance 31 | print("Initializing new 'ConnectionPool' instance") 32 | cls._instance = super(ConnectionPool, cls).__new__(cls) 33 | return cls._instance 34 | 35 | @classmethod 36 | def _get_object(cls): 37 | try: 38 | return getattr(cls, "pool") 39 | except AttributeError: 40 | cls.pool = cls() 41 | return cls.pool 42 | 43 | 44 | if __name__ == "__main__": 45 | pool1 = ConnectionPool() # Initializing new 'ConnectionPool' instance 46 | sleep(2) 47 | 48 | pool2 = ConnectionPool() # Reusing old 'ConnectionPool' instance 49 | sleep(2) 50 | 51 | pool3 = ConnectionPool() # Reusing old 'ConnectionPool' instance 52 | 53 | """ 54 | As the state of the singleton is shared across objects, changing one object 55 | will update the state of all instantiated objects. 56 | """ 57 | print(pool1.created_at) 58 | print(pool2.created_at) # created timestamp will be same 59 | print(pool3.created_at) # created timestamp will be same 60 | 61 | pool2.name = "Pool 2" 62 | 63 | print(pool1.name) # Pool 2 64 | print(pool3.name) # Pool 2 65 | -------------------------------------------------------------------------------- /notes/c01_basics/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 Fundamentals of Python 2 | 3 | **Table of Contents**: 4 | 5 | - [Introduction to Python](Chapter-1.1-Basics.md) 6 | - [Python Environment Setup, IDE Setup](Chapter-1.1-Basics.md#installing-python) 7 | - [Hello World in Python](Chapter-1.1-Basics.md#hello-world-with-idle) 8 | - [Running Python Programs](Chapter-1.1-Basics.md#creating-editing-and-running-python-files) 9 | - [comments and documentation](Chapter-1.1-Basics.md#comment-lines) 10 | - Single Line Comments 11 | - inline Comments 12 | - Multiline Comments 13 | - Docstrings 14 | - indentation 15 | 16 | ## Quiz 17 | 18 | - [Chapter 1 Quiz](quiz) 19 | -------------------------------------------------------------------------------- /notes/c01_basics/code/c0101_hello_world.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Hello world Program 5 | 6 | Note: Please refer to the file "chapter 1 Basics.md" for this note 7 | 8 | This file contains the basic hello world program structure that can be written in either of: 9 | 10 | 1. Python File 11 | 2. Python Console 12 | 13 | """ 14 | 15 | 16 | # in python files 17 | print('hello world') 18 | 19 | # in consoles 20 | 21 | # >>> 'hello world' 22 | # 23 | # or 24 | # 25 | # >>> print('hello world') 26 | 27 | # print the value of 2+2 28 | 29 | print(2 + 2) 30 | 31 | # or in console, >>> 2+2 32 | 33 | 34 | ''' 35 | things to note: 36 | 37 | 1. print 38 | 2. print() 39 | 3. 'hello world' 40 | 4. 2 41 | 5. """ 42 | 43 | ''' 44 | -------------------------------------------------------------------------------- /notes/c01_basics/code/c0102_comments.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Comments in python 5 | 6 | - Comments are the lines or the part of code that is ignored by the python interpreter. 7 | - If you want to describe what you're doing or what the line below states, you might want to add comments 8 | that makes other users easy to understand the code 9 | - comments are not interpretted and contains no syntax inside the comment line 10 | - comments can also be added after expressions 11 | - Sometimes If some lines of codes are not necessary, we just comment the lines for future use 12 | 13 | - Single line comments can be added using # 14 | - multiline comments can be added using triple single or double quotes 15 | """ 16 | 17 | # This is a single line comment. When I go to next line the comment scope ends. 18 | """ 19 | This is a multiline comment. 20 | I can write any lines of code inside these quotation marks and will 21 | still be counted as comment 22 | Multiline comment should be enclosed within triple single or double quotes 23 | """ 24 | 25 | print("Hello World!") # Single line comment can be at the end of the statement too. 26 | 27 | 28 | # Multiline comments can also be used as documentation purpose. 29 | # Please ignore the structure of a program since we're going to learn this in the future 30 | def add_me(a, b): 31 | """ 32 | 33 | Parameters: 34 | 35 | - `a`: The first argument that can accept either integer or floating point numbers 36 | - `b`: The second argument that can accept either integer or floating point numbers 37 | 38 | Returns: 39 | 40 | the sum of 2 numbers `a` and `b` 41 | 42 | 43 | """ 44 | return a + b 45 | -------------------------------------------------------------------------------- /notes/c01_basics/quiz/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 1 Quiz 3 | 4 | Please read the note carefully and try to solve the problem below: 5 | 6 | **Table of contents**: 7 | 8 | - [Chapter 1 Quiz](#chapter-1-quiz) 9 | - [Quiz: Q0101](#quiz-q0101) 10 | - [Quiz: Q0102](#quiz-q0102) 11 | 12 | ## Quiz: Q0101 13 | 14 | 1. Try to replicate the line below and change `'hello world'` to `[your name]` 15 | and `print` it in the terminal. 16 | 17 | ```python 18 | print("hello world") 19 | ``` 20 | 21 | 2. Try adding two numbers and print the value in the terminal. (example: `print(5+6)`) 22 | 3. Try adding more than 2 numbers and see the results 23 | Example: `print(3+4+5)` 24 | 4. Try printing hello world without quotation `''` or inverted comma and see if any error occurs 25 | Example: `print(Hello World)` 26 | 27 | 5. If error occurs in any of the above step, please note the line number and the message where the exception is thrown. 28 | 29 | **[Click](solution/q0101.py)** to see the solution 30 | 31 | ## Quiz: Q0102 32 | 33 | 1. Print `"Hello World"` using print() function 34 | 2. Comment the line from the above solution and try running the program again. Explain what happens and why it happens 35 | 3. Create a multiline comment using Triple single `'''` and double quotes `"""` and add your content. 36 | 37 | **[Click](solution/q0102.py)** to see the solution 38 | -------------------------------------------------------------------------------- /notes/c01_basics/quiz/solution/q0101.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0101 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | # Answers 8 | 9 | # 1 10 | print('John Doe') 11 | 12 | # 2 13 | print(10 + 20) 14 | 15 | # 3 16 | print(10 + 20 + 30) 17 | 18 | # 4 19 | # print(Hello World) 20 | """ 21 | Since the strings needs to be quoted with single or double 22 | quotation marks, the above line gives error when the code 23 | is uncommented 24 | """ 25 | -------------------------------------------------------------------------------- /notes/c01_basics/quiz/solution/q0102.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0102 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | # 1 8 | print("Hello World") 9 | 10 | # 2 11 | # print("Hello World") 12 | 13 | """ 14 | When above line is commented, Nothing gets printed since the comment line is ignored by the interpreter. 15 | """ 16 | 17 | # 3 18 | """ 19 | This is a multiline comment 20 | created using triple double-quotation marks 21 | """ 22 | 23 | ''' 24 | This is a multiline comment 25 | created using triple single-quotation marks 26 | ''' 27 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/Chapter 2.2 Numeric Data Types.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.2: Numeric Data types 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 2.2: Numeric Data types](#chapter-22-numeric-data-types) 6 | - [1. Integers `int`](#1-integers-int) 7 | - [2. Floating Point Numbers `float`](#2-floating-point-numbers-float) 8 | - [3. Complex data types `complex`](#3-complex-data-types-complex) 9 | - [4. Boolean Data types `bool`](#4-boolean-data-types-bool) 10 | 11 | ## 1. Integers `int` 12 | 13 | Integers are those data types that contains either positive or negative numbers without any decimal points. 14 | 15 | ```python 16 | num1 = 5 # decimal number 17 | 18 | # We can also assign binary, octal, or hexadecimal numbers 19 | 20 | num2 = 0x4a # hexadecimal number 21 | num3 = 0b101 # binary number 22 | num4 = 0o447 # Octal number 23 | 24 | num5: int = -5 # optional type hinting 25 | ``` 26 | 27 | ## 2. Floating Point Numbers `float` 28 | 29 | Floating point numbers are those data types that can contain decimal values. 30 | 31 | ```python 32 | num1 = 5.5 33 | num2 = -5.5 34 | num3: float = 5.5 # optional type hinting 35 | ``` 36 | 37 | ## 3. Complex data types `complex` 38 | 39 | Unlike other programming languages, python comes with built in complex data types. we store the data in format: 40 | 41 | `real` + `imaginary` `j` 42 | 43 | ```python 44 | comp1 = 5 + 4j 45 | comp2 = -5j 46 | comp3: complex = 8 - 5j # optional type hinting 47 | ``` 48 | 49 | ## 4. Boolean Data types `bool` 50 | 51 | We can count Boolean data types as both numeric and logical data types since `True` represents `1` and `False` represents `0`. In python we have keywords assigned for Boolean data types. 52 | 53 | ```python 54 | engaged = True 55 | married = False 56 | 57 | alive: bool = True # optional type hinting 58 | ``` 59 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/Chapter 2.6 Typecasting.md: -------------------------------------------------------------------------------- 1 | # Chapter 2.6. Typecasting 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 2.6. Typecasting](#chapter-26-typecasting) 6 | - [Implicit typecasting](#implicit-typecasting) 7 | - [Explicit typecasting](#explicit-typecasting) 8 | 9 | In case of python there are 2 types of type conversion 10 | 11 | 1. Implicit typecasting 12 | 2. Explicit typecasting 13 | 14 | ## Implicit typecasting 15 | 16 | - It is the type of typecasting when interpreter automatically converts data type while performing calculation 17 | - It is done when we perform operations between lower and higher precision quantities. 18 | - Implicit type casting always casts lower precision data type to higher precision one. 19 | - example: `int` can be implicitly type casted to `float` 20 | 21 | Example: 22 | 23 | ```python 24 | print(5 + 4.5) 25 | ``` 26 | 27 | In this case, the integer, `5` is automatically converted into `float` data 28 | type and is then added to `4.5` since 4.5 is higher precision number. 29 | 30 | While Using F-Strings, data automatically gets typecasted to display the correct representation. 31 | 32 | Example: 33 | 34 | ```python 35 | x = 45 36 | sentence = f'The value of x is {x}' 37 | ``` 38 | 39 | In this case, the value of x will automatically be typecasted into `str` and replaced inside `{}`. 40 | 41 | ## Explicit typecasting 42 | 43 | If we want to manually convert one data type into another, then it is known as 44 | explicit typecasting. 45 | 46 | - it is a manual process where we want to convert one type of data to another 47 | - it is more useful when we want to convert numeric to non-numeric and vice-versa. 48 | 49 | **Example**: _Converting string input to integer while parsing value from command line_ 50 | 51 | ```python 52 | rate = 2 53 | quantity = input('Enter the number of apples you want to buy: ') 54 | 55 | # here, the value of quantity when entered by user will be taken as string 56 | 57 | quantity = int(quantity) # Explicit type conversion from str to int 58 | 59 | amount = quantity * rate 60 | print (f'The total payable amount is: {amount}.) 61 | 62 | ``` 63 | 64 | Type casting from higher precision data-types to lower-precision data types can also 65 | lead to data loss. so we have to make sure that we actually need to convert the 66 | higher precision data type into lower precision one. 67 | 68 | **example**: _float to int type casting omits all numbers after the decimal point._ 69 | 70 | **Example 1:** Explicit typecasting before adding `45.5` and `54.5` will result `99` instead of `100.0` in which the difference might be negligible. 71 | 72 | ```python 73 | x = 45.5 74 | y = 54.5 75 | print(x + y) # 100.0 76 | print(int(x) + int(y)) # 99 77 | ``` 78 | 79 | **Example 2:** Explicit typecasting before taking power of `10.9` by `5.7` will result `100000` instead of `777168.88` in which the difference is significant and will lead to wrong results. 80 | 81 | ```python 82 | x = 10.9 83 | y = 5.7 84 | print(x ** y) # 777168.8877963118 85 | print(int(x) ** int(y)) # 100000 86 | ``` 87 | 88 | So before typecasting, we need to be sure that we're doing it right and is not significantly changing the expected output. 89 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 2 Variables, basic data type and operations 2 | 3 | Data Types are the variables that we use to reserve some space in memory. Data types define how the data is stored and how it behaves in different situations. For example `1` + `1` equals `2`, but `A` + `B` can not be `C`. This kind of properties are defined by data types. 4 | 5 | Unlike other programming languages, we do not declare data type explicitly. Since python 3.6 Optional type-hinting is introduced in python. 6 | 7 | **Table of contents**: 8 | 9 | - [2.1. Variables](Chapter%202.1%20Variables.md) 10 | - [2.2. Numeric Data Types](Chapter%202.2%20Numeric%20Data%20Types.md) 11 | - [2.3. Strings](Chapter%202.3%20Strings.md) 12 | - [2.4. String Formatting](Chapter%202.4%20string%20formatting.md) 13 | - [2.5. Basic Operations](Chapter%202.5%20Operations.md) 14 | - [2.6. Type Casting/conversion](Chapter%202.6%20Typecasting.md) 15 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0201_variables.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Variables in Python 4 | 5 | let us suppose we have a statement as follows: 6 | x = 5 7 | 8 | here, `x` is a variable. 9 | The word used in a variable is also known as identifier 10 | """ 11 | 12 | a = 5 # integer 13 | b = 5.45 # floating point numbers (floats) 14 | c = "Hello There+-!#$....." # string 15 | 16 | # the keyword = is used for assignment 17 | 18 | # statement 19 | x = a + 10 # x = 15 20 | 21 | x = x + 1 # x = 16 22 | # It might be confusing to people when we see `x = x+1` in the statement since it mathematically 23 | # violates the left hand and right hand values. We can think `=` as the update to the previous value, which makes it 24 | # visually similar to `x <- x+1` or x is updated to be x + 1 from now onwards 25 | 26 | 27 | # defining multiple variable at once in python 28 | # let us suppose we have 3 variable declarations 29 | v1 = 5 30 | v2 = 6 31 | v3 = 7 32 | # we can achieve it in the single line with the following statement 33 | v1, v2, v3 = 5, 6, 7 34 | 35 | # or 36 | (v1, v2, v3) = (5, 6, 7) 37 | 38 | 39 | # rules for defining an identifier (name of the variable) 40 | 41 | """ 42 | 1. it can start with alphabetical characters 43 | 2. it can start with _ character 44 | 3. it can have numeric characters in between or at the end but not in the beginning 45 | 4. we can not use special characters like space, tab, `+`, `-`, etc. 46 | 5. we can separate different words with underscores `_` 47 | 6. we use snake_case for variable definition 48 | 49 | lists of valid and invalid identifiers are shown below: 50 | 51 | 1. name = 'cow' -> Valid 52 | 2. Name = 'cow -> Valid but not recommended by PEP (Always use snake case) 53 | 3. name one = 'cow' -> Invalid since it can not contain special characters except `_` 54 | 4. 1name = 'cow' -> Invalid since it can not start with numeric characters 55 | 5. name1 = 'cow' -> Valid 56 | 6. name_1 = 'cow' -> Valid 57 | 7. first_name = 'John' -> Valid 58 | 8. FirstName = 'John' -> Valid bud not recommended by PEP (Camel case is recommended for classes) 59 | """ 60 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0202_basic_data_types.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Numeric Data Types in Python 4 | 5 | to run the file, we can go to the terminal and just type 6 | python 02_data_types/02_basic_data_types.py 7 | 8 | """ 9 | x: complex = -5j 10 | x = 5 # integer 11 | x = 5.5 # float 12 | x = True # Boolean 13 | 14 | # simple interest 15 | # p -> principal amount 10000 16 | # r -> rate 10.5% oof interest 17 | # t -> time (years) 1.5 18 | 19 | p = 10000 # integer 20 | r = 10.5 # float 21 | t = 1.45 # float 22 | 23 | print(type(p)) 24 | 25 | # practice 26 | simple_interest = (p * t * r) / 100 27 | print(simple_interest) 28 | 29 | 30 | # String Data Types in Python 31 | string_one = 'Hello World' 32 | 33 | # Lists 34 | ''' 35 | - They are similar to arrays in other programming languages 36 | - They are containers for multiple data 37 | - Items are ordered 38 | - First index of the list is 0 39 | - The list with n number of items has last index of n-1 40 | 41 | - Differences between arrays in other programming languages and lists in python are: 42 | - It can have more than one type of data 43 | [1, 1, 'a', 1.5, [1,'a'], True] 44 | - Lists are mutable 45 | 46 | - Lists can have duplicate values 47 | ''' 48 | 49 | # Initializing a list 50 | # we initialize a list using a large bracket or Square bracket [] 51 | # Each item is separated by comma 52 | # It is recommended to use a space after comma 53 | # It is also possible to assign it in multiple lines 54 | 55 | list_one = [1, 2, 3, 4, 5] 56 | list_one.append(6) 57 | 58 | wild_animals = [ 59 | 'Tiger', 60 | 'Lion', 61 | 'Leopard', 62 | 'Elephant', 63 | 'Rhino' 64 | ] 65 | 66 | 67 | # Tuple Data Type 68 | 69 | """ 70 | - Tuples are similar as lists, but the values are immutable 71 | - Tuples are ordered 72 | - Tuples index starts with 0 73 | - Tuples can have more than one data types 74 | - Tuples can be non-unique 75 | - Tuples can not be changed or modified once initialized 76 | - We can not add or remove elements from tuple 77 | - Tuples are represented by small brackets. 78 | - Tuples are faster than lists 79 | """ 80 | 81 | # initializing a tuple 82 | tuple_one = (1, 2.4, 3, 4, 4, 'Cat', [1, 2, 3]) 83 | 84 | wild_animals_tuple = ('Tiger', 'Lion', 'Leopard', 'Elephant', 'Rhino') 85 | 86 | 87 | # Sets 88 | """ 89 | - Sets are similar to lists except they have unique data 90 | - Sets are represented by braces {} 91 | - Sets are not ordered 92 | - Sets can not be accessed with indices. 93 | - Set items can not be changed individually, but can be added to it 94 | 95 | - Mathematical operations 96 | - Union 97 | - Intersection 98 | - Difference, ... 99 | 100 | """ 101 | 102 | # initializing a set 103 | 104 | set_one = {1, 2, 3, 4} 105 | 106 | set_2 = {1, 2, 3, 4, 4} 107 | 108 | print(set_2) 109 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0203_strings.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Strings in Python 4 | 5 | - single quotes 6 | - double quotes 7 | - escape characters 8 | - quotation inside quotes 9 | - concatenation 10 | - length of the string 11 | - capitalization 12 | - isdecimal 13 | 14 | Formatting strings: 15 | - %s 16 | - `format()` function 17 | - f-strings 18 | 19 | """ 20 | 21 | # We can use Single quote for strings 22 | name = 'John' 23 | 24 | # We can use double quotes for strings 25 | name = "John" 26 | 27 | # We can use single quotes inside double or vice versa 28 | x = "I am at John's" 29 | x = 'He said, "John is a cool guy!"' 30 | 31 | # If we want to have special characters or 32 | # We want single inside single quotes or double quotes inside double quotes, we have to use escape characters 33 | 34 | # We can achieve escape characters by using backslash `\` before the character. 35 | x = 'I am at John\'s' # this will output : I am at John's 36 | 37 | # some of the examples of escape characters and their use cases are as follows: 38 | """ 39 | \t => tabs 40 | \n => new lines 41 | \" => double quotes 42 | \' => single quotes 43 | \\ => backslash 44 | \a => ascii bell 45 | \b => backspace 46 | \r => carriage return 47 | \v => vertical tab 48 | for more detail about escape characters, please see https://docs.python.org 49 | """ 50 | 51 | two_lines = "This is the first line.\nThis is the second line." 52 | 53 | paragraph = "Microsoft and our third-party\n vendors use cookies to store and access information such as unique IDs to deliver, maintain and improve our\n services and ads. If you agree, MSN and Microsoft Bing will personalize the content and ads that you see. You can select \"I Accept\" to consent to these uses or click on \"Manage preferences\" to review your options  You can change your selection under \"Manage\n Preferences\" at the bottom of this page.\n\n\n" 54 | 55 | print(paragraph) 56 | 57 | hobbies = "\n\n1.\tsinging\n2.\tdancing\n3.\tcoding" 58 | print(hobbies) 59 | 60 | # Concatenation 61 | # it is the process of adding or joining 2 strings together 62 | 63 | first_name = "Mohammad" 64 | last_name = "Ibrahim" 65 | 66 | full_name = "Mohammad" + "Ibrahim" # MohammadIbrahim 67 | 68 | print("My name is " + full_name) # My name is MohammadIbrahim 69 | 70 | full_name = "My name is " + first_name + " " + last_name # My name is Mohammad Ibrahim 71 | 72 | # length of the string 73 | """ 74 | we can get the length of string with `__len__()` method 75 | 76 | the method gives the length of string including all the white spaces and special characters 77 | """ 78 | 79 | length = paragraph.__len__() 80 | 81 | print("the length of the paragraph is: " + str(length)) 82 | 83 | print("Mohammad".__len__()) # 8 84 | 85 | print("John Doe".__len__()) # 8 86 | 87 | # capitalization, Upper Case, and Lower Case 88 | 89 | print("john".capitalize()) # John 90 | 91 | print("john".upper()) # JOHN 92 | print("JOHN".lower()) # john 93 | 94 | # isdecimal() 95 | value = "10" 96 | print(value.isdecimal()) # True 97 | print("123stu".isdecimal()) # False 98 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0205_arithmetic.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Arithmetic operations 4 | - Addition 5 | - Subtraction 6 | - Multiplication 7 | - Division 8 | - Modulus 9 | - Exponentiation 10 | - Integer Division 11 | """ 12 | 13 | # %% Addition 14 | a1 = 5 + 6 # integer and integer 15 | a2 = 5.5 + 2.3 # float and float 16 | a3 = 4.7 + 2 # integer and float 17 | a4 = "Hello " + "world" # string and string 18 | a5 = (5 + 4j) + (6 + 5j) # complex and complex 19 | 20 | # a6 = 5 + 'Hello there' # numeric and non-numeric not supported 21 | 22 | 23 | # %% Subtraction 24 | b1 = 5 - 6 # integer and integer 25 | b2 = 5.5 - 2.3 # float and float 26 | b3 = 4.7 - 2 # integer and float 27 | # b4 = "Hello " - "world" # string subtraction not supported 28 | b5 = (5 + 4j) - (6 + 5j) # complex and complex 29 | 30 | # %% Multiplication 31 | c1 = 5 * 6 # integer and integer 32 | c2 = 5.5 * 2.3 # float and float 33 | c3 = 4.7 * 2 # integer and float 34 | c4 = "Hello " * 5 # string and int 35 | c5 = (5 + 4j) * (6 + 5j) # complex and complex 36 | 37 | # %% Division 38 | d1 = 5 / 6 # integer and integer 39 | d2 = 5.5 / 2.3 # float and float 40 | d3 = 4.7 / 2 # integer and float 41 | d4 = (5 + 4j) / (6 + 5j) # complex and complex 42 | 43 | # %% Modulus 44 | e1 = 55 % 4 # integer and integer 45 | e2 = 5.5 % 2.3 # float and float 46 | e3 = 4.7 % 2 # integer and float 47 | 48 | # %% Exponentiation 49 | 50 | f1 = 5 ** 4 # integer and integer 51 | f2 = 5.5 ** 2.3 # float and float 52 | f3 = 4.7 ** 2 # integer and float 53 | 54 | # %% Integer Division 55 | print(5//2) 56 | g1 = 45//2 57 | g2 = 45.8//5.1 # 8.0 # integer equivalent of float 58 | 59 | # %% 60 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0206_relational.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Relational Operations 4 | 5 | equals (==) 6 | not equals(!=) 7 | less than (<) 8 | less than or equals (<=) 9 | greater than (>) 10 | greater than or equals (>=) 11 | 12 | Relational operation always returns either True or False 13 | 14 | """ 15 | 16 | 17 | # %% equals ( == ) 18 | print(5 == 6) # False 19 | print(4+1 == 6-1) # True 20 | 21 | 22 | # %% not equals( != ) 23 | print(5 != 6) # True 24 | print(4+1 != 6-1) # False 25 | 26 | # %% less than ( < ) 27 | 28 | print(4 < 5) # True 29 | print(4 < 4) # False 30 | print(5 < 4) # False 31 | 32 | # %% less than or equals ( <= ) 33 | print(4 <= 5) # True 34 | print(4 <= 4) # True 35 | print(5 <= 4) # False 36 | 37 | 38 | # %% greater than ( > ) 39 | print(4 > 5) # False 40 | print(4 > 4) # False 41 | print(5 > 4) # True 42 | 43 | # %% greater than or equals ( >= ) 44 | print(4 >= 5) # False 45 | print(4 >= 4) # True 46 | print(5 >= 4) # True 47 | print('A' > 'B') # False 48 | 49 | # ASCII 50 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0207_logical.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Logical Operators 4 | - logical operators are used to combined 2 or more relational operators or conditions 5 | - They use human readable words as operators 6 | - and 7 | - or 8 | - not 9 | """ 10 | 11 | # %% and 12 | """ 13 | - it is used between two statements or conditions 14 | - the value is true if both of the conditions are true 15 | """ 16 | print(True and True) # True 17 | print(True and False) # False 18 | print(False and True) # False 19 | print(False and False) # False 20 | 21 | 22 | # %% or 23 | """ 24 | - it is used between two statements or conditions 25 | - the value is true if any of the conditions is true 26 | 27 | """ 28 | print(True or True) # True 29 | print(True or False) # True 30 | print(False or True) # True 31 | print(False or False) # False 32 | 33 | 34 | # %% not 35 | """ 36 | - it is used before a condition or statement 37 | - the value is opposite of the condition 38 | """ 39 | print(not True) # False 40 | print(not False) # True 41 | 42 | 43 | # %% we can combine multiple conditions using brackets 44 | 45 | print(not((4 < 5) or (5 < 3))) 46 | 47 | # Explanation 48 | # not( (4<5) or (5<3) ) 49 | # not( True or (5<3) ) 50 | # not( True or False ) 51 | # not( True ) 52 | # False 53 | 54 | # %% 55 | # we can assign the values first and perform operations later 56 | 57 | x = 4 < 5 or 6 > 7 58 | y = True and 10 > 12 59 | 60 | print(x and y) 61 | 62 | 63 | # %% complex Logical operation example 64 | can_cat_fly = False 65 | can_snake_walk = False 66 | can_dog_bark = True 67 | can_john_speak_French = True 68 | 69 | print( 70 | not( 71 | (not(can_cat_fly or can_dog_bark)or(can_snake_walk)) 72 | and can_john_speak_French) 73 | ) 74 | 75 | # %% 76 | # If we do not use brackets in complex logical operations, we 77 | # might not get the expected output 78 | 79 | print(not True or not False) # True 80 | print(not (True or not False)) # False 81 | print((not True) or (not False)) # True 82 | # %% 83 | 84 | 85 | 86 | # if it is cloudy today and if it rained yesterday, then it rains today 87 | # in other conditions it doesn't rain 88 | 89 | cloudy = False 90 | rained = True 91 | 92 | rains = cloudy and rained 93 | 94 | if rains: 95 | print("it rains today") 96 | else: 97 | print("It does not rain today") 98 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0208_identity.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Identity operations 4 | - used to compare whether they are same object or not 5 | - they are not used to compare for equality 6 | - identity operators are: 7 | 1. `is` 8 | 2. `is not` 9 | """ 10 | 11 | # %% The right method 12 | 13 | print(type('abc') is str) # True 14 | 15 | list1 = ['abc', 'def'] 16 | list2 = list1 # same object is referenced here 17 | list3 = list1.copy() # shallow copy is made here 18 | 19 | print(list1 is list2) # True 20 | 21 | # == is not same as is 22 | print(list1 is list3) # False 23 | print(list1 == list3) # True 24 | 25 | print(type(list1) is not int) # True 26 | 27 | 28 | # %% the wrong method 29 | print(1 + 4 is 6-1) 30 | # this gives equality but it shows warning to use double equals 31 | # rather than `is` since it is not intended to compare 32 | 33 | # %% 34 | # We will be dealing with identity operator when we 35 | # start object oriented programming 36 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0209_membership.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | # %% [markdown] 3 | """ 4 | # Python Membership Operations 5 | 6 | Operators: 7 | 8 | - Membership operators are used to check if the element is present in the specified object or not. 9 | - basic membership operators: 10 | - `in` (format: `a in x`) 11 | - `not in` (format: `a not in x`) 12 | - membership operators return either `True` or `False` 13 | 14 | """ 15 | # %% `in` operator 16 | 17 | x = [1, 2, 3, 4, 5] 18 | 19 | print(5 in x) # True 20 | print(10 in x) # False 21 | 22 | people = ['John', 'Jane', 'Janet'] 23 | 24 | print('John' in people) # True 25 | print('Mohammad' in people) # False 26 | 27 | 28 | # %% `not in` operator 29 | # this is just opposite of `in` operator 30 | print('John' not in people) # False 31 | 32 | print(5 not in x) # False 33 | print(10 not in x) # True 34 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0210_bitwise.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | # %% [markdown] 3 | """ 4 | # Bitwise Operators 5 | 6 | - bitwise operators, as suggested by name, work on bit. 7 | - bitwise operator works only with numeric values 8 | - operation is done with each bit position rather than the value as a whole 9 | - suppose we have 2 numbers `5` and `6` 10 | We need to understand binary to decimal conversion and vice versa to understand bitwise operation 11 | 12 | binary numbers are also known as base-2 numbers 13 | 14 | ** not just valid for python 15 | 16 | - `5` in decimal is `101` in binary 17 | - `16` in decimal is `10000` in binary 18 | 19 | for uint8 or u8, 20 | 21 | 5 = 0 0 0 0 0 1 0 1 22 | 16 = 0 0 0 1 0 0 0 0 23 | 24 | for uint32 or u32, 25 | 26 | 5 = `0000 0000 0000 0000 0000 0000 0000 0101` 27 | 28 | 16 = `0000 0000 0000 0000 0000 0000 0001 0000` 29 | 30 | in the above example, we perform bitwise operation to same positioned digit 31 | of the both numbers 32 | - the first digit of the first value interacts with the first digit of second value 33 | - the second digit of the first value interacts with the second digit of second value, 34 | ... 35 | - the last digit of the first value interacts with the the last digit of second value 36 | 37 | you can always check the binary value of a number with 38 | `bin()` function. 39 | 40 | 41 | Some Bitwise Operations 42 | 43 | - Bitwise AND operator `&` 44 | - Bitwise OR operator `|` 45 | - Bitwise NOT operator `~` 46 | - Bitwise XOR operator `^` 47 | - Bitwise SHIFT LEFT operator `<<` 48 | - Bitwise SHIFT RIGHT operator `>>` 49 | 50 | 51 | """ 52 | 53 | # %% Bitwise AND operator `&` 54 | 55 | ''' 56 | Here 5 is 101 and 6 is 110 57 | Starting from the last digit of both 5 and 6, 58 | we do bitwise AND Operation 59 | Bitwise means, 60 | ''' 61 | x = 5 # 0 0 1 0 1 62 | y = 21 # 1 0 1 0 1 63 | print(x & y) # 0 0 1 0 1 5 64 | 65 | # job married children 66 | # 1 0 0 67 | # 4 68 | # %% Bitwise OR operator `|` 69 | 70 | x = 5 # 0 0 1 0 1 71 | y = 16 # 1 0 0 0 0 72 | print(x | y) # 1 0 1 0 1 21 73 | 74 | # %% Bitwise NOT operator `~` 75 | 76 | x = 5 # 0 1 0 1 77 | print(~5) # 1 0 1 0 78 | 79 | # %% Bitwise XOR operator `^` 80 | 81 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0211_assignment.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | """ 3 | Assignment operations: 4 | = 5 | += 6 | -= 7 | *= 8 | /= 9 | %= 10 | //= 11 | **= 12 | &= 13 | |= 14 | ^= 15 | >>= 16 | <<= 17 | """ 18 | 19 | # %% = operator 20 | x = 50 21 | print(x) 22 | 23 | x, y = 5, 10 24 | # %% += operator 25 | 26 | x += 2 # x = x + 2 27 | print(x) 28 | 29 | # %% -= operator 30 | 31 | x -= 2 # x = x - 2 32 | print(x) 33 | 34 | # %% *= operator 35 | x *= 3 # x = x * 2 36 | print(x) 37 | 38 | # %% /= operator 39 | x /= 3 # x = x / 2 40 | print(x) 41 | 42 | # %% %= operator 43 | 44 | x %= 30 # x = x % 30 45 | print(x) 46 | # %% //= operator 47 | 48 | x //= 2 # x = x // 2 49 | print(x) 50 | 51 | # %% **= operator 52 | 53 | x **= 5 # x = x ** 2 54 | print(x) 55 | 56 | # %% &= operator 57 | x = 5 # 101 58 | x &= 6 # 110 # x = x & 2 59 | print(x) # 100 60 | 61 | # %% |= operator 62 | x = 5 # 101 63 | x |= 6 # 110 # x = x | 2 64 | print(x) # 111 65 | 66 | # %% ^= operator 67 | x = 5 # 101 68 | x ^= 6 # 110 # x = x ^ 2 69 | print(x) # 011 70 | 71 | # %% >>= operator 72 | x = 5 # 0000 0101 73 | x >>= 1 # -> shift right 1 time # x = x >> 1 74 | print(x) # [ 0000 0010 ] 1 <- this is discarded 75 | 76 | # %% <<= operator 77 | x = 5 # 0000 0101 78 | x <<= 2 # <- shift left 2 times # x = x << 2 79 | print(x) # 00[00 010100] <- 2 more zeros added in the end 80 | 81 | # %% 82 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/code/c0212_typecasting.py: -------------------------------------------------------------------------------- 1 | # https://sudipghimire.com.np 2 | 3 | """ 4 | Type Conversion 5 | - Type casting or type conversion is the process of converting the data from one 6 | one type to another. 7 | - We perform type casting to unify the data types for better and efficient calculation 8 | 9 | In case of python there are 2 types of type conversion 10 | 11 | 1. Implicit type casting 12 | - it is automatically converted by the interpreter while performing calculation 13 | - it is done when we perform operations between lower and higher precision quantities. 14 | - Implicit type casting always casts lower precision data type to higher precision one. 15 | - example: int can be implicitly type casted to float 16 | 17 | 2. explicit type casting 18 | - it is a manual process where we want to convert one type of data to another 19 | - it is more useful when we want to convert numeric to non-numeric and vice-versa. 20 | - type casting from higher precision data-types to lower-precision data types can also 21 | lead to the data loss. so we have to make sure that we actually need to convert the 22 | current data type. 23 | example: float to int type casting omits all numbers after the decimal. 24 | 25 | """ 26 | 27 | # %% Implicit Type Casting Example 28 | from typing import Type 29 | 30 | 31 | x = 5 32 | y = 10.5 33 | print(x+y) # here interpreter automatically casts x from 5 to 5.0 34 | # in case of other programming languages we might have to 35 | # convert the integer type data to floating poing before 36 | # performing such operations. 37 | 38 | # %% Explicit Type Casting 39 | # integer to float type casting 40 | 41 | x = 5 # x is int (5) 42 | x = float(x) # x is float (5.0) 43 | print(type(x)) 44 | 45 | # %% integer to string 46 | x = 5 # 5 47 | x = str(5) # '5' 48 | print(type(x)) 49 | 50 | # %% string to integer 51 | 52 | x = "45" 53 | x = int(45) 54 | print(type(x)) 55 | 56 | # %% string to float 57 | 58 | PI = "3.1415" 59 | PI = float(PI) 60 | print(type(PI)) 61 | 62 | # %% trying to convert incompatible data type gives us value error 63 | PI = "3.1415" 64 | PI = int(float(PI)) # gives Value Error 65 | 66 | # %% we can always check if it can be typecasted 67 | 68 | PI = "3.1415" 69 | if PI.isdigit(): # it checks if PI is int or not 70 | PI = int(PI) 71 | else: 72 | PI = float(PI) 73 | print(type(PI)) 74 | # %% 75 | 76 | x = [5.7, 8.12, 6, 18, 12.99, 45.86, 33.44] 77 | 78 | # no type casting 79 | result = 0 80 | for a in x: 81 | result = result + a 82 | print(result) 83 | 84 | # typecasting in the loop 85 | result = 0 86 | for a in x: 87 | result = result + int(a) 88 | print(result) 89 | 90 | # typecasting in the result 91 | result = 0 92 | for a in x: 93 | result = result + a 94 | print(int(result)) 95 | # %% 96 | # application 1 : removing the duplicates 97 | x = [1, 3, 5, 2, 7, 8, 3, 0, 1, 6, 3, 9, 3] 98 | 99 | x = list(set(x)) 100 | print(x) 101 | 102 | 103 | # %% 104 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/quiz/solution/q0201.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0201 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | # 1 8 | x = 55 9 | print(x) 10 | # 2 11 | x = 60 12 | print(x) 13 | # 3 14 | x = x+10 15 | print(x) 16 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/quiz/solution/q0202.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0202 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | #1 8 | x = 10 9 | print(x) 10 | 11 | #2 12 | y = 10.5 13 | print(x) 14 | 15 | # 3 16 | print(type(x)) 17 | print(type(y)) 18 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/quiz/solution/q0203.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0203 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | first_name = "John" 8 | last_name = "Doe" 9 | 10 | # 1 11 | full_name = "My Name is " + first_name + " " + last_name 12 | print(full_name) 13 | 14 | # 2 15 | 16 | full_name_formatted = "My Name is {} {}".format(first_name, last_name) 17 | 18 | print(full_name_formatted) 19 | 20 | # 3 21 | full_name_fstring = f"My Name is {first_name} {last_name}" 22 | print(full_name_fstring) 23 | 24 | # 4 25 | full_name_s = "My Name is %s %s" % (first_name, last_name) 26 | print(full_name_s) 27 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/quiz/solution/q0204.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0204 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | PI = 3.14159265 8 | 9 | # 1 10 | print(f"{PI: 5.3f}") 11 | 12 | # 2 13 | print(f"{PI: 10.2f}") 14 | 15 | # 3 16 | print(f"The value of PIE is{PI: 12.2f}") 17 | -------------------------------------------------------------------------------- /notes/c02_basic_data_types/quiz/solution/q0205.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0205 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | name = input("What is your name?: ") 8 | age = input("What is our age?: ") 9 | print(f"Hello {age} years old {name}!!") 10 | -------------------------------------------------------------------------------- /notes/c03_advanced_data_types/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 3. Advanced Data Types and Operations 2 | 3 | **Table of Contents**: 4 | 5 | - **[3.1. List](chapter%203.1%20list.md)** 6 | - **[3.2. Tuple](chapter%203.2%20tuple.md)** 7 | - **[3.3. Dictionary](chapter%203.3%20dictionary.md)** 8 | - **[3.4. Set](chapter%203.4%20set.md)** 9 | - **[3.5. Nesting](chapter%203.5%20nesting.md)** 10 | 11 | **Quiz**: 12 | 13 | - **[Chapter 3 Quiz](quiz/README.md)** 14 | -------------------------------------------------------------------------------- /notes/c03_advanced_data_types/code/c0305_type_hinting.py: -------------------------------------------------------------------------------- 1 | # %% 2 | # https://sudipghimire.com.np 3 | """ 4 | Type Hinting in Python 5 | 6 | - Introduced first in python 3.6 7 | - Hinting is not strict so the statement can contain different datatypes 8 | - Hintings are useful specifically for the development purpose rather than execution 9 | - Hintings do not make python statically typed, but adds confidence to the programmers 10 | - it makes programmers productive by hinting the exact data type so that we do not need to browse the source 11 | 12 | """ 13 | from typing import Dict, List 14 | 15 | 16 | # %% Some Examples of hinting in statements 17 | 18 | a: int = 5 # same as a = 5 19 | 20 | x: str = "ssdsd" 21 | 22 | b: float = 5.5 # same as b = 5.5 23 | c: str = 'Tyrion Lannister' 24 | d: list = [1, 2, 3, 4, 5, 'abc'] 25 | e: tuple = (1, 2, 3) 26 | 27 | # List 28 | 29 | # %% Compound Types 30 | import typing 31 | from typing import List 32 | l1: List['int'] = [4, 5] 33 | 34 | 35 | l2: List = ['abc', 45] 36 | 37 | d1: Dict[str, str] = {'k': 'v'} 38 | 39 | 40 | # these are the basic Type hintings. 41 | # Creating custom types will be discussed in the python L2 course since 42 | # this part requires the knowledge of classes and inheritance 43 | 44 | import math 45 | def add_int(x, y): 46 | 47 | return( math.floor(x) + y) 48 | -------------------------------------------------------------------------------- /notes/c03_advanced_data_types/quiz/solution/q0301.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0301 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | my_list = [] 8 | 9 | # 1 10 | my_list.append(5) 11 | my_list.append(9) 12 | print(f"The list is {my_list}") 13 | 14 | 15 | # 2 16 | num = input("Enter a number to append: ") 17 | my_list.append(int(num)) 18 | print(f"The list after appending {num} is {my_list}") 19 | 20 | # 3 21 | more_items = [3, 4, 5] 22 | my_list.extend(more_items) 23 | print(f"The list after extending with {more_items} is {my_list}") 24 | 25 | # 4 26 | print(f"Length of the list is {len(my_list)}") # using len() function 27 | print(f"Length of the list is {my_list.__len__()}") # using class method 28 | 29 | # 5 30 | print("List before popping out second item:", my_list) 31 | my_list.pop(1) 32 | print("List after popping out second item:", my_list) 33 | -------------------------------------------------------------------------------- /notes/c03_advanced_data_types/quiz/solution/q0302.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0302 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | 7 | wild = ['tiger', 'lion', 'deer', 'bear', 'zebra'] 8 | 9 | # a 10 | 11 | wild.sort() 12 | print("List after sorting: ", wild) 13 | 14 | 15 | # b 16 | wild.reverse() 17 | print("List after Reversing: ", wild) 18 | 19 | # c 20 | wild.extend(['leopard', 'elephant', 'rhino']) 21 | print("List after extending more animals: ", wild) 22 | 23 | # d 24 | print("the index of leopard is: ", wild.index('leopard')) 25 | 26 | index = wild.index('leopard') 27 | wild.pop(index) 28 | print("List after removing leopard: ", wild) 29 | 30 | # index = wild.index('leopard') #this gives error since 'leopard' has already been removed from the list 31 | 32 | # e 33 | wild.insert(2, "leopard") 34 | print("List after re-inserting leopard: ", wild) 35 | # f 36 | wild.remove('rhino') 37 | print("List after removing rhino: ", wild) 38 | -------------------------------------------------------------------------------- /notes/c03_advanced_data_types/quiz/solution/q0303.py: -------------------------------------------------------------------------------- 1 | """ 2 | Q0303 solutions 3 | 4 | https://sudipghimire.com.np 5 | """ 6 | multi = [[12, 52, 37], [46, 51, 16], [17, 82, 39]] 7 | 8 | # a 9 | print(multi[1][1]) 10 | 11 | # b 12 | print(multi[-1][-2]) 13 | 14 | # c 15 | multi.append([40, 61, 10]) 16 | print(multi) 17 | 18 | # d 19 | for item in multi: 20 | print(f'the item is: {item}') 21 | for inner_item in item: 22 | print("the inner item is: ", inner_item) 23 | 24 | # e 25 | multi.clear() 26 | print("the list after clearing is: ", multi) 27 | -------------------------------------------------------------------------------- /notes/c04_decision_making/code/c0402_match.py: -------------------------------------------------------------------------------- 1 | 2 | # Simple pattern matching example 3 | error_code = 500 4 | 5 | match error_code: 6 | case 400: 7 | print('looks like something is missing in your input') 8 | case 404: 9 | print("Could not find what you're searching.") 10 | 11 | case 500: 12 | print('This is a server side error!!') 13 | case _: 14 | print('I could not find what the problem is!!') 15 | 16 | 17 | # Pattern Matching with Or 18 | error_code = 403 19 | 20 | match error_code: 21 | case 400: 22 | print('looks like something is missing in your input') 23 | case 401 | 403: 24 | print("I think you have permission issues.") 25 | case _: 26 | print('I could not find what the problem is!!') 27 | 28 | 29 | # pattern matching with wildcards 30 | (x, y) = (5, 7) 31 | match (x, y): 32 | case (0, 0): 33 | print("The point lies in the origin.") 34 | case (_, 0): 35 | print("The point lies in the x-axis.") 36 | case (0, _): 37 | print("The point lies in the y-axis.") 38 | case (_, _): 39 | print("The point lies somewhere between.") 40 | -------------------------------------------------------------------------------- /notes/c04_decision_making/quiz/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 4 Decision Making Quiz 2 | 3 | Please read the note carefully and try to solve the problem below: 4 | 5 | **Table of contents**: 6 | 7 | - [Chapter 4 Decision Making Quiz](#chapter-4-decision-making-quiz) 8 | - [Quiz Q0401](#quiz-q0401) 9 | - [Quiz Q0402](#quiz-q0402) 10 | - [Quiz Q0403](#quiz-q0403) 11 | - [Quiz Q0404](#quiz-q0404) 12 | - [Quiz Q0405](#quiz-q0405) 13 | 14 | ## Quiz Q0401 15 | 16 | Write a program to input a number from the terminal and check whether a number is an integer or not. 17 | 18 | ## Quiz Q0402 19 | 20 | Write a program to input a word and find out if it is palindrome. 21 | 22 | A word is palindrome if it reads the same from both backward and forward. `EVE` `HANNAH` `BOB`, `ROTATOR`, `ANNA`, etc. are some of palindrome words 23 | 24 | The output in the console should look like below: 25 | 26 | ```shell 27 | >> Enter a word [press cancel to exit]: Hannah 28 | >> palindrome 29 | 30 | >> Enter a word [press cancel to exit]: John 31 | >> not palindrome 32 | ``` 33 | 34 | ## Quiz Q0403 35 | 36 | Enter the temperature in celsius, and convert the temperature to fahrenheit. Finally, display different fever levels of the user. 37 | 38 | `fahrenheit = 9/5*celsius + 32` 39 | 40 | Conditions: 41 | | Temperature | Remarks | 42 | | ---------------: | ------------------ | 43 | | below `96F` | Low Temperature | 44 | | `96F` to `98F` | Normal Temperature | 45 | | `99F` to `101F` | Normal Fever | 46 | | `102F` to `104F` | High Fever | 47 | | above `104F` | Critical | 48 | 49 | ## Quiz Q0404 50 | 51 | Write a program that accepts a number from the terminal and checks whether it is a multiple of both 3,4, and 5 or not 52 | 53 | ## Quiz Q0405 54 | 55 | Write a program that asks a user score in percentage and display the grade with some remarks as follows: 56 | | Temperature | Grade | Remarks | 57 | | -------------: | :---: | -------------------------------------------------- | 58 | | below `60%` | C | Work hard otherwise you're going to fail the exam. | 59 | | `61%` to `70%` | B | Your result is satisfactory. | 60 | | `71%` to `80%` | B+ | Good Job, Keep doing better. | 61 | | `81%` to `90%` | A | Amazing Your hard work paid off. | 62 | | above `90%` | A+ | Excellent work, Congratulations topper!! | 63 | -------------------------------------------------------------------------------- /notes/c05_loops/Chapter 5.3 Pattern Generation.md: -------------------------------------------------------------------------------- 1 | # Chapter 5.3: Pattern Generation 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 5.3: Pattern Generation](#chapter-53-pattern-generation) 6 | - [Pattern 1](#pattern-1) 7 | - [Pattern 2](#pattern-2) 8 | - [Pattern 3](#pattern-3) 9 | - [Pattern 4](#pattern-4) 10 | 11 | ## Pattern 1 12 | 13 | ``` 14 | A A A A A 15 | A A A A A 16 | A A A A A 17 | A A A A A 18 | A A A A A 19 | ``` 20 | 21 | **Solution:** 22 | 23 | ```python 24 | for x in range(5): 25 | for y in range(5): 26 | print('A', end=' ') 27 | print() 28 | ``` 29 | 30 | ## Pattern 2 31 | 32 | ``` 33 | 1 34 | 1 2 35 | 1 2 3 36 | 1 2 3 4 37 | 1 2 3 4 5 38 | ``` 39 | 40 | **Solution:** 41 | 42 | ```python 43 | for row in range(1, 6): 44 | for col in range(1, row + 1): 45 | print(col, end=' ') 46 | print() 47 | ``` 48 | 49 | ## Pattern 3 50 | 51 | ``` 52 | 1 53 | 2 2 54 | 3 3 3 55 | 4 4 4 4 56 | 5 5 5 5 5 57 | ``` 58 | 59 | **Solution:** 60 | 61 | ```python 62 | for row in range(1, 6): 63 | for col in range(row): 64 | print(row, end=' ') 65 | print() 66 | ``` 67 | 68 | ## Pattern 4 69 | 70 | ``` 71 | 1 72 | 2 1 73 | 3 2 1 74 | 4 3 2 1 75 | 5 4 3 2 1 76 | ``` 77 | 78 | **Explanation:** 79 | 80 | - Row 1: No. of cols = 1, spaces = 4, value = `1` 81 | - Row 2: No. of cols = 2, spaces = 3, value = `Row ... 1` = `2 1` 82 | - Row 3: No. of cols = 3, spaces = 2, value = `Row ... 1` = `3 2 1` 83 | - ... 84 | 85 | **Solution:** 86 | 87 | ```python 88 | for row in range(1, 6): 89 | # printing out spaces 90 | print(' ' * (5 - row), end='') 91 | # printing out patterns 92 | for col in range(row, 0, -1): 93 | print(col, end=' ') 94 | print() 95 | ``` 96 | -------------------------------------------------------------------------------- /notes/c05_loops/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 5. Loops and Comprehension 2 | 3 | **Table Of Contents**: 4 | 5 | - [5.1. While Loop](chapter%205.1%20while%20loop.md) 6 | - [The `break` and the `continue` statement](Chapter%205.1%20while%20loop.md#the-break-and-the-continue-statement) 7 | - [`while`-`else`](Chapter%205.1%20while%20loop.md#while-else) 8 | - [Nested `while` loop](Chapter%205.1%20while%20loop.md#nested-while-loop) 9 | - [5.2. For Loop](chapter%205.chapter%205.2%20for%20loop.md) 10 | - [the `enumerate()` function](Chapter%205.2%20for%20loop.md#the-enumerate-function) 11 | - [the `zip()` function](Chapter%205.2%20for%20loop.md#the-zip-function) 12 | - [5.3. Pattern Generation](chapter%205.1%20while%20loop.md) 13 | - [5.4. List Comprehensions](chapter%205.1%20while%20loop.md) 14 | 15 | ## Introduction to loops 16 | 17 | Loop is the sequence of instructions given to the program that runs repeatedly 18 | until a certain condition is reached. There are 2 ways to 19 | perform loop or iteration in python which are: 20 | 21 | - [While Loop](chapter%205.1%20while%20loop.md) 22 | - [For Loop](chapter%205.2%20for%20loop.md) 23 | -------------------------------------------------------------------------------- /notes/c05_loops/code/c0501_while.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | # %% [markdown] 3 | """ 4 | ## While Loop 5 | - It is used for iterating until the condition fails to satisfy 6 | 7 | Basic Syntax: 8 | 9 | while : 10 | # statements 11 | 12 | 13 | """ 14 | x = 0 15 | while x < 10: 16 | print(f'current value of x is {x}') 17 | x += 1 18 | # x++ # incorrect in case of python 19 | 20 | # %% another example 21 | value = 'y' 22 | while value.lower() != 'e': 23 | value = input('Enter text[`e` to exit]: ') 24 | print(f'You Entered: {value}') 25 | print('exiting now!!') 26 | 27 | # %% another example with numbers 28 | 29 | # %% infinite loop: 30 | # while True: 31 | # print('This loop does not end') 32 | 33 | # %% break and continue statements in a loop 34 | x = 0 35 | while x < 10: 36 | x += 1 37 | if x == 5: 38 | continue 39 | if x == 8: 40 | break 41 | print(f'current value of x is {x}') 42 | print('loop complete') 43 | 44 | # %% printing out only odd numbers below 10 45 | x = 0 46 | while x < 10: 47 | x += 1 48 | if x % 2 == 0: 49 | continue 50 | print(x) 51 | print('loop complete') 52 | 53 | # %% while else statement 54 | x = 0 55 | while x < 10: 56 | x += 1 57 | # These lines do not fulfill else condition in while else 58 | # if x == 5: 59 | # continue 60 | # print(f'current value of x is {x}') 61 | # if x == 8: 62 | # break 63 | else: 64 | print(f'x is now {x}, now Exiting!!') 65 | 66 | # %% nested loops 67 | """ 68 | 1 69 | 1 2 70 | 1 2 3 71 | 1 2 3 4 72 | """ 73 | x = 1 74 | while x <= 4: 75 | y = 1 76 | while y <= x: 77 | print(y, end=' ') 78 | y += 1 79 | print() 80 | x += 1 81 | 82 | 83 | # While Else 84 | -------------------------------------------------------------------------------- /notes/c05_loops/code/c0502_for.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | # %% [markdown] 3 | """ 4 | ## For Loop 5 | - it is different than for loops in other programming languages. 6 | - for loop in python is similar to for each loop in other languages. 7 | - it iterates over the iterable such as list, tuple, etc. 8 | 9 | in other programming languages we have the following syntax 10 | for(; ; ) 11 | for (int x =0; x<10; x++){ 12 | // sdsd 13 | } 14 | # for python 15 | for in : 16 | statement(s) 17 | """ 18 | 19 | # %% basic syntax 20 | singers = ['John Lennon', 'John Mayer', 'John Legend'] 21 | for singer in singers: 22 | print(f'singer is {singer}') 23 | 24 | # %% for loop in range 25 | 26 | for x in range(10): 27 | print(f'the value is {x}') 28 | 29 | # %% looping through letters in a string 30 | for char in 'Elephant': 31 | print(f'the character is: {char}') 32 | 33 | # %% we can use break, continue, and else statements in for loop too 34 | for i in range(10): 35 | if i == 5: 36 | continue 37 | if i == 8: 38 | break 39 | print(f'current value of x is {i}') 40 | print('loop complete') 41 | 42 | # %% 43 | for x in range(10): 44 | x += 1 45 | # These lines do not fulfill else condition in while else 46 | # if x == 5: 47 | # continue 48 | # print(f'current value of x is {x}') 49 | # if x == 8: 50 | # break 51 | else: 52 | print(f'x is now {x}, now Exiting!!') 53 | -------------------------------------------------------------------------------- /notes/c05_loops/code/c0504_pattern_generation.py: -------------------------------------------------------------------------------- 1 | # Nested loop for pattern Generation 2 | """ 3 | A A A A A 4 | A A A A A 5 | A A A A A 6 | A A A A A 7 | A A A A A 8 | """ 9 | 10 | for i in range(5): 11 | for j in range(5): 12 | print('A', end=' ') 13 | print() 14 | 15 | """ 16 | 1 17 | 1 2 18 | 1 2 3 19 | 1 2 3 4 20 | 1 2 3 4 5 21 | """ 22 | """ 23 | Row 1: No. of cols = 1 24 | Row 2: No. of cols = 2 25 | Row 3: No. of cols = 3 26 | ... 27 | """ 28 | for row in range(1, 6): 29 | for col in range(1, row + 1): 30 | print(col, end=' ') 31 | print() 32 | 33 | 34 | for row in range(1, 6): 35 | for col in range(1, row + 1): 36 | print(col * 3, end=' ') 37 | print() 38 | 39 | 40 | """ 41 | 1 42 | 2 2 43 | 3 3 3 44 | 4 4 4 4 45 | 5 5 5 5 5 46 | """ 47 | """ 48 | Row 1: No. of cols = 1, values = 1 49 | Row 2: No. of cols = 2, values = 2 50 | Row 3: No. of cols = 3, values = 3 51 | ... 52 | """ 53 | for row in range(1, 6): 54 | for col in range(1, row + 1): 55 | print(row, end=' ') 56 | print() 57 | 58 | 59 | """ 60 | 1 61 | 2 1 62 | 3 2 1 63 | 4 3 2 1 64 | 5 4 3 2 1 65 | 66 | Row 1: No. of cols = 1, spaces = 4, value = 1 67 | Row 2: No. of cols = 2, spaces = 3, value = Row -> 1 68 | Row 3: No. of cols = 3, spaces = 2 69 | 70 | """ 71 | for row in range(1, 6): 72 | # printing out spaces 73 | print(' ' * (5 - row), end='') 74 | # printing out patterns 75 | for col in range(row, 0, -1): 76 | print(col, end=' ') 77 | print() 78 | -------------------------------------------------------------------------------- /notes/c05_loops/code/c0505_list_comprehension.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | # %% [markdown] 3 | """ 4 | ## List Comprehension 5 | List Comprehension creates a new list by applying an expression to each element 6 | of an iterable. 7 | 8 | 9 | Basic Syntax: 10 | [ for in ] 11 | 12 | With if condition: 13 | [ for in if ] 14 | 15 | """ 16 | # %% creating a square of numbers from a list 17 | numbers = [1, 2, 3, 4, 5, 6, 7, 8] 18 | 19 | # regular method 20 | result = [] 21 | for x in numbers: 22 | result.append(x ** 2) 23 | 24 | # list comprehension 25 | result = [x ** 2 for x in range(1, 11)] 26 | print(result) 27 | 28 | # %% 29 | # multiplying each value of a list by 3 30 | numbers = [1, 45, 6, 23, 89, 2, 78, 41] 31 | result = [x * 2 for x in numbers] 32 | # %% conditions inside list comprehension 33 | x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 34 | 35 | # I want y to be odds 36 | # and z to be evens 37 | 38 | y = [y for y in x if y % 2 == 0] 39 | z = [y for y in x if y % 2 != 0] 40 | print(y) 41 | print(z) 42 | 43 | # %% 44 | """ 45 | Creating a multi-dimensional list [[1,2,3],[1,2,3],[1,2,3]] 46 | """ 47 | # %% Step 1, visualize with traditional loop 48 | outer = [] 49 | for x in range(1, 4): 50 | # some other non-related expressions are not used 51 | inner = [] 52 | for y in range(1, 4): 53 | inner.append(y) 54 | outer.append(inner) 55 | print(outer) 56 | 57 | # %% step 2 replace inner loop with list comprehension 58 | outer = [] 59 | for x in range(1, 4): 60 | inner = [y for y in range(1, 4)] 61 | outer.append(inner) 62 | print(outer) 63 | 64 | # %% step 3 simplify the inner loop 65 | 66 | outer = [] 67 | for x in range(1, 4): 68 | outer.append([y for y in range(1, 4)]) 69 | print(outer) 70 | # %% step 4, replace the outer loop with list comprehension 71 | 72 | outer = [[y for y in range(1, 4)] for x in range(1, 4)] 73 | print(outer) 74 | 75 | # %% example 2, 3d list 76 | """ 77 | Expected list 78 | [ 79 | [[1,2], [1,2]], 80 | [[1,2], [1,2]] 81 | ] 82 | """ 83 | outer = [[[z for z in range(1, 3)] for y in range(1, 3)] for x in range(1, 3)] 84 | print(outer) 85 | 86 | # %% 87 | lst = [1, 2, 3, 4, 5] 88 | # I want output to be [1, 4, 9, 16, 25] 89 | new_lst = [] 90 | for x in lst: 91 | new_lst.append(x ** 2) 92 | lst = new_lst 93 | print(lst) 94 | 95 | # %% with list comprehension 96 | lst = [1, 2, 3, 4, 5] 97 | lst = [x ** 2 for x in lst] 98 | print(lst) 99 | -------------------------------------------------------------------------------- /notes/c06_functions/Chapter 6.2 default arguments.md: -------------------------------------------------------------------------------- 1 | # Chapter 6.2: Default parameters in functions 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 6.2: Default parameters in functions](#chapter-62-default-parameters-in-functions) 6 | - [Introduction](#introduction) 7 | 8 | ## Introduction 9 | 10 | While defining function, we can add default values to some or all parameters so 11 | that it takes default value when we do not pass values while calling the 12 | function. We need to add default parameters only after required parameters are 13 | added otherwise it raises an exception 14 | 15 | **Example 1:** A program that can add either 2 or 3 numbers 16 | 17 | ```python 18 | def add_numbers(a, b, c = 0): 19 | return a + b + c 20 | 21 | print(add_numbers(1, 2)) # 3 22 | print(add_numbers(1, 2, 3)) # 6 23 | ``` 24 | 25 | Here, in this example, if we pass 2 arguments `(1,2)`, the default value of `c` 26 | is already `0` so when we call function, it does not give us exception. 27 | 28 | If we try to define required parameters after default parameters, then it raises 29 | an exception. 30 | 31 | ```python 32 | def add_numbers(a=0, b, c): 33 | return a + b + c 34 | # SyntaxError: non-default argument follows default argument 35 | ``` 36 | 37 | **Example 2:** internationalization 38 | 39 | ```python 40 | def greet(key, lang='en'): 41 | words = { 42 | 'hello': { 43 | 'en': 'Hello', 44 | 'fr': 'Bonjour', 45 | 'np': 'नमस्कार', 46 | 'jp': 'こにちは' 47 | }, 48 | 'bye': { 49 | 'en': 'Bye', 50 | 'fr': 'au revoir', 51 | 'np': 'फेरी भेटौला', 52 | 'jp': 'さよなら' 53 | } 54 | } 55 | print(words[key][lang]) 56 | greet('hello') # Hello (default value of lang is 'en') 57 | greet('hello', 'fr') # Bonjour 58 | greet('hello', 'np') # नमस्कार 59 | ``` 60 | -------------------------------------------------------------------------------- /notes/c06_functions/Chapter 6.3 args kwargs.md: -------------------------------------------------------------------------------- 1 | # Chapter 6.3: Arbitrary arguments in python 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 6.3: Arbitrary arguments in python](#chapter-63-arbitrary-arguments-in-python) 6 | - [Background](#background) 7 | - [Arbitrary number of arguments](#arbitrary-number-of-arguments) 8 | - [Arbitrary number of keyword arguments](#arbitrary-number-of-keyword-arguments) 9 | 10 | ## Background 11 | 12 | As our functions become more and more dynamic, it is not possible to define a 13 | function with maximum number of default arguments which makes our code more 14 | complicated. Example, we want to be able to add either 3, or 4, or 5 or even 15 | 10 arguments. 16 | 17 | **The Traditional Approach:** 18 | 19 | ```python 20 | def add_numbers(a, b, c=0, d=0, e=0, f=0, g=0): 21 | return a + b + c + d + e + f + g 22 | 23 | print(add_numbers(1,2)) # 3 24 | print(add_numbers(1,2, 3)) # 6 25 | print(add_numbers(1,2, 3, 4)) # 10 26 | print(add_numbers(1,2, 3, 4, 5)) # 15 27 | print(add_numbers(1,2, 3, 4, 5, 6)) # 21 28 | ``` 29 | 30 | Here, the main problem is default parameters in all optional values which even 31 | decreases the code readability. If we could pass dynamic number of arguments, 32 | then we did not need to set default values to each variable. 33 | 34 | ## Arbitrary number of arguments 35 | 36 | In the above scenario, if we use arbitrary number of arguments, then we could do 37 | the following: 38 | 39 | ```python 40 | def add_numbers(a, b, *args): 41 | sum_value = a + b 42 | for item in args: 43 | sum_value += item 44 | return sum_value 45 | print(add_numbers(1, 2)) # 3 46 | print(add_numbers(1, 2, 3)) # 6 47 | print(add_numbers(1, 2, 3, 4)) # 10 48 | print(add_numbers(1, 2, 3, 4, 5)) # 15 49 | print(add_numbers(1, 2, 3, 4, 5, 6)) # 21 50 | ``` 51 | 52 | Here, add_number needs at lease 2 numbers to calculate the sum but is capable of 53 | accepting any number of arguments and perform addition 54 | 55 | ## Arbitrary number of keyword arguments 56 | 57 | Arbitrary number of keyword arguments are those arguments which are used as 58 | named parameters in a function call. They are extra arguments which will be 59 | treated as a dictionary whenever we want to access keys and values from it. 60 | 61 | ```python 62 | def display_student_detail(name, age, **kwargs): 63 | print(f'[ details of {age} years old student: {name}]') 64 | for key, value in kwargs.items(): 65 | print(f'The {key} is {value}') 66 | 67 | display_student_detail( 68 | 'John Doe', 20, 69 | subject='Science', class_teacher='John Lennon', address='Singapore' 70 | ) 71 | 72 | ``` 73 | 74 | **Output:** 75 | 76 | ``` 77 | [ details of 20 years old student: John Doe] 78 | The subject is Science 79 | The class_teacher is John Lennon 80 | The address is Singapore``` 81 | ``` 82 | -------------------------------------------------------------------------------- /notes/c06_functions/Chapter 6.4 recursive functions.md: -------------------------------------------------------------------------------- 1 | # Chapter 6.4: Recursive Function 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 6.4: Recursive Function](#chapter-64-recursive-function) 6 | - [Introduction](#introduction) 7 | 8 | ## Introduction 9 | 10 | Recursive function is a function which calls itself. A recursive function is 11 | generally created to iterate over the dynamic parameter until it reaches the 12 | recursion limit. Recursive function contains 2 different elements: 13 | 14 | 1. recursion limit 15 | 2. recursive function call 16 | 17 | **Basic Syntax** 18 | 19 | ```python 20 | def function_name(params): 21 | if condition: 22 | function_name(updated_params) 23 | else: # recursion Limit 24 | return final_value 25 | ``` 26 | 27 | **Example 1:** A recursive function that prints out the factorial of a number. 28 | 29 | - `5 ! = 5 * 4 * 3 * 2 * 1 = 120` 30 | - `0 ! = 1` 31 | 32 | ```python 33 | def factorial(n): 34 | if n > 0: 35 | return(n * factorial(n-1)) 36 | return 1 37 | 38 | print(factorial(7)) # 5040 39 | ``` 40 | 41 | **Example 2:** A recursive function that prints out the first nth item of a 42 | fibonacci series `1, 1, 2, 3, 5, 8, 13, 21, 34, ...`. 43 | 44 | ```python 45 | def fibonacci(n): 46 | if n > 1: 47 | return(fibonacci(n-1) + fibonacci(n-2)) 48 | return n 49 | 50 | item = fibonacci(7) 51 | 52 | print(item) # 13 53 | ``` 54 | 55 | **Example 3:** A recursive function to convert decimal to binary 56 | 57 | ```python 58 | def dec2bin(n: int): 59 | if n != 0: 60 | return dec2bin(n // 2) * 10 + n % 2 61 | else: 62 | return 0 63 | 64 | print(dec2bin(14)) # 1110 65 | ``` 66 | -------------------------------------------------------------------------------- /notes/c06_functions/Chapter 6.5 lambda.md: -------------------------------------------------------------------------------- 1 | # Chapter 6.5: Lambda Expressions 2 | 3 | **Table of Contents**: 4 | 5 | - [Chapter 6.5: Lambda Expressions](#chapter-65-lambda-expressions) 6 | - [Introduction](#introduction) 7 | - [Lambda function with Zero arguments](#lambda-function-with-zero-arguments) 8 | - [Ternary operators on lambda functions](#ternary-operators-on-lambda-functions) 9 | 10 | ## Introduction 11 | 12 | In python Lambda functions or lambda expressions are single line expressions 13 | with no name that can have any number of arguments. Lambda functions are also 14 | called as anonymous functions or one-liner functions. Lambdas are useful when 15 | we have to do small repetitive tasks. 16 | 17 | **Basic Syntax:** 18 | 19 | ```python 20 | value = lambda arg_1, arg_2, ..., arg_n : return_expression 21 | ``` 22 | 23 | here, everything separated with comma before `:` are arguments and the 24 | expression after `:` is used as the return value or expression. Lambda 25 | expression do not expect `return` keyword however whatever expression is 26 | written after the colon `:`, will be treated as return statement. 27 | 28 | **Example 1:** A lambda to add 2 values 29 | 30 | ```python 31 | add_num = lambda a,b: a+b 32 | 33 | print(add_num(5, 6)) # 11 34 | ``` 35 | 36 | ## Lambda function with Zero arguments 37 | 38 | We can also create a lambda expression with zero arguments. To create a lambda 39 | with zero arguments, we just use `lambda` keyword before a colon `:`. 40 | 41 | **Example 2:** Lambda to print Hello World 42 | 43 | ```python 44 | hello_world = lambda: "Hello World" 45 | 46 | print(hello_world()) # Hello World 47 | ``` 48 | 49 | ## Ternary operators on lambda functions 50 | 51 | ```python 52 | odd_even = lambda num: 'Even' if num % 2 == 0 else 'Odd 53 | 54 | print(odd_even(14)) # Even 55 | ``` 56 | -------------------------------------------------------------------------------- /notes/c06_functions/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 6 Functions 2 | 3 | **Table of contents**: 4 | 5 | - [6.1. Introduction to functions](Chapter%206.1%20function.md) 6 | - Defining a function 7 | - Calling a function 8 | - The pass statement 9 | - The return statement 10 | - local, non-local, and global variables 11 | - [6.2. Function with default arguments](Chapter%206.2%20default%20arguments.md) 12 | - [6.3. Arbitrary Arguments and Keyword Arguments](Chapter%206.3%20args%20kwargs.md) 13 | - [6.4. Recursive Functions](Chapter%206.4%20recursive%20functions.md) 14 | - [6.5. Lambda Functions](Chapter%206.5%20lambda.md) 15 | -------------------------------------------------------------------------------- /notes/c06_functions/code/c0602_default_parameters.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | """ 3 | Default Parameters in functions 4 | 5 | - default parameters are the parameters that has values assigned if it is not supplied 6 | while calling the function 7 | - default parameters always should be at last 8 | - any non default parameters after default causes error 9 | - default parameters are completely optional while calling 10 | """ 11 | 12 | 13 | # %% 14 | def add(x, y, z=0): 15 | return x + y + z 16 | 17 | 18 | print(add(3, 4, 10)) 19 | 20 | print(add(3, 4)) 21 | 22 | 23 | # %% example use case 24 | def greet(key, lang='en'): 25 | dict_1 = { 26 | 'hello': { 27 | 'en': 'Hello', 28 | 'fr': 'Bonjour', 29 | 'np': 'नमस्कार', 30 | 'jp': 'こにちは' 31 | }, 32 | 'bye': { 33 | 'en': 'Bye', 34 | 'fr': 'au revoir', 35 | 'np': 'फेरी भेटौला', 36 | 'jp': 'さよなら' 37 | } 38 | } 39 | print(dict_1[key][lang]) 40 | 41 | 42 | greet('hello', 'fr') 43 | greet('hello', 'jp') 44 | greet('hello') 45 | 46 | 47 | # %% power of the number with default value 2 48 | def power(x, y=2): 49 | return x ** y 50 | 51 | 52 | print(power(10)) 53 | print(power(10, 4)) 54 | 55 | # %% 56 | lst = [1, 2, 3, -3, -5, 7, 6, -4, 8, -8] 57 | new_list = [] 58 | for (index, value) in enumerate(lst): 59 | for x in lst[index + 1:]: 60 | tmp_sum = x + value 61 | for n in lst: 62 | if tmp_sum + n == 0: 63 | new_list = [*new_list, [value, x, n]] 64 | print(new_list) 65 | -------------------------------------------------------------------------------- /notes/c06_functions/code/c0605_lambda.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | # %% 3 | """ 4 | Python Lambdas 5 | - lambdas are also known as anonymous functions 6 | - they are tiny functions which contains one statement 7 | - lambdas are written in a single line 8 | - they can have zero or more arguments 9 | - they are useful when we have to do small repetitive tasks 10 | 11 | structure of lambda 12 | = lambda ,, ...: 13 | 14 | - everything before `:` and after `lambda` are arguments 15 | - expression after `:` is the return value 16 | example: 17 | f1 = lambda a,b: a+b 18 | f2 = lambda a: a**2 19 | f3 = lambda : print("Hey There!") 20 | """ 21 | 22 | 23 | # %% regular function to add 2 numbers and return value 24 | 25 | def add_numbers(a, b): 26 | return a + b 27 | 28 | 29 | add_numbers(4, 5) 30 | 31 | # %% same operation with lambda 32 | 33 | add_num = lambda a, b: a + b 34 | add_num(4, 5) 35 | 36 | # %% 37 | # multiply 2 numbers 38 | a = 6 39 | b = 8 40 | e = 10 41 | 42 | multiply = lambda c, d: c * d + e 43 | 44 | multiply(a, b) 45 | # %% lambda with no arguments 46 | 47 | 48 | print_hello = lambda: print("hello world") 49 | print_hello() 50 | 51 | # %% conditionals inside lambda 52 | 53 | # ternary operator "Even" if x % 2 == 0 else 'Odd' 54 | 55 | odd_even = lambda x: "Even" if x % 2 == 0 else 'Odd' 56 | 57 | print(odd_even(14)) 58 | 59 | # %% double the list 60 | lst = [1, 2, 3, 4, 5] 61 | 62 | double1 = lambda l: [x * 2 for x in l] 63 | 64 | double1(lst) 65 | 66 | lst = [x * 2 for x in lst] 67 | -------------------------------------------------------------------------------- /notes/c07_oop/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 7 Object-Oriented Programming (OOP) 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | > 9 | 10 | **Table of contents**: 11 | 12 | - [7.1. Introduction to OOP](Chapter-7.1-oop.md) 13 | - Class 14 | - Class Attributes, Methods and the `self` parameter 15 | - The constructor method 16 | - Built-in class attributes 17 | - object 18 | - [7.2. Class Methods and Static Methods](Chapter-7.2-Class-Methods-and-Static-Methods.md) 19 | - [7.3. Operator Overloading](Chapter-7.3-Operator-Overloading.md) 20 | - [7.4. Encapsulation](Chapter-7.4-Encapsulation.md) 21 | - [7.5. Inheritance and Polymorphism](Chapter-7.5-Inheritance-and-Polymorphism.md) 22 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0701_oop_intro.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | """ 3 | # Object Oriented Programming 4 | - it covers the limitations of functional approach of programming 5 | - functional programming is just a list of instructions but OOP approach uses block of code 6 | that contains both data and functions that operate on those data. 7 | - collection of those data and functions is known as object 8 | 9 | Features: 10 | 11 | - Inheritance 12 | - Polymorphism 13 | - encapsulation 14 | - objects 15 | - Data Abstraction 16 | """ 17 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0702_class.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | # %% 3 | """ 4 | # Class 5 | 6 | - building block of OOP 7 | - we must instantiate an object for a class to perform specific task 8 | 9 | 10 | 11 | ## basic Structure 12 | 13 | class : 14 | 15 | 16 | 17 | 18 | # Basic Convention 19 | 20 | - Class names are in `UpperCamelCase` 21 | - MyClass 22 | - ClassOne 23 | - Animal 24 | - Attributes (variables & methods) are in `snake_case` 25 | - It is better to have 2 vertical spaces before and after classes 26 | - It is better to have a vertical space before and after methods 27 | 28 | 29 | Class works just like a group or a container for variables and functions 30 | 31 | Person 32 | - first_name 33 | - last_name 34 | - address 35 | 36 | - show_fullname() 37 | 38 | detailed structure of a class: 39 | 40 | class ClassName: 41 | var1 = 42 | var2 = 43 | 44 | def function_1(self): 45 | 46 | 47 | The basics of class includes the following: 48 | 49 | - Attributes 50 | - Class Attributes 51 | - Instance Attributes 52 | - Methods 53 | - self parameter 54 | 55 | - Instance 56 | 57 | - Initializer or constructor [ __init__() method ] 58 | 59 | 60 | """ 61 | 62 | 63 | class Person: 64 | first_name = '' 65 | last_name = '' 66 | address = '' 67 | city = '' 68 | age = 0 69 | 70 | def show_fullname(self): 71 | print(f"{self.first_name} {self.last_name}") 72 | 73 | 74 | # instantiating the class 75 | mickey = Person() 76 | # what happens after creating an instance of a class? 77 | # discussion in lecture 78 | 79 | # Assigning the values to attrobutes 80 | mickey.first_name = "Mickey" 81 | mickey.last_name = "Mouse" 82 | mickey.address = "123 Fantasy Way" 83 | mickey.city = "Anahiem" 84 | mickey.age = 73 85 | 86 | # Accessing the values from attributes 87 | 88 | print(mickey.first_name) 89 | 90 | # calling the methods from the instance 91 | mickey.show_fullname() 92 | 93 | 94 | # example of a class (Animal) with the __init__() method 95 | class Animal: 96 | name = "" # Class Attribute 97 | paws = 0 # Instance Attribute 98 | word = "" 99 | 100 | def __init__(self, name, word): 101 | """ 102 | Constructor or initializer method 103 | """ 104 | self.name = name 105 | self.word = word 106 | 107 | def __str__(self): 108 | return f"Instance of Animal named {self.name}" 109 | 110 | def speak(self): 111 | print(f'I am {self.name} and I speak with {self.word}s.') 112 | 113 | def rename(self, new_name): 114 | self.name = new_name 115 | 116 | 117 | elephant = Animal("John", "trumpet") 118 | 119 | elephant.speak() 120 | elephant.rename("Jane") 121 | elephant.speak() 122 | 123 | dog = Animal('rockey', 'bark') 124 | dog.speak() 125 | 126 | print(dog) 127 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0703_classmethod_staticmethod.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | # Class Methods and static methods 5 | 6 | Class Methods 7 | - Class methods are special kind of methods that are bound to the class instead of the instance 8 | - If we create an instance and change the property of the object, it is not going to affect the property 9 | of the classmethod. 10 | 11 | Static Methods 12 | - Static methods are methods that do not bind to anything at all and simply return the underlying function 13 | without any transformation. 14 | - They just behave like a function. Only the difference is they are called along with the class name. 15 | """ 16 | 17 | 18 | class Animal: 19 | legs = 4 20 | 21 | @classmethod # bound to the class so class is passed as the first argument instead of self 22 | def print_legs(cls): 23 | print('Total no. of legs: ', cls.legs) 24 | 25 | def print_legs_1(self): # Regular method that is bound to the instance so self is passed as the first argument 26 | print('Total no. of legs: ', self.legs) 27 | 28 | 29 | print(Animal.legs) # 4 30 | 31 | Animal.print_legs() # 4 # not instantiated 32 | Animal().print_legs() # 4 # instantiated 33 | 34 | human = Animal() 35 | human.legs = 2 36 | human.print_legs() # 4 # instantiated and attribute is changed but still prints 4 instead of 2 37 | 38 | print(human.legs) # 2 39 | 40 | print(Animal.legs) # 4 41 | 42 | 43 | # def cm_to_m(value: float): 44 | # return value/100 45 | 46 | # def kg_to_lb(value: float): 47 | # return value * 2.20462 48 | 49 | # cm_value = 3536 50 | # m_value = cm_to_m(cm_value) 51 | 52 | # print(m_value) 53 | 54 | # wt_kg = 5 55 | # print(kg_to_lb(wt_kg)) 56 | 57 | # Static Methods 58 | # Just works like a regular function inside the module 59 | 60 | class Length: 61 | cm = 5438 62 | 63 | @staticmethod # unbound since it does not invoke either of class or an instance. 64 | def cm_to_m(value: float): 65 | return value / 100 66 | 67 | @staticmethod 68 | def m_to_cm(value: float): 69 | return value * 100 70 | 71 | 72 | class Weight: 73 | @staticmethod 74 | def kg_to_lb(value: float): 75 | return value * 2.20462 76 | 77 | @staticmethod 78 | def lb_to_kg(value: float): 79 | return value / 2.20462 80 | 81 | 82 | print(Length.cm_to_m(Length.cm)) 83 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0705_property.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | 5 | @property decorator in python classes 6 | 7 | - They look like regular object variables but are capable of attaching custom behavior to the class. 8 | - They are used as alternative of getters and setters 9 | - whenever we create a property inside a class, it's behavior will be tightly controlled. 10 | 11 | for example we want to add a private variable to class and want to access and modify it. 12 | I can achieve it using getter and setter method along with private variable. 13 | 14 | or I can add getter, setter and deleter property so that I can access it 15 | similar to attributes rather than calling methods. 16 | 17 | structure: 18 | class ABC: 19 | 20 | @property 21 | def my_property(self): 22 | # property body 23 | """ 24 | 25 | # %% 26 | 27 | 28 | # Using regular getter and setter 29 | class Student: 30 | 31 | def __init__(self, count: int): 32 | self.__count = count 33 | 34 | def get_count(self): 35 | return self.__count 36 | 37 | def set_count(self, value): 38 | print("I can do other things while updating my value") 39 | self.__count = value 40 | 41 | 42 | class_1 = Student(10) 43 | print(class_1.get_count()) # Getter 44 | class_1.set_count(11) 45 | print(class_1.get_count()) # Setter 46 | 47 | # %% 48 | # it can be achieved using the property decorator as follows 49 | """ 50 | Getter: 51 | Whenever the getter property is added, the method can be used as the regular variable 52 | we can access this using the regular access method instead of calling the function 53 | eg: value = obj.count 54 | 55 | 56 | syntax: 57 | 58 | @property 59 | def (params): 60 | method body 61 | 62 | Whenever the setter property is added, the method can be used as the regular variable and we can assign 63 | the value using regular assignment operator "=" 64 | 65 | eg: obj.count = 10 66 | 67 | syntax: 68 | 69 | @.setter 70 | def (params): 71 | method body 72 | 73 | """ 74 | 75 | 76 | class Students: 77 | 78 | def __init__(self, count: int): 79 | self.__count = count 80 | 81 | @property 82 | def count(self): # Getter 83 | return self.__count 84 | 85 | @count.setter # Setter 86 | def count(self, value): 87 | print("I can do other things while updating my value") 88 | self.__count = value 89 | 90 | 91 | class_1 = Students(10) 92 | print(class_1.count) 93 | class_1.count = 11 94 | print(class_1.count) 95 | 96 | # %% 97 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0707_inheritance.py: -------------------------------------------------------------------------------- 1 | # © https://sudipghimire.com.np 2 | 3 | # %% [markdown] 4 | """ 5 | # Inheritance 6 | - This concept is exactly similar to biological inheritance where child inherits the feature of parent. 7 | - in inheritance, there exists a parent class and child classes which inherits parent's behaviors. 8 | - The base class will be the parent class and the class that is derived from the base class will 9 | be treated as a child class. 10 | 11 | Basic Structure 12 | class Parent: 13 | 14 | 15 | 16 | class Child(Parent): 17 | 18 | 19 | """ 20 | 21 | 22 | # %% Example Rectangle 23 | class Rectangle: # Parent Class 24 | 25 | def __init__(self, width: float, height: float): 26 | self.width = width 27 | self.height = height 28 | 29 | def perimeter(self): 30 | return 2 * (self.width + self.height) 31 | 32 | def area(self): 33 | return self.width * self.height 34 | 35 | def diagonal_length(self): 36 | return (self.width ** 2 + self.height ** 2) ** (1 / 2) 37 | 38 | 39 | # Child Class 40 | class Square(Rectangle): # Here, the class `Square` is inheriting all the properties from the class `Rectangle`. 41 | 42 | def __init__(self, width): 43 | super().__init__(width=width, height=width) # in python3, super() does not require any arguments 44 | 45 | def diagonal_length(self): 46 | return self.width * (2 ** .5) 47 | 48 | 49 | # %% 50 | room_1 = Rectangle(5, 10) 51 | print(f'Area of room 1: {room_1.area()}') 52 | print(f'Perimeter of room 1: {room_1.perimeter()}') 53 | print(f'Diagonal length of room 1: {room_1.diagonal_length()}') 54 | 55 | # %% 56 | room_2 = Square(5) 57 | 58 | print(f'Area of room 2: {room_2.area()}') 59 | print(f'Perimeter of room 2: {room_2.perimeter()}') 60 | print(f'Diagonal length of room 2: {room_2.diagonal_length()}') 61 | 62 | # %% builtin functions that work with inheritance 63 | """ 64 | isinstance() 65 | the method returns True if an object is the instance of the class ir any of the derived classes of a class 66 | 67 | """ 68 | 69 | 70 | class Parent: 71 | pass 72 | 73 | 74 | class Child(Parent): 75 | pass 76 | 77 | 78 | obj1 = Parent() 79 | obj2 = Child() 80 | """ 81 | here, 82 | - obj1 is an instance of a class Parent 83 | - obj2 is an instance of a class Child 84 | 85 | Note: 86 | - instance of a parent class is not an instance of a child class 87 | - instance of a child class is also an instance of a parent class since it is inherited from a parent class 88 | """ 89 | 90 | print(isinstance(room_2, Square)) 91 | print(isinstance(room_1, Rectangle)) 92 | print(isinstance(room_1, Square)) 93 | print(isinstance(room_2, Rectangle)) 94 | """ 95 | issubclass() 96 | the method returns True if the derived class is the subclass of the base class. 97 | """ 98 | 99 | print(issubclass(Square, Rectangle)) 100 | print(issubclass(Rectangle, Square)) 101 | print(issubclass(Rectangle, object)) 102 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0708_multiple_inheritance.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Multiple Inheritance. 5 | 6 | Multiple inheritance means inheriting the behavior from 2 or more parent classes. 7 | 8 | The structure would be 9 | 10 | class A: 11 | 12 | 13 | 14 | class B: 15 | 16 | 17 | 18 | classs C(A, B): 19 | 20 | 21 | 22 | - In multiple inheritance, Python uses C3 Linearization algorithm to determine the order in which 23 | to resolve class attributes and methods. The process is also known as Method Resolution Order (MRO) 24 | 25 | To learn more about C3 linearization, you can check the link below: 26 | https://en.wikipedia.org/wiki/C3_linearization 27 | 28 | """ 29 | 30 | 31 | class Parent1: 32 | x = 5 33 | 34 | 35 | class Parent2: 36 | x = 10 37 | 38 | 39 | class Child(Parent1, Parent2): # According to C3 linearization, Child accepts x from Parent1. 40 | pass 41 | 42 | 43 | child = Child() 44 | 45 | print(child.x) 46 | 47 | 48 | class Shop: 49 | def __init__(self) -> None: 50 | self.__reg_no=0 51 | 52 | def get_reg_no(self): 53 | return self.__reg_no 54 | 55 | def set_reg_no(self, value): 56 | self.__reg_no = value 57 | 58 | 59 | 60 | class CoffeeShop(Shop): 61 | coffee_price = 30 62 | 63 | 64 | class Bakery(Shop): 65 | dough_nut_price = 10 66 | 67 | 68 | class Restaurant(CoffeeShop, Bakery): 69 | pizza_price = 20 70 | 71 | def __init__(self, reg_no) -> None: 72 | super().__init__() 73 | self.set_reg_no(reg_no) 74 | 75 | 76 | r1 = Restaurant('A12345') 77 | 78 | r1.dough_nut_price = 15 79 | 80 | print(r1.get_reg_no()) 81 | print(r1.dough_nut_price) 82 | print(r1.coffee_price) 83 | print(r1.pizza_price) 84 | 85 | print(isinstance(r1, Restaurant)) 86 | print(isinstance(r1, Bakery)) 87 | print(isinstance(r1, CoffeeShop)) 88 | print(isinstance(r1, Shop)) 89 | -------------------------------------------------------------------------------- /notes/c07_oop/code/c0709_monkey_patching.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Monkey Patching 5 | 6 | - it is the process of adding new variable or method to a class after it's been defined 7 | - we can introduce a new instance attribute to an object even after it has been initialized 8 | - monkey patching is useful when we do not want to perform inheritance or create a child class and 9 | change the behavior of the previously defined classes or previously instantianted objects. 10 | 11 | - if we monkey patch the instance attribute, it is not going to change the class template, instead 12 | it affects only the instance we've created 13 | """ 14 | 15 | 16 | class Student: 17 | def __init__(self, name): 18 | self.name = name 19 | 20 | Student.grade = 1 # Monkey Patching Class attribute 21 | Student.major = 'science' 22 | john = Student("John Doe") 23 | 24 | print(john.name) 25 | john.roll = 5 # Monkey Patching instance attribute 26 | print(john.roll) 27 | 28 | 29 | def display_name(self): 30 | print(self.name) 31 | 32 | 33 | def display_major(self): 34 | print(self.major) 35 | 36 | 37 | Student.display_name = display_name # Monkey patching class attribute 38 | Student.display_major = display_major 39 | 40 | john.display_name() 41 | 42 | 43 | jane = Student("Jane") 44 | print(jane.roll) 45 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0701.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a python class with following properties: 5 | 6 | 1. private class attribute 7 | 2. public class attribute 8 | 3. instance attribute 9 | 4. initializer method 10 | 5. string representation method [__str__()] 11 | """ 12 | 13 | # Your solution here 14 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0702.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a class named Person and add the following attributes and methods: 5 | - name: Instance attribute 6 | - age: Instance attribute 7 | - gender: Instance attribute 8 | - weight: Class attribute 9 | 10 | - year_of_birth(): 11 | Returns the year by subtracting the age from the current year. 12 | 13 | - get_pronouns(): 14 | Returns list of ['he', 'his', 'him'] or ['she', 'her', 'hers'] by checking the gender 15 | 16 | the initializer method should take name, age, and gender 17 | """ 18 | 19 | import datetime 20 | 21 | current_year = datetime.date.today().year 22 | 23 | 24 | # answer 25 | class Person: 26 | pass # Write your code here 27 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0703.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a class Employee that contains different attributes. 5 | - id 6 | - first_name 7 | - last_name 8 | - project 9 | - department 10 | - salary 11 | 12 | Make attributes project, department, and salary as private and use getter and setter methods to get and 13 | set respective values 14 | 15 | id should be private and can only be initialized when employee instance is created 16 | 17 | first_name and last_name should be initialized with constructor and can be changed any time 18 | """ 19 | 20 | # answer 21 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0704.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create 3 different classes Length, Weight, and Time 5 | 6 | Add respective attributes to store values. 7 | Add static methods for conversion of different units. 8 | 9 | Example: 10 | Length: cm to inches, kilometer to miles, etc 11 | Weight: KG to pounds, gram to Ounces, etc. 12 | Time: seconds to hours, milliseconds to seconds 13 | 14 | """ 15 | 16 | # answer 17 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0705.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | create a classes Rectangle, Circle and Box and add respective attributes eg.: radius, length, width, height 5 | 6 | for Rectangle, create methods to find out: 7 | perimeter 8 | area 9 | length of diagonal 10 | 11 | for Circle, create methods to find out: 12 | circumference 13 | area 14 | diameter 15 | 16 | for Box, create methods to find out: 17 | surface area 18 | volume 19 | """ 20 | 21 | # answer 22 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0706.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create 2 different classes Major and Subject 5 | 6 | create an instance of Major and add different subjects to the 'subject' attribute as a set of subjects 7 | 8 | Create another instance for Major and add subjects to the instance 9 | 10 | add operator overloading to add different methods so that we can create another major. 11 | 12 | Example we have majors science, commerce 13 | 14 | if we call 15 | 16 | new_major = science + commerce 17 | 18 | it should be able to return the new instance of Major with all subjects inside of them 19 | 20 | Note: 21 | you can try other operator overloading options too. 22 | """ 23 | 24 | # answer 25 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz/q0708.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a class Vehicle add some attributes and methods to it 5 | - name 6 | - brand 7 | - wheels_count 8 | - engine_type 9 | - braking_system 10 | 11 | 12 | Create a child class HeavyVehicle and inherit all the attributes from the parent class Vehicle 13 | - change the wheels_count from 4 to 6 in the initializer or accept the value while instantiating 14 | - add more instance attributes like max_load, mileage, etc. 15 | 16 | Create a child class Bike and inherit all the attributes from the parent class Vehicle 17 | - change the wheels_count from 4 to 2 in the initializer 18 | - add setter or getter methods or property to add bike number, and owner name 19 | - try adding property instead of setter or getter for passenger/ pillion attribute 20 | 21 | create different instances of Vehicle, HeavyVehicle, and Bike and check whether each other are subclasses 22 | and instances of different classes or not. 23 | 24 | """ 25 | 26 | # answer 27 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0701.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a python class with following properties: 5 | 6 | 1. private class attribute 7 | 2. public class attribute 8 | 3. instance attribute 9 | 4. initializer method 10 | 5. string representation method [__str__()] 11 | """ 12 | 13 | # This is the example solution you can create on your own style 14 | class MyClass: 15 | var_1 = 5 # 2 16 | __var_2 = 10 # 1 17 | 18 | def __init__(self): # 4 19 | self.var_3 = "Hello" # 3 20 | 21 | def __str__(self) -> str: # 5 22 | return f"MyClass with var3: {self.var_3}" 23 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0702.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a class named Person and add the following attributes and methods: 5 | - name: Instance attribute 6 | - age: Instance attribute 7 | - gender: Instance attribute 8 | - weight: Class attribute 9 | 10 | - year_of_birth(): 11 | Returns the year by subtracting the age from the current year. 12 | 13 | - get_pronouns(): 14 | Returns list of ['he', 'his', 'him'] or ['she', 'her', 'hers'] by checking the gender 15 | 16 | the initializer method should take name, age, and gender 17 | """ 18 | 19 | import datetime 20 | from ntpath import join 21 | 22 | # answer 23 | MALE = 'male' 24 | FEMLALE = 'female' 25 | 26 | class Person: 27 | 28 | weight = 0 29 | 30 | def __init__(self, name: str, age: int, gender: str) -> None: 31 | self.name = name 32 | self.age = age 33 | self.gender = gender 34 | 35 | def year_of_birth(self): 36 | return datetime.date.today().year - self.age 37 | 38 | def get_pronouns(self): 39 | return ['he', 'his', 'him'] if self.gender == MALE else ['she', 'her', 'hers'] 40 | # you can also accept other, unspecified option to gender for practice 41 | 42 | 43 | 44 | # Test 45 | john = Person("John Doe", 20, MALE) 46 | 47 | print(john.year_of_birth()) 48 | print(john.get_pronouns()) 49 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0703.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a class Employee that contains different attributes. 5 | - id 6 | - first_name 7 | - last_name 8 | - project 9 | - department 10 | - salary 11 | 12 | Make attributes project, department, and salary as private and use getter and setter methods to get and 13 | set respective values 14 | 15 | id should be private and can only be initialized when employee instance is created 16 | 17 | first_name and last_name should be initialized with constructor and can be changed any time 18 | """ 19 | 20 | # answer 21 | 22 | 23 | class Employee: 24 | 25 | def __init__(self, id, first_name, last_name) -> None: 26 | self.__id = id 27 | self.first_name = first_name 28 | self.last_name = last_name 29 | 30 | self.__project = '' 31 | self.__department = '' 32 | self.__salary = '' 33 | 34 | def get_project(self): 35 | return self.__project 36 | 37 | def get_department(self): 38 | return self.__department 39 | 40 | def get_salary(self): 41 | return self.__salary 42 | 43 | def set_project(self, project: str): 44 | self.__project = project 45 | 46 | def set_department(self, department: str): 47 | self.__department = department 48 | 49 | def set_salary(self, salary: int): 50 | self.__salary = salary 51 | 52 | 53 | # Test 54 | john = Employee(1, "John", "Doe") 55 | 56 | john.set_project("ABC management system") 57 | 58 | john.set_salary(5000) 59 | john.set_department("Software") 60 | 61 | 62 | print(john.get_department()) 63 | print(john.get_salary()) 64 | print(john.get_project()) 65 | 66 | john.last_name = "Lennon" 67 | 68 | print(john.first_name, john.last_name) 69 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0704.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create 3 different classes Length, Weight, and Time 5 | 6 | Add respective attributes to store values. 7 | Add static methods for conversion of different units. 8 | 9 | Example: 10 | Length: cm to inches, kilometer to miles, etc 11 | Weight: KG to pounds, gram to Ounces, etc. 12 | Time: seconds to hours, milliseconds to seconds 13 | 14 | """ 15 | 16 | # answer 17 | 18 | class Length: 19 | 20 | @staticmethod 21 | def meter_to_feet(meter: float): 22 | return meter * 3.28084 23 | 24 | @staticmethod 25 | def feet_to_meter(feet: float): 26 | return feet / 3.28084 27 | 28 | class Weight: 29 | 30 | @staticmethod 31 | def kg_to_pound(kg: float): 32 | return kg * 2.20462 33 | 34 | @staticmethod 35 | def pound_to_kg(pound: float): 36 | return pound / 2.20462 37 | 38 | class Time: 39 | @staticmethod 40 | def second_to_hour(second: float): 41 | return second/3600 42 | 43 | @staticmethod 44 | def hour_to_second(hour: float): 45 | return hour *3600 46 | 47 | 48 | # tests 49 | print(Length.feet_to_meter(5.9)) 50 | print(Weight.kg_to_pound(5.9)) 51 | print(Time.second_to_hour(7200)) 52 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0705.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | create a classes Rectangle, Circle and Box and add respective attributes eg.: radius, length, width, height 5 | 6 | for Rectangle, create methods to find out: 7 | perimeter 8 | area 9 | length of diagonal 10 | 11 | for Circle, create methods to find out: 12 | circumference 13 | area 14 | diameter 15 | 16 | for Box, create methods to find out: 17 | surface area 18 | volume 19 | """ 20 | 21 | # answer 22 | 23 | class Rectangle: 24 | width = 0 25 | height = 0 26 | 27 | def __init__(self, width, height) -> None: 28 | self.width = width 29 | self.height = height 30 | 31 | def perimeter(self): 32 | return 2 * (self.width + self.height) 33 | 34 | def area(self): 35 | return self.width * self.height 36 | 37 | def diagonal_length(self): 38 | return (self.width**2 * self.height**2)**0.5 39 | 40 | 41 | class Circle: 42 | __PI = 3.1415 43 | 44 | def __init__(self, radius) -> None: 45 | self.radius = radius 46 | 47 | def area(self): 48 | return self.__PI * self.radius**2 49 | 50 | def diameter(self): 51 | return 2 * self.radius 52 | 53 | def circumference(self): 54 | return self.diameter() * self.__PI 55 | # return 2 * self.radius * self.__PI # This solution also works 56 | 57 | 58 | class Box: 59 | def __init__(self, length, width, height) -> None: 60 | self.length = length 61 | self.width = width 62 | self.height = height 63 | 64 | def surface_area(self): 65 | return 2 * (self.length * self.width + self.width * self.height + self.length * self.height) 66 | 67 | def volume(self): 68 | return self.length * self.width + self.height 69 | 70 | 71 | # tests 72 | 73 | rect1 = Rectangle(5, 6) 74 | print('Area of rectangle:', rect1.area()) 75 | print('Perimeter of rectangle:', rect1.perimeter()) 76 | print('Length of diagonal:', rect1.diagonal_length()) 77 | print() 78 | 79 | circle = Circle(10) 80 | print('Area of Circle:', circle.area()) 81 | print('Perimeter of Circle:', circle.circumference()) 82 | print('Diameter of Circle:', circle.diameter()) 83 | print() 84 | 85 | box1 = Box(5, 6, 7) 86 | print('Surface Area of Box:', box1.surface_area()) 87 | print('Volume of the box:', box1.volume()) 88 | print() 89 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0706.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create 2 different classes Major and Subject 5 | 6 | create an instance of Major and add different subjects to the 'subject' attribute as a set of subjects 7 | 8 | Create another instance for Major and add subjects to the instance 9 | 10 | add operator overloading to add different methods so that we can create another major. 11 | 12 | Example we have majors science, commerce 13 | 14 | if we call 15 | 16 | new_major = science + commerce 17 | 18 | it should be able to return the new instance of Major with all subjects inside of them 19 | 20 | Note: 21 | you can try other operator overloading options too. 22 | """ 23 | 24 | # answer 25 | 26 | 27 | class Subject: 28 | 29 | def __init__(self, name) -> None: 30 | self.name = name 31 | 32 | def __str__(self) -> str: 33 | return f"subject: {self.name}" 34 | 35 | def __repr__(self) -> str: 36 | return self.__str__() 37 | 38 | 39 | class Major: 40 | name = '' 41 | 42 | def __init__(self, name) -> None: 43 | self.name = name 44 | self.subjects = [] 45 | 46 | def __add__(self, other: "Major"): 47 | sub = Major(f'{self.name} + {other.name}') 48 | sub.subjects = [*self.subjects, *other.subjects] 49 | return sub 50 | 51 | 52 | # Tests 53 | 54 | sc = Major("Science") 55 | sc.subjects.append(Subject('Physics')) 56 | sc.subjects.append(Subject('Chemistry')) 57 | 58 | com = Major("Commerce") 59 | com.subjects.append(Subject('Business Mathematics')) 60 | com.subjects.append(Subject('Accounting')) 61 | 62 | new_major = sc + com 63 | 64 | print(new_major.name) 65 | print(new_major.subjects) 66 | -------------------------------------------------------------------------------- /notes/c07_oop/quiz_solution/q0708.py: -------------------------------------------------------------------------------- 1 | """ 2 | © https://sudipghimire.com.np 3 | 4 | Create a class Vehicle add some attributes and methods to it 5 | - name 6 | - brand 7 | - wheels_count 8 | - engine_type 9 | - braking_system 10 | 11 | 12 | Create a child class HeavyVehicle and inherit all the attributes from the parent class Vehicle 13 | - change the wheels_count from 4 to 6 in the initializer or accept the value while instantiating 14 | - add more instance attributes like max_load, mileage, etc. 15 | 16 | Create a child class Bike and inherit all the attributes from the parent class Vehicle 17 | - change the wheels_count from 4 to 2 in the initializer 18 | - add setter or getter methods or property to add bike number, and owner name 19 | - try adding property instead of setter or getter for passenger/ pillion attribute 20 | 21 | create different instances of Vehicle, HeavyVehicle, and Bike and check whether each other are subclasses 22 | and instances of different classes or not. 23 | 24 | """ 25 | 26 | # answer 27 | class Vehicle: 28 | wheels_count = 4 29 | engine_type = 'Diesel Engine' 30 | braking_system = 'ABS' 31 | def __init__(self, name, brand): 32 | pass 33 | 34 | 35 | class HeavyVehicle(Vehicle): 36 | max_load = 4000 37 | milage = 40 38 | def __init__(self, name, brand): 39 | self.wheels_count = 6 40 | super().__init__(name, brand) 41 | 42 | class Bike(Vehicle): 43 | def __init__(self, name, brand): 44 | self.wheels_count = 2 45 | super().__init__(name, brand) 46 | self.__reg_no = '' 47 | self.__owner = '' 48 | 49 | @property 50 | def reg_no(self): 51 | return self.__reg_no 52 | 53 | @reg_no.setter 54 | def reg_no(self, value): 55 | self.__reg_no = value 56 | 57 | @property 58 | def owner(self): 59 | return self.__owner 60 | 61 | @owner.setter 62 | def owner(self, value: str): 63 | self.__owner = value 64 | 65 | 66 | b1 = Bike("Apache RTR 200", "TVS") 67 | b1.reg_no = 11223344 68 | b1.owner = "John Doe" 69 | 70 | print(b1.reg_no) 71 | print(b1.owner) 72 | 73 | 74 | print(issubclass(Bike, Vehicle)) 75 | 76 | print(isinstance(b1, Vehicle)) 77 | print(isinstance(b1, HeavyVehicle)) 78 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Chapter 8 Modules and Packages 3 | 4 | > **Note**: 5 | > 6 | > please refer to the repository 7 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 8 | > exercises and projects related to this chapter. 9 | > 10 | > 11 | **Table of contents**: 12 | 13 | - [8.1. Python Modules](chapter-8.1-modules.md) 14 | - [8.2. Packages](chapter-8.2-packages.md) 15 | - [8.3. `datetime` Module](chapter-8.3-datetime.md) 16 | - [8.4. `random` Module](chapter-8.4-random.md) 17 | - [8.5. `json` Module](chapter-8.5-json.md) 18 | - [8.6. `math` Module](chapter-8.6-math.md) 19 | - [8.7. `complex` and `cmath` Module](chapter-8.7-complex-and-cmath.md) 20 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/chapter-8.1-modules.md: -------------------------------------------------------------------------------- 1 | # Chapter 8.1 Python Modules 2 | 3 | - Module is a collection of different classes, functions and variables 4 | - A module is nothing but a single file which can be imported into another file. 5 | 6 | ## basic structure of a module 7 | 8 | ```shell 9 | 📁 working_dir/ 10 | |-- 📄 module.py 11 | | 📦 variables 12 | | 📦 functions 13 | | 📦 classes 14 | | 15 | |-- 📄 main.py 16 | 📦 imports from module.py 17 | 📦 extra logic/code 18 | ``` 19 | 20 | File 1: `school.py` 21 | 22 | ```python 23 | class Student: 24 | def __init__(self, name, roll): 25 | self.name = name 26 | self.roll = roll 27 | ``` 28 | 29 | File 2: `main.py` 30 | 31 | ```python 32 | from school import Student 33 | john = Student('John Doe', 1) 34 | jane = Student('Jane Doe', 1) 35 | ``` 36 | 37 | ## Importing the whole module 38 | 39 | if `animal.py` is the filename for the module, the name of the module would be `animal`. so we can import the whole module by the following command. 40 | 41 | ```python 42 | import animal 43 | ``` 44 | 45 | Importing the whole module imports everything that is defined, imported, and initialized inside the module. 46 | 47 | ## Importing individual elements from the module 48 | 49 | we can use `from` keyword to import the individual component from the module. For example the module `animal` contains the class `DomesticAnimal`, we can import it using the following command. 50 | 51 | ```python 52 | from animal import DomesticAnimal 53 | # we can also import multiple elements in the single line using comma 54 | from animal import DomesticAnimal, WildAnimal 55 | 56 | # we can also use small brackets so that we can import in different lines 57 | 58 | from animal import ( 59 | Animal, 60 | DomesticAnimal, 61 | WildAnimal, 62 | ) 63 | ``` 64 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/chapter-8.2-packages.md: -------------------------------------------------------------------------------- 1 | # Chapter 8.2 Python Packages 2 | 3 | - A python package is a directory that contains multiple python files or modules 4 | - A package contains a special file `__init__.py` as the initializer file. 5 | - it might contain subdirectories or subpackages too. 6 | 7 | ## Basic Package directory structure 8 | 9 | ``` 10 | 📁 my_package/ 11 | |-- 📄 module_1.py 12 | |-- 📄 module_2.py 13 | |-- 📄 module_3.py 14 | |-- 📄 __init__.py 15 | ``` 16 | 17 | ## Basic Package structure with subpackage inside it 18 | 19 | ``` 20 | 📁 my_package/ 21 | |-- 📁 pkg_1/ 22 | | |-- 📄 __init__.py 23 | | |-- 📄 pkg1_module_1.py 24 | | |-- 📄 pkg1_module_2.py 25 | | 26 | |-- 📄 __init__.py 27 | |-- 📄 module_1.py 28 | |-- 📄 module_2.py 29 | |-- 📄 module_3.py 30 | ``` 31 | 32 | ## The `__init__.py` file 33 | 34 | The `__init__.py` file is the initializer for the package that is capable of importing all the components inside of the modules of the package. 35 | 36 | It is capable of creating shortcuts so that user do not need to go deeper to import required components. To create a shortcut, we can simply import everything from the modules from the package. 37 | 38 | Eg: ** `__init__.py` of package: `my_package` 39 | 40 | ```python 41 | from .module_1 import Class1, Class2 42 | from .pkg_1.pkg1_module_1 import * 43 | ``` 44 | 45 | after importing all elements with the above command, we can replace the following command 46 | 47 | ```python 48 | from my_package.module_1 import Class1 49 | from my_package.pkg_1.module_1 import ABC 50 | 51 | ``` 52 | 53 | with the following command: 54 | 55 | ```python 56 | from my_package import Class1, ABC 57 | ``` 58 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/chapter-8.4-random.md: -------------------------------------------------------------------------------- 1 | # Chapter 8.4. The `random` Module 2 | 3 | **Table Of Contents**: 4 | 5 | - [Chapter 8.4. The `random` Module](#chapter-84-the-random-module) 6 | - [Usage](#usage) 7 | - [Printing out a random number from a range 1 to 6](#printing-out-a-random-number-from-a-range-1-to-6) 8 | - [Printing out 10 random numbers from a range 1 to 6](#printing-out-10-random-numbers-from-a-range-1-to-6) 9 | - [Printing out a random character from a string](#printing-out-a-random-character-from-a-string) 10 | - [Printing out 5 random characters as a list from a string](#printing-out-5-random-characters-as-a-list-from-a-string) 11 | - [Printing out a random password string with 8 characters](#printing-out-a-random-password-string-with-8-characters) 12 | 13 | Reference: 14 | 15 | - `random` module implements pseudo-random generators for various distributions. 16 | 17 | **Some of the functions are as follows:** 18 | 19 | - `random.seed()` 20 | 21 | It initializes the random number generator. 22 | 23 | - `random.randrange()` 24 | 25 | It accepts 3 parameters `start`, `stop`, and `step` which is similar to `range()` function. It returns the random from the eligible numbers from the range function. 26 | 27 | - `random.randint()` 28 | 29 | It is similar to `randrange()` method but the `stop` number is inclusive 30 | 31 | - `random.choice()` 32 | 33 | It chooses a character from the string provided. 34 | 35 | - `random.choices()` 36 | 37 | It chooses `k` number of characters as a list from the string provided. 38 | 39 | ## Usage 40 | 41 | ### Printing out a random number from a range 1 to 6 42 | 43 | ```python 44 | import random 45 | 46 | print(random.randint(1,6)) 47 | ``` 48 | 49 | ### Printing out 10 random numbers from a range 1 to 6 50 | 51 | ```python 52 | import random 53 | 54 | for _ in range(10): 55 | print(random.randint(1,6)) 56 | ``` 57 | 58 | ### Printing out a random character from a string 59 | 60 | ```python 61 | import random 62 | 63 | options = "12345ABCDEabcde" 64 | print(random.choice(options)) 65 | ``` 66 | 67 | ### Printing out 5 random characters as a list from a string 68 | 69 | ```python 70 | import random 71 | 72 | options = "12345ABCDEabcde" 73 | print(random.choices(options, k=5)) 74 | ``` 75 | 76 | ### Printing out a random password string with 8 characters 77 | 78 | ```python 79 | import random 80 | 81 | options = "12345ABCDEabcde" 82 | print(''.join(random.choices(options, k=8))) 83 | ``` 84 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/code/animal.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module Animal 3 | 4 | This is a sample module animal which can be imported into different files. 5 | The module contains different constants, variables, functions and classes. 6 | """ 7 | DOMESTIC = 'domestic' 8 | WILD = 'wild' 9 | age = 20 10 | 11 | 12 | def abc(): 13 | print("Did Something") 14 | 15 | 16 | class Animal: 17 | name = "" 18 | paws = 0 19 | word = "" 20 | 21 | def __init__(self, name, word): 22 | self.name = name 23 | self.word = word 24 | 25 | def __str__(self): 26 | return (f"Instance of Animal named {self.name}") 27 | 28 | def speak(self): 29 | print(f'I am {self.name} and I speak with {self.word}s.') 30 | 31 | def rename(self, new_name): 32 | self.name = new_name 33 | 34 | 35 | class DomesticAnimal(Animal): 36 | purpose_of_domestication = '' 37 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/code/c0805_json_module.py: -------------------------------------------------------------------------------- 1 | """ 2 | JSON 3 | 4 | - JavaScript Object Notation 5 | - Used for data exchange throughout machines and platforms. 6 | - It is used as data interchange format for REST APIs 7 | 8 | - JSON contains Key Value Pairs. 9 | 10 | - Data Types in json 11 | - number (9, 9.9) 12 | - string ("str1") 13 | - boolean (true, false) 14 | - null (Python Equivalent is None) 15 | 16 | - Array [1,2] (Python Equivalents are List, tuple, set) 17 | - Object (Python Equivalent is Dictionary) 18 | {"key": "value"} 19 | 20 | { 21 | "key": { 22 | "key": { 23 | "key1": value1 24 | "key2": value2 25 | "key3": [1,2] 26 | "key3": [ 27 | {"key": "Value"}, 28 | {"key": "Value"}, 29 | {"key": "Value"} 30 | ] 31 | } 32 | } 33 | } 34 | 35 | """ 36 | 37 | 38 | # JSON Methods in python 39 | 40 | """ 41 | dump - it converts python dictionary to json file 42 | load - it retrieves all values from a json file and loads into a dictionary 43 | 44 | dumps - it is similar to dump() method, but, it just returns string instead of storing in a file 45 | loads - it is similar to load() method but, it converts json formatted string into a dictionary 46 | """ 47 | 48 | 49 | # To use json we have to import the json module 50 | import json 51 | from json.decoder import JSONDecodeError 52 | 53 | # Dumping a dictionary to a file 54 | person = { 55 | "name": "John Doe", 56 | "age": 20, 57 | "married": False, 58 | "occupation": None, 59 | "father":{ 60 | "name": "John Doe Sr.", 61 | "age": 50, 62 | "childrens":("Jon", "Jane",), 63 | }, 64 | 5: 10.5, 65 | } 66 | 67 | with open('person.json', 'w') as f: 68 | json.dump(person, f) 69 | 70 | 71 | 72 | # loading a json object from a file 73 | 74 | with open('response.json', 'r') as f: 75 | try: 76 | response = json.load(f) 77 | 78 | print(type(response)) 79 | except JSONDecodeError as e: 80 | print("The JSON file you provided is not in a correct format") 81 | 82 | 83 | # dumping a dictionary into json string 84 | 85 | 86 | value = json.dumps(person) 87 | 88 | print(value) 89 | 90 | 91 | json_string = '{"name": "Jon Doe"}' 92 | 93 | 94 | dict2 = json.loads(json_string) 95 | 96 | print(dict2["name"]) 97 | 98 | 99 | # dumping a file with indented key value pairs 100 | 101 | with open('person.json', 'w') as f: 102 | json.dump(person, f, indent=4) 103 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/code/c08_01_module_intro.py: -------------------------------------------------------------------------------- 1 | """ 2 | importing the whole module 3 | 4 | """ 5 | # import animal 6 | 7 | # importing the individual element 8 | 9 | from animal import DomesticAnimal 10 | 11 | # we can also import multiple elements in the single line using commaa 12 | from animal import Animal, DomesticAnimal 13 | 14 | # we can also use small brackets so that we can import in different lines 15 | 16 | from animal import ( 17 | Animal, 18 | DomesticAnimal, 19 | WILD, 20 | DOMESTIC, 21 | abc, 22 | ) 23 | 24 | abc() 25 | elephant = Animal("John", "trumpet") 26 | 27 | # elephant.name = "sdsds" 28 | # elephant.paws = 34 29 | # elephant.word = "sds" 30 | elephant.speak() 31 | elephant.rename("Jane") 32 | elephant.speak() 33 | 34 | cow = DomesticAnimal("My Cow", "moo") 35 | 36 | print(DOMESTIC) 37 | print(WILD) 38 | -------------------------------------------------------------------------------- /notes/c08_modules_packages/quiz/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 8 Modules and Packages Quiz 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | > 9 | -------------------------------------------------------------------------------- /notes/c09_file/code/.gitignore: -------------------------------------------------------------------------------- 1 | **/*.txt 2 | -------------------------------------------------------------------------------- /notes/c09_file/code/reading_file.py: -------------------------------------------------------------------------------- 1 | # FIXME: please use this path if you run this script from current directory 2 | # file_name = "file_1.txt" 3 | 4 | # ! Please create a new file named `file1.txt` in this directory to open the file 5 | 6 | # Please use this path if you run this script fromthe project directory 7 | file_name = "c09_file/code/file1.txt" 8 | 9 | 10 | # ------------------------------------------------------------------------------ 11 | # opening the file in reading mode 12 | file = open(file_name, "r") 13 | 14 | # reading the whole content 15 | content = file.read() 16 | print(content) 17 | 18 | # ? output: 19 | # This is Line 1 20 | # This is Line 2 21 | # This is Line 3 22 | 23 | 24 | # we need to close the file once we don't need anymore 25 | file.close() 26 | 27 | # ------------------------------------------------------------------------------ 28 | # reading line by line 29 | 30 | file = open(file_name, "r") 31 | line = 0 32 | while content := (file.readline()).strip(): 33 | print(f"{line:>3} | {content}") 34 | line += 1 35 | print(content) 36 | 37 | # ? output: 38 | # 0 | This is Line 1 39 | # 1 | This is Line 2 40 | # 2 | This is Line 3 41 | 42 | file.close() 43 | -------------------------------------------------------------------------------- /notes/c09_file/code/reading_file_with.py: -------------------------------------------------------------------------------- 1 | # FIXME: please use this path if you run this script from current directory 2 | # file_name = "file_1.txt" 3 | 4 | # ! Please create a new file named `file1.txt` in this directory to open the file 5 | 6 | # Please use this path if you run this script fromthe project directory 7 | file_name = "c09_file/code/file1.txt" 8 | 9 | # ------------------------------------------------------------------------------ 10 | # reading line by line 11 | 12 | with open(file_name, "r") as file: 13 | line = 0 14 | while content := (file.readline()).strip(): 15 | print(f"{line:>3} | {content}") 16 | line += 1 17 | print(content) 18 | 19 | # ? output: 20 | # 0 | This is Line 1 21 | # 1 | This is Line 2 22 | # 2 | This is Line 3 23 | 24 | # closing the file is not necessary when using `with` statement 25 | # file.close() 26 | -------------------------------------------------------------------------------- /notes/c09_file/code/updating_file.py: -------------------------------------------------------------------------------- 1 | # FIXME: please use this path if you run this script from current directory 2 | # file_name = "file_1.txt" 3 | 4 | """ 5 | Please create a new file named `file1.txt` in this directory with content 6 | as follows: 7 | 8 | c09_file/code/file1.txt 9 | 10 | This is Line 1 11 | This is Line 2 12 | This is Line 3 13 | 14 | """ 15 | 16 | # Please use this path if you run this script from the project directory 17 | file_name = "c09_file/code/file1.txt" 18 | 19 | # ------------------------------------------------------------------------------ 20 | # opening the file in append mode 21 | # using with statement 22 | with open(file_name, "a") as file: 23 | file.write("This is Line 4") 24 | 25 | """ 26 | After executing this script, you should be able to see the appended content in 27 | the file which will look like below: 28 | 29 | This is Line 1 30 | This is Line 2 31 | This is Line 3 32 | This is Line 4 33 | 34 | """ 35 | -------------------------------------------------------------------------------- /notes/c09_file/code/writing_file.py: -------------------------------------------------------------------------------- 1 | # FIXME: please use this path if you run this script from current directory 2 | # file_name = "file_1.txt" 3 | 4 | # ! Please create a new file named `file1.txt` in this directory to open the file 5 | 6 | # Please use this path if you run this script from the project directory 7 | file_name = "c09_file/code/file2.txt" 8 | binary_file_name = "c09_file/code/binary_file.txt" 9 | 10 | content = """This is a content to the written file. 11 | - This is a point 1 12 | - This is a point 2 13 | """ 14 | 15 | binary_content = ( 16 | """{ 17 | "name": "Tony Stark 🙂", 18 | "affiliation": "Avengers", 19 | "age": 40, 20 | } 21 | """ 22 | ).encode("utf-8") 23 | 24 | # ------------------------------------------------------------------------------ 25 | # opening the file in writing mode 26 | file = open(file_name, "w") 27 | 28 | file.write(content) 29 | 30 | # we need to close the file once we don't need anymore 31 | file.close() 32 | 33 | # ------------------------------------------------------------------------------ 34 | # using with statement 35 | with open(file_name, "w") as file: 36 | file.write(content) 37 | 38 | # ------------------------------------------------------------------------------ 39 | # writing a binary file 40 | with open(binary_file_name, "wb") as file: 41 | file.write(binary_content) 42 | -------------------------------------------------------------------------------- /notes/c09_file/quiz/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9 File Quiz 2 | https://sudipghimire.com.np 3 | 4 | Please read the note carefully and try to solve the problem below: 5 | 6 | > **Note**: 7 | > 8 | > please refer to the repository 9 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 10 | > exercises and projects related to this chapter. 11 | > 12 | -------------------------------------------------------------------------------- /notes/c10_exceptions/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 10: Exceptions and Exception Handling 2 | 3 | **Table of Contents**: 4 | 5 | - [10.1. Exceptions](chapter-10.1-exceptions.md) 6 | - [10.2. Exception Handling in Python](chapter-10.2-exception-handling.md) 7 | - [10.3. Creating Custom Exceptions](chapter-10.3-custom-exceptions.md) 8 | -------------------------------------------------------------------------------- /notes/c10_exceptions/chapter-10.3-custom-exceptions.md: -------------------------------------------------------------------------------- 1 | # Creating custom Exceptions 2 | 3 | We can create a new `Exception` by inheriting the `BaseException` class. The 4 | following is an example of a CustomException: 5 | 6 | ```py 7 | class MyCustomException(Exception): 8 | pass 9 | ``` 10 | 11 | ## Example1: AgeError 12 | 13 | We can create a custom age error that can raise the exception if a user enters 14 | the invalid range of an age of a person. 15 | 16 | ```py 17 | class AgeError(Exception): 18 | min_age = 0 19 | max_age = 100 20 | 21 | def __init__(self, age, *args): 22 | super().__init__(*args) 23 | self.age = age 24 | 25 | def __str__(self): 26 | return f'The age {self.age} is not in between {self.min_age} and {self.max_age}' 27 | ``` 28 | 29 | ## Example2: `LengthError` 30 | 31 | The example below explains how a custom exception can be raised using the 32 | object-oriented oriented approach: 33 | 34 | ```py 35 | class LengthError(Exception): 36 | def __init__(self, value: int, *args: object) -> None: 37 | self.value = value 38 | super().__init__(*args) 39 | 40 | def __str__(self): 41 | return f'The length {self.value} is not possible' 42 | 43 | 44 | class Length: 45 | def __init__(self, value): 46 | if value < 0: 47 | raise LengthError(value) 48 | self.value = value 49 | 50 | 51 | l1 = Length(5) 52 | l2 = Length(-5) # LengthError: The length -5 is not possible 53 | ``` 54 | -------------------------------------------------------------------------------- /notes/c10_exceptions/code/c1001_custom_exception.py: -------------------------------------------------------------------------------- 1 | class AgeError(Exception): 2 | min_age = 0 3 | max_age = 100 4 | 5 | def __init__(self, age, *args): 6 | super().__init__(*args) 7 | self.age = age 8 | 9 | def __str__(self): 10 | return f'The age {self.age} is not in between {self.min_age} and {self.max_age}' 11 | 12 | x = 101 13 | if not x <=0 <=100: 14 | raise AgeError(x) 15 | 16 | # AgeError: The age 101 is not in between 0 and 100 17 | print(x) -------------------------------------------------------------------------------- /notes/c10_exceptions/code/c1002_lentth_exception.py: -------------------------------------------------------------------------------- 1 | class LengthError(Exception): 2 | def __init__(self, value: int, *args: object) -> None: 3 | self.value = value 4 | super().__init__(*args) 5 | 6 | def __str__(self): 7 | return(f'The length {self.value} is not possible') 8 | 9 | 10 | class Length: 11 | def __init__(self, value): 12 | if value < 0: 13 | raise LengthError(value) 14 | self.value = value 15 | 16 | 17 | l1 = Length(5) 18 | l2 = Length(-5) # LengthError: The length -5 is not possible 19 | -------------------------------------------------------------------------------- /notes/c10_exceptions/quiz/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 10 Exceptions Quiz 2 | 3 | Please read the note carefully and try to solve the problem below: 4 | 5 | > **Note**: 6 | > 7 | > please refer to the repository 8 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 9 | > exercises and projects related to this and following chapters. 10 | > 11 | -------------------------------------------------------------------------------- /notes/c11_pip/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 11 Python Package Management 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | 9 | **Table of contents**: 10 | 11 | - [Introduction to Semantic Versioning(semver)](chapter-11.1-semver.md) 12 | - [PIP package manager](chapter-11.2-pip.md) 13 | -------------------------------------------------------------------------------- /notes/c11_pip/chapter-11.1-semver.md: -------------------------------------------------------------------------------- 1 | # 11.1. Semantic Versioning system 2 | 3 | Ref: https://semver.org/ 4 | 5 | - Semantic Versioning system uses major, minor, and patch release labels to track the current release of the software. 6 | 7 | Eg: Django version `3.2.10` 8 | 9 | - The `major` release is `3` 10 | - The `minor` release is `2` 11 | - The `patch` release is `10` 12 | 13 | Sometimes, additional labels for pre-releases are also tracked using `dev`, `alpha`, `beta`, `release candidates` 14 | or `rc`, and `build_id`. 15 | 16 | Some examples of pre-release versions are as follows: 17 | 18 | - `3.2.10-a1` 19 | - `3.2.10-alpha.1` 20 | - `3.2.10-beta` 21 | - `3.2.10-rc.1` 22 | - `3.2.10-rc.1+41A39F2`, etc. 23 | 24 | ## Major Release 25 | 26 | - This release introduce new features 27 | - It is going to break your existing code with previous versions 28 | - With every major release, the first release number is going to be increased by 1 and other release numbers would be 0 29 | - `1.2.10` -> `2.0.0` 30 | 31 | ## Minor Release 32 | 33 | - They introduce some new features, but not with heavy changes. 34 | - The code might break in this case also, but can be fixed with small refactorings. (Backward Compatible) 35 | - In this release, features just gets depreciated, but do not get removed. 36 | - With this release second release number is going to be increased by 1 and the last would be 0 37 | - `11.2.10` -> `11.3.0` 38 | 39 | ## Patch Release / Bugfix Release 40 | 41 | - This release is not going to introduce any new features 42 | - The older code is not going to break in this release 43 | - Every changes is going to be internal changes or logical changes. 44 | - With this release the last release number is going to be increased by 1 45 | - `11.2.10` -> `11.2.11` 46 | 47 | The tree below shows an example of the release cycle of a software. 48 | 49 | ``` 50 | v0.1.0 51 | v0.2.0 52 | ... 53 | v0.9.0 54 | v0.10.0 55 | ... 56 | v1.0.0-a1 57 | v1.0.0-a2 58 | ... 59 | v1.0.0-b1 60 | v1.0.0-b2 61 | ... 62 | v1.0.0-rc.1 63 | v1.0.0-rc.2 64 | ... 65 | v1.0.0 (Major release) 66 | v1.0.1 (patch / bugfix) 67 | v1.0.2 68 | ... 69 | 70 | v1.1.0 (Minor Release) 71 | v1.1.1 (patch / bugfix) 72 | v1.1.2 73 | ... 74 | 75 | v1.2.0 (Minor Release) 76 | ... 77 | 78 | v2.0.0 (Major Release) 79 | ``` 80 | -------------------------------------------------------------------------------- /notes/c12_virtual_environment/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 12 Virtual environments 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | 9 | **Table of Contents** 10 | 11 | - [Introduction to Virtual environments](chapter-12.1-virtual-environment-intro.md) 12 | - [VENV and its usage](chapter-12.2-venv.md) 13 | - [PIPENV and its usage](chapter-12.3-pipenv.md) 14 | - [Poetry package manager](chapter-12.4-poetry.md) 15 | -------------------------------------------------------------------------------- /notes/c12_virtual_environment/chapter-12.1-virtual-environment-intro.md: -------------------------------------------------------------------------------- 1 | # 12.1. Introduction to Virtual environments 2 | 3 | Let us consider that we have to work simultaneously on 2 different projects with different package dependencies as follows 4 | 5 | ### **Base Python** 6 | - `django==4.0.0` 7 | 8 | ### **Project 1** 9 | - `django==2.2` 10 | - `pillow==7.0` 11 | - `numpy==1.0.0` 12 | 13 | 14 | ### **Project 2** 15 | - `django==3.2.10` 16 | - `pillow==9.1.0` 17 | - `numpy==1.5.0` 18 | 19 | Suppose we have base python `3.9` installed and different requirements for different projects. Switching back and forth between the projects requires installing and uninstalling different packages which is impractical in daily use. 20 | 21 | 22 | To avoid frequent installation and uninstalling of packages, we can use virtual environments. 23 | 24 | 25 | A Virtual Environment is a tool that keeps dependencies required by different projects in separate places. 26 | 27 | 28 | Followings are common virtual environments: 29 | 30 | - `venv`, `virtualenv` 31 | - `pipenv` 32 | - `direnv` 33 | - `pyenv` 34 | - `conda` 35 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 13: Advanced Functions 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | 9 | **Table of Contents** 10 | 11 | - [`groupby()` function](chapter-13.1-groupby.md) 12 | - [`sorted()` function](chapter-13.2-sorted.md) 13 | - [`filter()` function](chapter-13.3-filter.md) 14 | - [`map()` function](chapter-13.4-map.md) 15 | - [`reduce()` function](chapter-13.5-reduce.md) 16 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/chapter-13.1-groupby.md: -------------------------------------------------------------------------------- 1 | # 13.1. The `groupby()` Function 2 | 3 | 4 | The `groupby()` function is a function that returns consecutive keys and groups from the iterable. 5 | 6 | The `groupby()` generates an iterator that has key and value pair in which key is the value to be grouped by and value is the iterator again which contains grouped value. 7 | Source: https://docs.python.org/3/library/itertools.html#itertools.groupby 8 | 9 | **Example 1** 10 | ```python 11 | from itertools import groupby 12 | 13 | # example 1 14 | animals = ['Bear', 'Donkey', 'Cat', 'Dog', 'Camel', 'Elephant'] 15 | animals = sorted(animals) 16 | values = groupby(animals, lambda x: x[0]) 17 | grouped = {k: list(v) for k, v in values} 18 | print(grouped) 19 | ``` 20 | 21 | Output 22 | ``` 23 | {'B': ['Bear'], 'C': ['Camel', 'Cat'], 'D': ['Dog', 'Donkey'], 'E': ['Elephant']} 24 | ``` 25 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/chapter-13.2-sorted.md: -------------------------------------------------------------------------------- 1 | **Table of Contents** 2 | - [13.2. The `sorted()` Function](#132-the-sorted-function) 3 | - [Sorting the list in an ascending order](#sorting-the-list-in-an-ascending-order) 4 | - [Sorting the list in a descending order](#sorting-the-list-in-a-descending-order) 5 | - [Complex Sorting with the `sorted()` function](#complex-sorting-with-the-sorted-function) 6 | - [a `key` parameter in `sorted()` function](#a-key-parameter-in-sorted-function) 7 | # 13.2. The `sorted()` Function 8 | 9 | https://docs.python.org/3.9/howto/sorting.html 10 | 11 | Although there's a `list.sort()` method that modifies the list in place, we need to return the sorted list without modifying the original list. for that purpose, we can use `sorted()` function that builds a new sorted list from an iterable 12 | 13 | 14 | ## Sorting the list in an ascending order 15 | Sorting the list in an ascending order is done by just passing the list as an argument in the `sorted()` function. 16 | 17 | ```py 18 | print(sorted([1, 4, 7, 2, 9, 5, 3])) 19 | # [1, 2, 3, 4, 5, 7, 9] 20 | ``` 21 | 22 | **Note**: _We can sort the list using `list.sort()` method which is more efficient than `sorted()` method, but remember, `list.sort()` method modifies the list in place so we cannot recover the original list._ 23 | 24 | ## Sorting the list in a descending order 25 | Sorting in a descending order is as easy as sorting in an ascending order; we just pass the second argument `reverse=True`. 26 | 27 | ```py 28 | print(sorted([1, 4, 7, 2, 9, 5, 3], reverse=True)) 29 | # [9, 7, 5, 4, 3, 2, 1] 30 | ``` 31 | 32 | 33 | ## Complex Sorting with the `sorted()` function 34 | Unlike `list.sort()` which is available in the list only, we can sort any iterable with the sorted() method. For example we can sort the dictionary, a multidimensional iterable, etc. 35 | 36 | The following is an example of sorting the dictionary 37 | 38 | ```py 39 | person = { 40 | 'name': 'John Doe', 41 | 'age': 20, 42 | 'occupation': 'student' 43 | } 44 | print(sorted(person)) 45 | 46 | # ['age', 'name', 'occupation'] 47 | ``` 48 | **Note**: _When we sort the dictionary, it returns the list of keys of the dictionary._ 49 | 50 | If we want to sort the dictionary and get the final result as the dictionary, then we need to sort using `dict.items()` and then regenerate the dictionary again. 51 | 52 | ```py 53 | person = { 54 | 'name': 'John Doe', 55 | 'age': 20, 56 | 'occupation': 'student' 57 | } 58 | 59 | print(sorted(person.items())) 60 | # output (dict_items) 61 | # [('age', 20), ('name', 'John Doe'), ('occupation', 'student')] 62 | 63 | 64 | print({k:v for k,v in sorted(person.items())}) 65 | # output (dict) 66 | # {'age': 20, 'name': 'John Doe', 'occupation': 'student'} 67 | ``` 68 | 69 | ### a `key` parameter in `sorted()` function 70 | 71 | Additionally, we can pass the `key` parameter to the `sorted()` function to do advanced sorting. for example: 72 | 73 | ```py 74 | students = [ 75 | ('John', 2), 76 | ('Eve', 4), 77 | ('Jennifer', 3), 78 | ('Adam', 1) 79 | ] 80 | print(sorted(students, key=lambda x: x[1])) 81 | 82 | # Output 83 | # [('Adam', 1), ('John', 2), ('Jennifer', 3), ('Eve', 4)] 84 | ``` 85 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/chapter-13.3-filter.md: -------------------------------------------------------------------------------- 1 | **Table of Contents** 2 | 3 | 4 | # 13.3. The `filter()` function 5 | 6 | https://docs.python.org/3/library/functions.html#filter 7 | 8 | A `filter()` function is a generator function that filters an iterable by specified condition. A filter function takes 9 | 10 | ```py 11 | def is_even(num): 12 | return num%2==0 13 | 14 | list_1 = [1,2,3,4,5,6,7,8,9,10] 15 | 16 | list_2 = filter(is_even, list_1) 17 | 18 | print(list(list_2)) 19 | ``` 20 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/chapter-13.4-map.md: -------------------------------------------------------------------------------- 1 | # Chapter 13.4: The `map()` Function 2 | 3 | **Table of Contents** 4 | 5 | - [Chapter 13.4: The `map()` Function](#chapter-134-the-map-function) 6 | - [Introduction to `map()` function](#introduction-to-map-function) 7 | - [Using `map()` function with other functions to transform data](#using-map-function-with-other-functions-to-transform-data) 8 | - [Using `map()` function with lambda to transform data](#using-map-function-with-lambda-to-transform-data) 9 | - [Using `map()` function to manipulate 2 lists](#using-map-function-to-manipulate-2-lists) 10 | 11 | ## Introduction to `map()` function 12 | 13 | Map function is a generator function that applies another function to elements 14 | of the iterable that is passed as an argument. The basic structure of a `map()` 15 | function is as follows: 16 | 17 | ```python 18 | map(function, iterable) 19 | map(function, iterable, *iterables) 20 | ``` 21 | 22 | The first parameter `function` is function itself that transforms the element to 23 | the iterable. The `function` can either be a regular function or a lambda. 24 | 25 | Reference: 26 | 27 | ## Using `map()` function with other functions to transform data 28 | 29 | ```python 30 | def capitalize_and_ascii_sum(word: str): 31 | """ 32 | This method capitalizes the word and finds out the sum of ASCII value of 33 | all characters of a word 34 | """ 35 | return sum(ord(x) for x in word.capitalize()) 36 | 37 | 38 | animals = ['cat', 'dog', 'cow'] 39 | 40 | transformed_data = map(capitalize_and_ascii_sum, animals) 41 | print(list(transformed_data)) # [280, 282, 297] 42 | 43 | ``` 44 | 45 | Here the first parameter `capitalize_and_ascii_sum` is a callable that is mapped 46 | to each data of the list `animals`. 47 | 48 | ## Using `map()` function with lambda to transform data 49 | 50 | We can also use `lambda` instead of a function for transforming the data. 51 | 52 | ```python 53 | numbers = [1, 2, 3, 4, 5] 54 | 55 | squares = map(lambda x: x ** 2, numbers) 56 | print(list(squares)) # [1, 4, 9, 16, 25] 57 | ``` 58 | 59 | Here, the `lambda` squares the number that is passed and maps the square values 60 | of all the elements of the list `numbers`. 61 | 62 | 63 | ## Using `map()` function to manipulate 2 lists 64 | 65 | We can also map 2 or more iterables to find out the resulting iterable. This 66 | method is useful when we have to perform operations between different data such 67 | as sum of each element of list. 68 | 69 | If we pass 2 iterables of different length, it will map the value until the 70 | shortest iterable gets exhausted. 71 | 72 | ```python 73 | food = ['apple', 'potato', 'chicken', 'banana'] 74 | product = ['juice', 'chips', 'chilly', 'shake'] 75 | 76 | dishes = map(lambda a, b: f'{a} {b}', food, product) 77 | print(list(dishes)) # ['apple juice', 'potato chips', 'chicken chilly', 'banana shake'] 78 | ``` 79 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/chapter-13.5-reduce.md: -------------------------------------------------------------------------------- 1 | # Chapter 13.5: The `reduce()` Function 2 | 3 | **Table of Contents** 4 | 5 | - [Chapter 13.5: The `reduce()` Function](#chapter-135-the-reduce-function) 6 | - [Introduction to `reduce` function](#introduction-to-reduce-function) 7 | 8 | ## Introduction to `reduce` function 9 | 10 | The `reduce` function performs calculation on all elements by iterating on items 11 | of an iterable. For example, we can find out the product of all numbers of a 12 | list. The function `reduce` can be imported from `functools` builtin library. 13 | 14 | The first parameter of the `reduce` function takes a function as a parameter 15 | that takes exactly 2 arguments. To reduce the iterable it first takes first 2 16 | elements and performs operation. The result of them is then fed to the function 17 | again with another element until the iterable gets exhausted. 18 | 19 | ```python 20 | from functools import reduce 21 | 22 | numbers = [1, 12, 30, 24, 8, 11, 15, ] 23 | result = reduce(lambda x, y: x * y, numbers) 24 | print(result) # 11404800 25 | ``` 26 | 27 | Explanation of the above function: 28 | 29 | function: `lambda x, y: x * y` 30 | 31 | - step1: `lambda` => `1 * 12` = `12` 32 | - step2: `lambda` => `12 * 30` = `360` 33 | - step3: `lambda` => `360 * 24` = `8640` 34 | - step4: `lambda` => `8640 * 8` = `69120` 35 | - step5: `lambda` => `69120 * 11` = `760320` 36 | - step6: `lambda` => `760320 * 15` = `11404800` 37 | 38 | > **Note**: We might mistakenly think that we could find out sum of squares with 39 | > the `reduce` function by passing lambda as `lambda x,y: x**2 + y**2` but it 40 | > end up giving very large number since it finds out square of first 2 numbers 41 | > and in the next iteration, it again finds out the square of the past result 42 | > and add it with the square of the next occurrence. 43 | 44 | Explanation: 45 | 46 | ```python 47 | from functools import reduce 48 | 49 | numbers = [1, 2, 3, 4, 5] 50 | result = reduce(lambda x, y: x ** 2 + y ** 2, numbers) 51 | print(result) # 1373609 52 | 53 | ``` 54 | 55 | here we might think that the reduce function does `1 + 4 + 9 + 16 + 25` but it 56 | does something like below: 57 | 58 | `[{(1 + 4) ** 2 + 9} ** 2 + 16] ** 2 + 25` which will be equal to `1373609` 59 | 60 | - step 1: `lambda` => `1 ** 2 + 2 ** 2` = `1 + 4` = `5` 61 | - step 2: `lambda` => `5 ** 2 + 3 ** 2` = `25 + 9` = `34` 62 | - step 3: `lambda` => `34 ** 2 + 4 ** 2` = `1156 + 16` = `1172` 63 | - step 4: `lambda` => `1172 ** 2 + 5 ** 2` = `1,373,584 + 25` = `1373609` 64 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/code/c0501_groupby.py: -------------------------------------------------------------------------------- 1 | from itertools import groupby 2 | 3 | """ 4 | `groupby()` function 5 | """ 6 | # example 1 7 | animals = ['Bear', 'Donkey', 'Cat', 'Dog', 'Camel', 'Elephant'] 8 | animals = sorted(animals) 9 | values = groupby(animals, lambda x: x[0]) 10 | grouped = {k: list(v) for k, v in values} 11 | print(grouped) 12 | 13 | # {'B': ['Bear'], 'C': ['Camel', 'Cat'], 'D': ['Dog', 'Donkey'], 'E': ['Elephant']} 14 | 15 | # Example 2 16 | coordinates = ((1, 2), (2, 3), (3, 4), (1, 3), (2, 4), (1, 4)) 17 | f = lambda f: f[0] 18 | 19 | grouped = groupby(sorted(coordinates, key=f), f) 20 | 21 | print({k: list(v) for k,v in grouped}) 22 | # {1: [(1, 2), (1, 3), (1, 4)], 2: [(2, 3), (2, 4)], 3: [(3, 4)]} 23 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/code/c0502_sorted.py: -------------------------------------------------------------------------------- 1 | # sorting in an ascending order 2 | print(sorted([1, 4, 7, 2, 9, 5, 3])) 3 | 4 | # sorting in an ascending order 5 | print(sorted([1, 4, 7, 2, 9, 5, 3], reverse=True)) 6 | 7 | 8 | ## Sorting the dictionary 9 | person = { 10 | 'name': 'John Doe', 11 | 'age': 20, 12 | 'occupation': 'student' 13 | } 14 | print(sorted(person)) 15 | 16 | # Sorting the dictionary to get the dictionary 17 | print(sorted(person.items())) 18 | 19 | print({k:v for k,v in sorted(person.items())}) 20 | 21 | 22 | # sorting with the key 23 | 24 | students = [ 25 | ('John', 2), 26 | ('Eve', 4), 27 | ('Jennifer', 3), 28 | ('Adam', 1) 29 | ] 30 | 31 | print(sorted(students, key=lambda x: x[1])) 32 | -------------------------------------------------------------------------------- /notes/c13_advanced_functions/code/c0503_filter.py: -------------------------------------------------------------------------------- 1 | # https://docs.python.org/3/library/functions.html#filter 2 | 3 | def is_even(num): 4 | return num%2==0 5 | 6 | list_1 = [1,2,3,4,5,6,7,8,9,10] 7 | 8 | list_2 = filter(is_even, list_1) 9 | 10 | print(list(list_2)) 11 | -------------------------------------------------------------------------------- /notes/c14_regex/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 14: Regular Expressions (REGEX) 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | > 9 | 10 | **Table of contents**: 11 | 12 | - [Regular Expressions](chapter-14.1-regular-expressions.md) 13 | - [REGEX Special Characters](chapter-14.2-regex-special-characters.md) 14 | - [The `re` module](chapter-14.3-the-re-module.md) 15 | -------------------------------------------------------------------------------- /notes/c14_regex/chapter-14.1-regular-expressions.md: -------------------------------------------------------------------------------- 1 | **Table of Contents** 2 | 3 | # Chapter 14 Introduction to Regular Expressions 4 | 5 | **res**: https://docs.python.org/3/library/re.html 6 | 7 | Python has a standard library for regular expressions called `re` module. The `re` module provides us regular expression matching operations. 8 | 9 | Both _Unicode Strings_ and _Byte Strings_ can be searched using regular expressions in python however both cannot be mixed. 10 | 11 | Regular Expressions contain even wider range of escape characters to represent different patterns inside strings. 12 | 13 | 14 | Let us suppose we have the sentence: 15 | 16 | ```python 17 | str_1 = 'Regular expressions are better pattern matchers.' 18 | ``` 19 | 20 | **Example 1:** 21 | If we want to match all `er` from the above string like shown below: 22 | 23 | > Regular expressions are bett`er` patt`er`n match`er`s. 24 | 25 | We can match the regular expression by specyfing regex string as: `r'er'` 26 | 27 | 28 | **Example 2:** 29 | If we want to match all `er` and `ar` from the above string like shown below: 30 | 31 | > Regul`ar` expressions `ar`e bett`er` patt`er`n match`er`s. 32 | 33 | We can match the regular expression by specifying regex string as: `r'[ae]r'` 34 | 35 | 36 | **Example 3:** 37 | If we want to match all the occurence that start with `J` and end with `n` from the following expression: 38 | 39 | ```python 40 | str_1 = 'John, Jane, Jennifer, Joan, Jon, Adam, Eve' 41 | ``` 42 | 43 | We can match the regular expression by specifying regex string as: `r'J\w*n'` which results in matching the following: 44 | 45 | > `John`, `Jan`e, `Jenn`ifer, `Joan`, `Jon`, Adam, Eve' 46 | 47 | The example code snippet would be: 48 | 49 | ```py 50 | import re 51 | str_1 = 'John, Jane, Jennifer, Joan, Jon, Adam, Eve' 52 | print(re.findall(r'J\w*n', str_1)) 53 | 54 | # ['John', 'Jan', 'Jenn', 'Joan', 'Jon'] 55 | ``` 56 | 57 | 58 | In above examples, we can see ordinary characters like `J`, `n`, etc. and special characters like `.`, `\`, `[ ]`, etc. A combination of those characters creates a regular expression. 59 | 60 | For Example 61 | 62 | - `"Ap+le"` matches words `Aple`, `Apple`, `Appple`, etc. 63 | - `"b..k"` matches words `book`, `beak`, `b ok`, etc. 64 | -------------------------------------------------------------------------------- /notes/c14_regex/code/ch1400_regex.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # compile 4 | countries = ['USA', 'Japan', 'Angola', 'China', 'Algeria', 'Nepal', 'Argentina', 'Albania'] 5 | 6 | # a word that starts with A and ends with a 7 | pattern = re.compile(r'^A\w*a$') 8 | 9 | for country in countries: 10 | print(pattern.match(country)) 11 | 12 | """ 13 | # Output 14 | 15 | None 16 | None 17 | 18 | None 19 | 20 | None 21 | 22 | 23 | """ 24 | 25 | # compile example 2 26 | 27 | strings = ["0xaa", 'FA04', 'Ak45', 'As40', '0x5h', '0x56'] 28 | 29 | pat = re.compile(r'^(0x)?[0-9A-Fa-f]+$') 30 | 31 | for st in strings: 32 | print(pat.match(st)) 33 | 34 | """ 35 | # output 36 | 37 | 38 | 39 | None 40 | None 41 | None 42 | 43 | """ 44 | 45 | # search() 46 | text = "Dear sir, I've emailed you last week regarding the assignment. I've also sent you another mail specifying the next assignment." 47 | 48 | results = re.search(r'e?mail(ed)?', text) 49 | print(results) 50 | 51 | """ 52 | # Output 53 | 54 | 55 | """ 56 | 57 | # match() 58 | text = 'Dear sir' 59 | print(re.match(r'[a-z]e\w+', text, re.I)) 60 | """ 61 | # output 62 | 63 | 64 | """ 65 | 66 | # # findall 67 | countries = 'USA, Japan, Angola, China, Algeria, Nepal, Argentina, Albania' 68 | 69 | print(re.findall(r'A[a-z]+a', countries)) 70 | 71 | """ 72 | # Output 73 | 74 | ['Angola', 'Algeria', 'Argentina', 'Albania'] 75 | """ 76 | 77 | 78 | # split 79 | countries = 'USA, Japan; Angola! China, Algeria% Nepal; Argentina/ Albania' 80 | print(re.split(r'\W+', countries)) 81 | """ 82 | # Output: 83 | 84 | ['USA', ' Japan', ' Angola', ' China', ' Algeria', ' Nepal', ' Argentina', ' Albania'] 85 | """ 86 | 87 | 88 | print(re.sub(r'\W+', ', ', countries)) 89 | """ 90 | # Output 91 | 92 | USA, Japan, Angola, China, Algeria, Nepal, Argentina, Albania 93 | """ 94 | -------------------------------------------------------------------------------- /notes/c15_type_hinting/example/ch1501_basic_type_hinting.py: -------------------------------------------------------------------------------- 1 | id: int = 1 2 | name: str = "John Doe" 3 | subjects: list[str] = ["Physics", "Chemistry", "Biology"] 4 | data: dict[str, int] = { 5 | "roll": 1, 6 | "age": 10, 7 | "grade": 5, 8 | } 9 | -------------------------------------------------------------------------------- /notes/c15_type_hinting/example/ch1502_function_hinting.py: -------------------------------------------------------------------------------- 1 | # type hinting in functions 2 | 3 | 4 | def distance(x1: int, y1: int, x2: int, y2: int) -> float: 5 | return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5 6 | 7 | 8 | if __name__ == "__main__": 9 | dist = distance(0, 0, 5, 5) 10 | print(f"The distance is: {dist:10.3}.") 11 | # The distance is: 7.07. 12 | -------------------------------------------------------------------------------- /notes/c15_type_hinting/example/ch1503_typing_module.py: -------------------------------------------------------------------------------- 1 | # Advanced type hinting using typing module 2 | # This is useful for python version below 3.10 3 | 4 | from typing import List, Literal, TypedDict, Union 5 | 6 | my_list: List[Union[int, str]] = ["John", "Jane", 2] 7 | 8 | 9 | class User(TypedDict): 10 | id: int 11 | name: str 12 | type: Literal["admin", "staff"] 13 | 14 | 15 | if __name__ == "__main__": 16 | print(f"My List: {my_list }") 17 | 18 | user: User = { 19 | "id": 1, 20 | "name": "John Doe", 21 | "type": "admin", 22 | } 23 | print(user) 24 | -------------------------------------------------------------------------------- /notes/c15_type_hinting/example/ch1504_advanced_hinting.py: -------------------------------------------------------------------------------- 1 | # Advanced type hinting with less typing module 2 | # This is useful for python version greater than or equal to 3.10 3 | 4 | from typing import Literal, TypedDict 5 | 6 | my_list: list[int | str] = ["John", "Jane", 2] 7 | 8 | 9 | class User(TypedDict): 10 | id: int 11 | name: str 12 | type: Literal["admin", "staff"] 13 | 14 | 15 | if __name__ == "__main__": 16 | print(f"My List: {my_list }") 17 | 18 | user: User = { 19 | "id": 1, 20 | "name": "John Doe", 21 | "type": "admin", 22 | } 23 | print(user) 24 | -------------------------------------------------------------------------------- /notes/c16_decorators/code/ch1401_decorator.py: -------------------------------------------------------------------------------- 1 | # ================================[ Example 1 ]================================ 2 | # raw behavior of decorator 3 | 4 | 5 | def decorate_me(func): 6 | def inner(*args, **kwargs): 7 | print("This is first called before executing the original method") 8 | return func(*args, **kwargs) 9 | 10 | return inner 11 | 12 | 13 | def original_method(): 14 | print("This is an original method") 15 | 16 | 17 | decorated = decorate_me(original_method) 18 | print("Calling Decorated method: ") 19 | decorated() 20 | 21 | # ================================[ Example 2 ]================================ 22 | 23 | 24 | def auto_typecast(fn): 25 | def inner(x, y): 26 | if isinstance(x, str): 27 | x = int(x) 28 | if isinstance(y, str): 29 | y = int(y) 30 | return fn(x, y) 31 | 32 | return inner 33 | 34 | 35 | @auto_typecast 36 | def add(x, y): 37 | return x + y 38 | 39 | 40 | @auto_typecast 41 | def subtract(x, y): 42 | return x - y 43 | 44 | 45 | if __name__ == "__main__": 46 | print("adding number 5 and string '6': ", add(5, "6")) 47 | print("subtracting number 5 from string '10': ", subtract("10", 5)) 48 | -------------------------------------------------------------------------------- /notes/c16_decorators/code/ch1402_decorator_factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Decorator Factory 3 | 4 | Decorator factory is a higher order function, that accepts arguments 5 | """ 6 | import time 7 | 8 | 9 | # ================================[ Example 1 ]================================ 10 | 11 | 12 | def log(before: bool, after: bool): 13 | def factory(func): 14 | def _inner(*args, **kwargs): 15 | if before: 16 | print(f"function {func} execution started at: {time.time()}") 17 | result = func(*args, **kwargs) 18 | if after: 19 | print(f"function {func} execution ended at: {time.time()}") 20 | return result 21 | 22 | return _inner 23 | 24 | return factory 25 | 26 | 27 | @log(True, True) 28 | def delay_execution(seconds): 29 | time.sleep(seconds) 30 | 31 | @log(before=True, after=False) 32 | def print_hello(): 33 | print("Hello") 34 | # ================================[ Example 2 ]================================ 35 | def moves(x: int, y: int): 36 | """ 37 | This Decorator factory accepts 2 arguments which will then be used later by 38 | the inner function. 39 | """ 40 | 41 | def factory(function): 42 | def inner(current_x, current_y): 43 | return function(current_x + x, current_y + y) 44 | 45 | return inner 46 | 47 | return factory 48 | 49 | 50 | @moves(x=5, y=5) 51 | def move_king(x, y): 52 | print(f"moved king to: ({x}, {y})") 53 | 54 | 55 | if __name__ == "__main__": 56 | delay_execution(2) 57 | print_hello() 58 | move_king(1, 1) 59 | -------------------------------------------------------------------------------- /notes/c16_decorators/code/ch1403_class_based_decorator.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable 2 | 3 | 4 | class LoggerDecorator: 5 | def __init__(self, func: Callable) -> None: 6 | self.func = func 7 | 8 | def __call__(self, *args: Any, **kwds: Any) -> Any: 9 | print(f"the function {self.func.__name__} is being called") 10 | result = self.func(*args, **kwds) 11 | print(f"the function {self.func.__name__} has been successfully called") 12 | return result 13 | 14 | 15 | @LoggerDecorator 16 | def regular_function(): 17 | print("this is a function body") 18 | 19 | 20 | if __name__ == "__main__": 21 | regular_function() 22 | -------------------------------------------------------------------------------- /notes/c16_decorators/code/ch1404_decorating_class.py: -------------------------------------------------------------------------------- 1 | def personify(cls): 2 | class Wrapped(cls): 3 | def set_full_name(self, full_name: str): 4 | self.full_name = full_name 5 | 6 | return Wrapped 7 | 8 | 9 | @personify 10 | class Animal: 11 | pass 12 | 13 | 14 | animal = Animal() 15 | animal.set_full_name("John") 16 | print(f"The full name is: {animal.full_name}") 17 | -------------------------------------------------------------------------------- /notes/c17_mixins/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 17: Mixins 2 | 3 | **Table of contents**: 4 | - [Chapter 17: Mixins](#chapter-17-mixins) 5 | - [Introduction to mixin](#introduction-to-mixin) 6 | - [Example of a mixin](#example-of-a-mixin) 7 | 8 | 9 | ## Introduction to mixin 10 | 11 | Mixin is a class that adds method implementation. It is intended to be used to 12 | add method implementation but not instantiation so we generally add methods to 13 | the mixin class. Mixin uses a concept of multiple inheritance to combine all 14 | parent methods to create a new type by a child class. 15 | 16 | We can implement multiple mixins in a child class to expand its usability and 17 | reduce code duplication. Python uses `C3 Linearization` or 18 | `Method Resolution order` to deal with collisions if there are same methods 19 | defined in multiple mixins. To know more about it, you can review the topic 20 | `Multiple Inheritance`. 21 | 22 | Reference: 23 | 24 | ### Example of a mixin 25 | ```python 26 | # resource/c17_mixins/example/ch1501_mixin.py 27 | class Vehicle: 28 | """ 29 | Base class 30 | """ 31 | 32 | name: str 33 | 34 | def __init__(self, name: str) -> None: 35 | self.name = name 36 | 37 | 38 | class EvMixin: 39 | """ 40 | A mixin that adds more method implementation without affecting the parent's 41 | instantiation 42 | """ 43 | 44 | name: str # this mixin expects attribute `name` to be in the parent class 45 | 46 | def recharge_with_ac(self): 47 | assert self.name 48 | print(f"⚡: Recharging {self.name} with alternating current") 49 | 50 | def recharge_with_dc(self): 51 | print(f"⚡: Recharging {self.name} with direct current") 52 | 53 | 54 | class ElectricCar(Vehicle, EvMixin): 55 | """ 56 | A child class that inherits `Vehicle` and implements `EvMixin`. 57 | """ 58 | 59 | def __init__(self, name: str) -> None: 60 | super().__init__(name) 61 | 62 | 63 | if __name__ == "__main__": 64 | car = ElectricCar("my electric car") 65 | car.recharge_with_ac() 66 | car.recharge_with_dc() 67 | 68 | ``` 69 | 70 | **Output** 71 | ``` 72 | ⚡: Recharging my electric car with alternating current 73 | ⚡: Recharging my electric car with direct current 74 | ``` 75 | -------------------------------------------------------------------------------- /notes/c17_mixins/example/ch1701_mixin.py: -------------------------------------------------------------------------------- 1 | """ 2 | ! To run, please run the following command in your preferred shell 3 | 4 | python resource/c17_mixins/example/ch1701_mixin.py 5 | 6 | """ 7 | 8 | 9 | class Vehicle: 10 | """ 11 | Base class 12 | """ 13 | 14 | name: str 15 | 16 | def __init__(self, name: str) -> None: 17 | self.name = name 18 | 19 | 20 | class EvMixin: 21 | """ 22 | A mixin that adds more method implementation without affecting the parent's 23 | instantiation 24 | """ 25 | 26 | name: str # this mixin expects attribute `name` to be in the parent class 27 | 28 | def recharge_with_ac(self): 29 | assert self.name 30 | print(f"⚡: Recharging {self.name} with alternating current") 31 | 32 | def recharge_with_dc(self): 33 | print(f"⚡: Recharging {self.name} with direct current") 34 | 35 | 36 | class ElectricCar(Vehicle, EvMixin): 37 | """ 38 | A child class that inherits `Vehicle` and implements `EvMixin`. 39 | """ 40 | 41 | def __init__(self, name: str) -> None: 42 | super().__init__(name) 43 | 44 | 45 | if __name__ == "__main__": 46 | car = ElectricCar("my electric car") 47 | car.recharge_with_ac() 48 | car.recharge_with_dc() 49 | -------------------------------------------------------------------------------- /notes/c17_mixins/example/ch1702_multiple_mixins.py: -------------------------------------------------------------------------------- 1 | """ 2 | ! To run, please run the following command in your preferred shell 3 | 4 | python resource/c17_mixins/example/ch1702_multiple_mixins.py 5 | 6 | """ 7 | 8 | 9 | class Vehicle: 10 | """ 11 | Base class 12 | """ 13 | 14 | name: str 15 | 16 | def __init__(self, name: str) -> None: 17 | self.name = name 18 | 19 | 20 | class EvMixin: 21 | """ 22 | A mixin that adds more method implementation without affecting the parent's 23 | instantiation 24 | """ 25 | 26 | name: str # this mixin expects attribute `name` to be in the parent class 27 | 28 | def recharge_with_ac(self): 29 | assert self.name 30 | print(f"⚡: Recharging {self.name} with alternating current") 31 | 32 | def recharge_with_dc(self): 33 | print(f"⚡: Recharging {self.name} with direct current") 34 | 35 | 36 | class GasolineMixin: 37 | """ 38 | Something similar to EvMixin, but with other method implementation 39 | """ 40 | 41 | MAX_VOLUME: int 42 | fuel_available: int 43 | 44 | def refuel(self, volume: int): 45 | new_volume = self.fuel_available + volume 46 | if new_volume > self.MAX_VOLUME: 47 | print(f"Overflow: {new_volume - self.MAX_VOLUME}") 48 | self.fuel_available = self.MAX_VOLUME 49 | else: 50 | self.fuel_available = new_volume 51 | 52 | 53 | class HybridCar(Vehicle, EvMixin, GasolineMixin): 54 | """ 55 | A child class that inherits `Vehicle` and implements `EvMixin`. 56 | """ 57 | 58 | MAX_VOLUME = 50 59 | 60 | def __init__(self, name: str) -> None: 61 | super().__init__(name) 62 | self.fuel_available = 0 63 | 64 | 65 | if __name__ == "__main__": 66 | car = HybridCar("my electric car") 67 | car.refuel(60) # Overflow: 10 68 | car.recharge_with_ac() # ⚡: Recharging my electric car with alternating current 69 | -------------------------------------------------------------------------------- /notes/c18_python_http/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 18: the `http` module 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | 9 | **Table of contents**: 10 | 11 | ### 🚧 Under Construction 🚧 12 | This section is currently incomplete and will be updated in the future. Stay tuned for more content! 13 | 14 | 💡 **Want to contribute?** 15 | Fork the repository, make your changes, and submit a Pull Request. Your contributions are greatly appreciated! 🙌 16 | -------------------------------------------------------------------------------- /notes/c18_python_http/example/ch1801_http_client.py: -------------------------------------------------------------------------------- 1 | from http import client 2 | 3 | 4 | connection = client.HTTPSConnection("www.python.org") 5 | 6 | # connection.connect() 7 | connection.request("GET", "/") 8 | 9 | resp = connection.getresponse() 10 | print("status, reason: {}, {}".format(resp.status, resp.reason)) 11 | -------------------------------------------------------------------------------- /notes/c19_requests/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 19: the `requests` module 2 | 3 | > **Note**: 4 | > 5 | > please refer to the repository 6 | > **[python projects](https://github.com/ghimiresdp/python-projects)** for more 7 | > exercises and projects related to this chapter. 8 | 9 | **Table of contents**: 10 | 11 | ### 🚧 Under Construction 🚧 12 | This section is currently incomplete and will be updated in the future. Stay tuned for more content! 13 | 14 | 💡 **Want to contribute?** 15 | Fork the repository, make your changes, and submit a Pull Request. Your contributions are greatly appreciated! 🙌 16 | -------------------------------------------------------------------------------- /notes/c21_testing/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 21: Software Testing in Python 2 | 3 | **Table of contents**: 4 | 5 | - [21.1: Introduction to Software Testing](./chapter-21.1-intro.md) 6 | - [21.2: Testing using `unittest` module](./chapter-21.2-unittest.md) 7 | - [21.3: The `pytest` Package](./chapter-21.3-pytest.md) 8 | -------------------------------------------------------------------------------- /notes/c21_testing/chapter-21.2-unittest.md: -------------------------------------------------------------------------------- 1 | # Chapter 21.2: Testing using `unittest` module 2 | 3 | **Table of contents**: 4 | - [Chapter 21.2: Testing using `unittest` module](#chapter-212-testing-using-unittest-module) 5 | - [Introduction to `unittest`](#introduction-to-unittest) 6 | - [Example of unittest](#example-of-unittest) 7 | - [`setUp` and `tearDown` methods](#setup-and-teardown-methods) 8 | 9 | 10 | 11 | ## Introduction to `unittest` 12 | 13 | The `unittest` module is a python's builtin test module. It contains all basics 14 | of software testing, example: test discovery,assertion, setup/teardown, etc. 15 | 16 | As python is an Object-oriented programming language, it's `unittest` module 17 | also implements Class-based interface to setup the test module. We can easily 18 | import `unittest` module to start testing our software. 19 | 20 | ### Example of unittest 21 | 22 | The following is a basic example of a testcase which will test for the 23 | correctness of the given function, "reverse_word". 24 | 25 | ```python 26 | import unittest 27 | 28 | def reverse_word(word: str): 29 | return word[::-1] 30 | 31 | class TestReverseWord(unittest.TestCase): 32 | def test_reverse(): 33 | self.assertEqual(reverse_word("abcde"), "edcba") 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | ``` 39 | 40 | ## `setUp` and `tearDown` methods 41 | 42 | A setup method is a method that runs before each test. The `setUp` method 43 | generally initializes values before testcases are run. For example, If we need 44 | an authorization token to test an API, we can get them in the `setUp` method. 45 | 46 | A `tearDown` method is a method that runs after each test. This method is used 47 | as a cleanup method after test gets executed. 48 | 49 | An example of setup and teardowm methods are as follows: 50 | ```python 51 | def celsius_to_fahrenheit(celsius: float): 52 | return (9 / 5) * celsius + 32 53 | 54 | 55 | class TestTemperatureChange(TestCase): 56 | def setUp(self) -> None: 57 | # This method runs before each test case inside of this test class 58 | self.celsius = 37 59 | 60 | def tearDown(self) -> None: 61 | # This method runs after each test case inside of this test class 62 | del self.celsius 63 | 64 | def test_conversion_from_setup(self): 65 | self.assertAlmostEqual(celsius_to_fahrenheit(self.celsius), 98.6) 66 | 67 | ``` 68 | -------------------------------------------------------------------------------- /notes/c21_testing/chapter-21.3-pytest.md: -------------------------------------------------------------------------------- 1 | # Chapter 21.3: The `pytest` Package 2 | 3 | **Table of contents**: 4 | 5 | 6 | ## The `pytest` package. 7 | 8 | Pytest is a testing framework which is made to simplify tests. It allows us to 9 | test our programs with the help of functional programming technique. 10 | 11 | Some of the interesting features of `pytest` are as follows: 12 | 13 | - Auto discovery of test modules and functions 14 | - Fixtures to reduce repetitions in test codes 15 | - Plugins can be introduced to enhance testing experience 16 | 17 | 18 | ## Installing `pytest` 19 | 20 | To install `pytest`, we need to install it using package manager of our choice. 21 | For example, we could use `pip`, `poetry`, etc. to install `pytest` package. 22 | ```shell 23 | # using pip 24 | pip install pytest 25 | 26 | # using poetry 27 | poetry add pytest 28 | ``` 29 | To know more about `pytest`, you can read the detailed `pytest` documentation 30 | [here](https://docs.pytest.org/en/stable/). 31 | -------------------------------------------------------------------------------- /notes/c21_testing/code/c21_001_intro.py: -------------------------------------------------------------------------------- 1 | """ 2 | ! Simple unit testing with `assert` keyword 3 | 4 | * The `assert` keyword is used to check for the truthfulness or equality of the 5 | given expression 6 | * It is a python's reserved keyword. 7 | """ 8 | 9 | 10 | def multiply_divide(number): 11 | if number % 2 == 0: 12 | return int(number / 2) 13 | return number * 2 14 | 15 | 16 | def test(): 17 | assert multiply_divide(4) == 2 18 | 19 | assert multiply_divide(4) == 8 # AssertionError 20 | """NOTE: if we uncomment the above line, we get an `AssertionError`. 21 | Assertion error occurs when we do not get expected result. 22 | 23 | The test below was intentionally checked for the equality of 1 + 2 == 4 24 | so that we would get `AssertionError` 25 | """ 26 | 27 | 28 | if __name__ == "__main__": 29 | test() 30 | -------------------------------------------------------------------------------- /notes/c21_testing/code/c21_002_unittest.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase, main 2 | 3 | 4 | def reverse_word(word: str): 5 | return word[::-1] 6 | 7 | 8 | def divide(a, b): 9 | """ 10 | 11 | Args: 12 | a (int | float): _description_ 13 | b (int | float): _description_ 14 | 15 | Returns: 16 | _type_: float 17 | """ 18 | return a / b 19 | 20 | 21 | class TestReverseWord(TestCase): 22 | def test_correct_reverse(self): 23 | self.assertEqual(reverse_word("abcde"), "edcba") 24 | 25 | def test_almost_equal_value(self): 26 | self.assertAlmostEqual(divide(22, 7), 3.14, places=2) 27 | 28 | def test_raises_error(self): 29 | with self.assertRaises(ZeroDivisionError) as err: 30 | divide(2, 0) 31 | self.assertEqual(str(err.exception), "division by zero") 32 | 33 | 34 | if __name__ == "__main__": 35 | main() 36 | -------------------------------------------------------------------------------- /notes/c21_testing/code/c21_003_setup_teardown.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase, main 2 | 3 | 4 | def celsius_to_fahrenheit(celsius: float): 5 | return (9 / 5) * celsius + 32 6 | 7 | 8 | class TestTemperatureChange(TestCase): 9 | def setUp(self) -> None: 10 | # This method runs before each test case inside of this test class 11 | self.celsius = 37 12 | 13 | def tearDown(self) -> None: 14 | # This method runs after each test case inside of this test class 15 | del self.celsius 16 | 17 | @classmethod 18 | def setUpClass(cls) -> None: 19 | # This method runs once before test case inside of this test class start 20 | cls.initial_value = 0 21 | 22 | def test_conversion_from_setup_class(self): 23 | self.assertEqual(self.initial_value, 0) 24 | 25 | def test_conversion_from_setup(self): 26 | self.assertAlmostEqual(celsius_to_fahrenheit(self.celsius), 98.6) 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /problem_solving/README.md: -------------------------------------------------------------------------------- 1 | # Problem Solving 2 | 3 | This section will help us become more proficient with problem solving skills 4 | with Python Programming Language. 5 | 6 | This section primarily contains 2 different sections which are as follows: 7 | 8 | ## Basic Problem Solving 9 | 10 | This section helps us warming up with basic problem solving skills. It's goal is 11 | to review our previously learned python notes section. 12 | 13 | Some of the Basic Problem Solving solutions are as follows: 14 | 15 | 1. [Practical Number Solution](basic/practical_number.py) 16 | 2. [Greatest Common Divisor](basic/gcd.py) 17 | 3. [Matrix Multiplication](basic/matrix_multiplication.py) 18 | 4. [Median Calculation](basic/median.py) 19 | 5. [Reverse digits of an integer](basic/reverse_digits.py) 20 | 21 | 22 | ## Dynamic Programming 23 | 24 | This section helps us solving advanced problems more efficiently and using more 25 | optimum solutions. Solving this type of problem helps us efficiently solve 26 | problems while managing time and space complexity efficiently. 27 | 28 | Some of the Dynamic Programming solutions are as follows: 29 | 30 | 1. [Coin Change Problem](dp/coin_change.py) 31 | 2. [Fibonacci Series Problem](dp/fibonacci.py) 32 | 3. Palindrome Partition Problem 33 | 4. Minimizing the sum of list of integers 34 | 5. Longest Common Subsequence Problem 35 | -------------------------------------------------------------------------------- /problem_solving/basic/gcd.py: -------------------------------------------------------------------------------- 1 | """ 2 | Greatest Common Divisor (GCD) 3 | ----------------------------- 4 | 5 | Greatest Common Divisor or the highest common factor is a common divisor of the 6 | given numbers. 7 | 8 | It is the largest positive integer that divides both numbers without leaving 9 | remainders. 10 | 11 | For example numbers 12 and 18 has GCD 6 12 | 13 | """ 14 | 15 | from functools import reduce 16 | 17 | 18 | def _gcd(a: int, b: int): 19 | """ 20 | Finding out GCD between 2 numbers is so easy in python. we try to perform 21 | modulo operator between 2 numbers until we find remainder to be 0. 22 | 23 | For example we have 2 numbers 48 and 64 24 | 25 | a = 48, b = 64 26 | Step 1: (a, b) = (b, a%b) = (64, 48 % 64) = (64, 48) b != 0 27 | Step 2: (a, b) = (48, 64 % 48) = (48, 16) b != 0 28 | Step 3: (a, b) = (16, 48 % 16) = (16, 0) b == 0 29 | 30 | Setp 4: b == 0, so GCD = a = 16 31 | 32 | """ 33 | # find out gcd between 2 numbers 34 | while b: 35 | a, b = b, a % b 36 | return a 37 | 38 | 39 | def gcd(numbers: list[int]) -> int: 40 | """ 41 | To find out gcd between multiple numbers, we simply reduce the above _gcd 42 | function to each numbers in the list. 43 | 44 | """ 45 | return reduce(_gcd, numbers) 46 | 47 | 48 | if __name__ == "__main__": 49 | print(_gcd(1, 2)) # 1 50 | print(_gcd(12, 18)) # 6 51 | print(_gcd(9, 18)) # 9 52 | print(_gcd(32, 40)) # 8 53 | 54 | print(gcd([1, 2])) # 1 55 | print(gcd([32, 40, 80])) # 8 56 | print(gcd([32, 64, 96])) # 32 57 | print(gcd([64, 96, 160, 320])) # 32 58 | print(gcd([64, 96, 160, 320, 48])) # 16 59 | -------------------------------------------------------------------------------- /problem_solving/basic/median.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Median 3 | -------- 4 | 5 | Median is a statistical calculation procedure that finds out the number that 6 | separates the higher half of the numbers from a list with lower half. 7 | 8 | Generally, when we have odd number of elements, we have the number in the list, 9 | however, if there are even number of elements, we have to find the mean of two 10 | median values. 11 | 12 | if x(n) is nth element of the list, then 13 | 14 | | x((n+1)/2) if n is odd 15 | median = | 16 | | (x(n/2) + x((n/2)+1)/2 if n is even 17 | 18 | NOTE: The index starts at 0 in python so while of finding out nth element, we 19 | have to find out element at (n-1)th index hence, 20 | x((n+1)/2) becomes x((n+1)/2 -1) and 21 | x((n/2) becomes x((n/2 -1)) 22 | """ 23 | 24 | 25 | def median(items: list[int]): 26 | """ 27 | Median needs sorted list since we will find out the middle number that is in 28 | ascending order. 29 | """ 30 | items.sort() 31 | n = items.__len__() 32 | 33 | if n % 2 == 1: 34 | return items[(n + 1) // 2 - 1] 35 | else: 36 | return (items[n // 2 - 1] + items[n // 2]) / 2 37 | 38 | 39 | if __name__ == "__main__": 40 | list = [1, 2, 3, 4, 5] 41 | print(f"Median of the list is : {median(list)}") # 3 42 | 43 | list = [1, 2, 3, 4, 5, 6] 44 | print(f"Median of the list is : {median(list)}") # 3.5 45 | -------------------------------------------------------------------------------- /problem_solving/basic/practical_number.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Practical Number Solution 3 | --------------------------- 4 | 5 | A practical number is a positive integer where every smaller can be expressed as 6 | a sum of distinct divisors of that number. 7 | 8 | In other words, a number is a practical number whose divisors can be combined to 9 | create a number below it. 10 | 11 | For example 12 is a practical number. 12 | It's divisors are: 1, 2, 3, 4, and 6. 13 | 14 | 1 -> 1 15 | 2 -> 2 16 | 3 -> 3 17 | 4 -> 4 18 | 5 -> 4 + 1 19 | 6 -> 6 20 | 7 -> 7 + 1 21 | 8 -> 6 + 2 22 | 9 -> 6 + 3 23 | 10 -> 6 + 4 24 | 11 -> 6 + 4 + 1 25 | 26 | """ 27 | 28 | import math 29 | 30 | 31 | def find_divisors(num: int) -> list[int]: 32 | divisors = {1} 33 | for n in range(2, int(math.sqrt(num)) + 1): 34 | if num % n == 0: 35 | divisors.add(n) 36 | divisors.add(num // n) 37 | return list(divisors) 38 | 39 | 40 | def has_sum(target: int, _set: list[int]): 41 | # if the number itself exists, or number return True 42 | if target == 0 or target in _set: 43 | return True 44 | 45 | sums = {0} 46 | 47 | for num in _set: 48 | new_sums = {x + num for x in sums} 49 | sums.update(new_sums) 50 | if target in sums: 51 | return True 52 | return False 53 | 54 | 55 | def is_practical_number(num: int) -> bool: 56 | if num < 1: 57 | return False 58 | divisors = find_divisors(num) 59 | sum_previous = 1 60 | 61 | for i in range(1, len(divisors)): 62 | if divisors[i] > sum_previous + 1: 63 | return False 64 | sum_previous += divisors[i] 65 | 66 | for n in range(1, num): 67 | if not has_sum(n, divisors): 68 | return False 69 | return True 70 | 71 | 72 | if __name__ == "__main__": 73 | for number in [8, 10, 12, 15]: 74 | print( 75 | f"{number:<5d}: {'✅' if is_practical_number(number) else '⛔ Not'} Practical" 76 | ) 77 | 78 | """ 79 | 80 | OUTPUT 81 | --------- 82 | 8 : ✅ Practical 83 | 10 : ⛔ Not Practical 84 | 12 : ✅ Practical 85 | 15 : ⛔ Not Practical 86 | 87 | """ 88 | -------------------------------------------------------------------------------- /problem_solving/basic/reverse_digits.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reverse Digits of an integer 3 | ---------------------------- 4 | 5 | Given an integer, reverse all the digits of an integer mathematically. Example, 6 | if an integer 12345 is given, the reversed integer should be 54321. 7 | 8 | Mathematically reversing an integer requires a loop that divides the integer and 9 | find outs the remainder to know the digit of one place and multiplying back by 10 | 10 and adding the remainder of the previous iteration until the number becomes 0. 11 | 12 | Steps: 13 | 1. result = 0 14 | 15 | 2. divide the integer by 10 and find out remainder and result 16 | - number = num // 10 -> 12345 // 10 = 1234 17 | - r = num % 10 -> 12345 %10 = 5 18 | - num = number -> 1234 19 | 3. now multiply the previous remainder by 10 and add with the current remainder 20 | - result = result * 10 + r 21 | 4. Repeat steps 2 and 3 until number becomes 0 22 | """ 23 | 24 | 25 | def reverse_digits(number: int) -> int: 26 | result = 0 27 | if number < 0: 28 | raise ValueError("Negative numbers not allowed") 29 | if number < 10: 30 | return number 31 | while number > 0: 32 | number, result = (number // 10, result * 10 + number % 10) 33 | return result 34 | 35 | 36 | if __name__ == "__main__": 37 | print(reverse_digits(123)) # 321 38 | print(reverse_digits(12345)) # 54321 39 | print(reverse_digits(87214)) # 41278 40 | -------------------------------------------------------------------------------- /problem_solving/dp/fibonacci.py: -------------------------------------------------------------------------------- 1 | """ 2 | Find the nth item of a fibonacci series 3 | --------------------------------------- 4 | 5 | Fibonacci Series is a series in which a number in a series is equal to the sum 6 | of last 2 numbers in the series. 7 | 8 | An example of a fibonacci series is 1, 1, 2, 3, 5, 8, 13, 21, 34... 9 | 10 | Nth item of fibonacci series can be solved both using dynamic programming and 11 | recursion however recursive functions will be much slower for higher value of n 12 | so it is recommended to use dynamic programming to find it out. 13 | """ 14 | 15 | 16 | def fibonacci_recursion(n: int) -> int: 17 | if n < 1: 18 | raise ValueError("Negative number is not acceptable") 19 | if n < 3: 20 | return 1 21 | return fibonacci_recursion(n - 1) + fibonacci_recursion(n - 2) 22 | 23 | 24 | def fibonacci(n: int): 25 | if n < 1: 26 | raise ValueError("Negative number is not acceptable") 27 | if n < 3: 28 | return 1 29 | a, b = 1, 1 30 | for n in range(3, n + 1): 31 | a, b = b, a + b 32 | 33 | return b 34 | 35 | 36 | if __name__ == "__main__": 37 | print(fibonacci_recursion(8)) # 21 38 | print(fibonacci_recursion(9)) # 34 39 | print(fibonacci_recursion(10)) # 55 40 | # It takes too long to find out the number for higher value of N 41 | # and eventually maximum depth of recursion will be exceeded in such case 42 | # print(fibonacci_recursion(40)) # 102334155 (very slow) 43 | 44 | print(fibonacci(8)) # 21 45 | print(fibonacci(9)) # 34 46 | print(fibonacci(10)) # 55 47 | print(fibonacci(40)) # 102334155 48 | print(fibonacci(100)) # 354224848179261915075 49 | -------------------------------------------------------------------------------- /projects/solutions/hangman/README.md: -------------------------------------------------------------------------------- 1 | # Hangman Game 2 | -------------------------------------------------------------------------------- /projects/solutions/hangman/game_data.py: -------------------------------------------------------------------------------- 1 | HANGMAN = """\ 2 | ██ ██ ██████ ███ ██ ██████ ██ ██ ██████ ███ ██ 3 | ██ ██ ██ ██ ████ ██ ██ ███ ███ ██ ██ ████ ██ 4 | ████████ ████████ ██ ██ ██ ██ ████ ██ ██ ██ ████████ ██ ██ ██ 5 | ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ████ 6 | ██ ██ ██ ██ ██ ███ ███████ ██ ██ ██ ██ ██ ███ 7 | """ 8 | 9 | WINNER = """\ 10 | ██ ██ ████████ ███ ██ ███ ██ ████████ ███████ 11 | ██ ██ ██ ████ ██ ████ ██ ██ ██ ██ 12 | ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████████ ███████ 13 | ███ ███ ██ ██ ████ ██ ████ ██ ██ ██ 14 | ██ ██ ████████ ██ ███ ██ ███ ████████ ██ ███ 15 | """ 16 | 17 | LOOSER = """\ 18 | ██ ██████ ██████ ███████ ████████ ███████ 19 | ██ ██ ██ ██ ██ ██ ██ ██ ██ 20 | ██ ██ ██ ██ ██ ██████ ████████ ████████ 21 | ██ ██ ██ ██ ██ ██ ██ ██ ██ 22 | ████████ ██████ ██████ ███████ ████████ ██ ███ 23 | """ 24 | -------------------------------------------------------------------------------- /projects/solutions/rock_paper_scissor/README.md: -------------------------------------------------------------------------------- 1 | # Project Rock Paper Scissor 2 | 3 | The project uses `random` library. 4 | the file `game.py` consists of a class `Game` that does all the gaming logic. 5 | 6 | all of the states of the game are defined by instance attributes which are as follows: 7 | 8 | 1. `username`: Used to save the name of the player. 9 | 2. `_consecutive_wins`: Used to track consecutive wins. 10 | 3. `last_win`: Used to track who won the last match. 11 | 4. `_matches`: Used to count the total number of matches. 12 | 5. `_draws`: Used to count total number of draws. 13 | 6. `_bot_score`: Used to count the total number of matches won by the bot 14 | 7. `_user_score`: Used to count the total number of matches won by the user 15 | 8. `user_input`: Used to store the last input of the user 16 | 9. `bot_input`: Used to store the last input of the bot 17 | 18 | ## The `__init__()` method 19 | The `__init__()` function sets the default state whenever initialized. 20 | 21 | ## The `__get_bot_choice()` method: 22 | This method gives the guess of the bot. 23 | 24 | ## The `__display_menu()` method: 25 | This method displays the main menu 26 | 27 | ## Running the game 28 | to run the project, move the terminal to the project folder. 29 | 30 | ```shell 31 | $ cd projects/project_01/rock_paper_scissor 32 | ``` 33 | and then run the `game.py` file using the command below: 34 | ```shell 35 | $ python game.py 36 | ``` 37 | -------------------------------------------------------------------------------- /projects/solutions/school_manager/README.md: -------------------------------------------------------------------------------- 1 | # School Manager 2 | -------------------------------------------------------------------------------- /projects/solutions/school_manager/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | RECORD_PATH = 'records' 4 | 5 | def clear_screen(): 6 | os.system('cls' if os.name == 'nt' else 'clear') 7 | 8 | def display_message(msg: str): 9 | print("+", '-'*78, '+' , sep='') 10 | print("|", msg.center(78), '|' , sep='') 11 | print("+", '-'*78, '+' , sep='') 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name="python-perks" 3 | version = "4.0.1" 4 | description="A repository for Python course notes, examples, and lab exercises targeted to students, professionals, and enthusiasts." 5 | authors=[ 6 | { name="Sudip Ghimire", email="ghimiresdp@gmail.com" }, 7 | ] 8 | license="MIT" 9 | readme="README.md" 10 | requires-python=">=3.10" 11 | 12 | dependencies=[ 13 | "requests>=2.32.3", 14 | ] 15 | 16 | [dependency-groups] 17 | dev=[ 18 | "ipython>=8.31.0", 19 | "ruff>=0.8.4", 20 | ] 21 | test=[ 22 | "pytest>=8.3.4", 23 | ] 24 | 25 | [tool.ruff] 26 | line-length=88 27 | indent-width=4 28 | target-version="py312" 29 | # Exclude a variety of commonly ignored directories. 30 | exclude=[ 31 | ".bzr", 32 | ".direnv", 33 | ".eggs", 34 | ".git", 35 | ".git-rewrite", 36 | ".hg", 37 | ".ipynb_checkpoints", 38 | ".mypy_cache", 39 | ".nox", 40 | ".pants.d", 41 | ".pyenv", 42 | ".pytest_cache", 43 | ".pytype", 44 | ".ruff_cache", 45 | ".svn", 46 | ".tox", 47 | ".venv", 48 | ".vscode", 49 | "__pypackages__", 50 | "_build", 51 | "buck-out", 52 | "build", 53 | "dist", 54 | "node_modules", 55 | "site-packages", 56 | "venv", 57 | ] 58 | 59 | [tool.ruff.format] 60 | quote-style="double" 61 | indent-style="space" 62 | skip-magic-trailing-comma=false 63 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # This file was autogenerated by uv via the following command: 2 | # uv pip compile pyproject.toml -o requirements.txt 3 | certifi==2024.12.14 4 | # via requests 5 | charset-normalizer==3.4.1 6 | # via requests 7 | idna==3.10 8 | # via requests 9 | requests==2.32.3 10 | # via python-perks (pyproject.toml) 11 | urllib3==2.3.0 12 | # via requests 13 | --------------------------------------------------------------------------------