diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..176a458f94e0 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.github/stale.yml b/.github/stale.yml index 22aae982abdc..36ca56266b26 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -1,20 +1,63 @@ -# Number of days of inactivity before an issue becomes stale +# Configuration for probot-stale - https://github.com/probot/stale + +# Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 30 -# Number of days of inactivity before a stale issue is closed + +# Number of days of inactivity before an Issue or Pull Request with the stale label is closed. +# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: 7 -# Issues with these labels will never be considered stale + +# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) +onlyLabels: [] + +# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - - bug - - help wanted - - OK to merge -# Label to use when marking an issue as stale -staleLabel: wontfix -# Comment to post when marking an issue as stale. Set to `false` to disable -markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. -# Comment to post when closing a stale issue. Set to `false` to disable -closeComment: > - Please reopen this issue once you commit the changes requested or - make improvements on the code. Thank you for your contributions. + - "Status: on hold" + +# Set to true to ignore issues in a project (defaults to false) +exemptProjects: false + +# Set to true to ignore issues in a milestone (defaults to false) +exemptMilestones: false + +# Set to true to ignore issues with an assignee (defaults to false) +exemptAssignees: false + +# Label to use when marking as stale +staleLabel: stale + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 5 + +# Comment to post when removing the stale label. +# unmarkComment: > +# Your comment here. + +# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': +pulls: + # Comment to post when marking as stale. Set to `false` to disable + markComment: > + This pull request has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + + # Comment to post when closing a stale Pull Request. + closeComment: > + Please reopen this pull request once you commit the changes requested + or make improvements on the code. If this is not the case and you need + some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) + or ping one of the reviewers. Thank you for your contributions! + +issues: + # Comment to post when marking as stale. Set to `false` to disable + markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + + # Comment to post when closing a stale Issue. + closeComment: > + Please reopen this issue once you add more information and updates here. + If this is not the case and you need some help, feel free to seek help + from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the + reviewers. Thank you for your contributions! diff --git a/.github/workflows/auto_close_empty_issues.yml b/.github/workflows/auto_close_empty_issues.yml deleted file mode 100644 index a6334d6ade32..000000000000 --- a/.github/workflows/auto_close_empty_issues.yml +++ /dev/null @@ -1,20 +0,0 @@ -# GitHub Action that uses close-issue auto-close empty issues after they are opened. -# If the issue body text is empty the Action auto-closes it and sends a notification. -# Otherwise if the issue body is not empty, it does nothing and the issue remains open. -# https://github.com/marketplace/actions/close-issue - -name: auto_close_empty_issues -on: - issues: - types: [opened] -jobs: - check-issue-body-not-empty: - runs-on: ubuntu-latest - steps: - - if: github.event.issue.body == 0 - name: Close Issue - uses: peter-evans/close-issue@v1 - with: - comment: | - Issue body must contain content. - Auto-closing this issue. diff --git a/.github/workflows/autoblack.yml b/.github/workflows/autoblack.yml deleted file mode 100644 index ce34170d44bb..000000000000 --- a/.github/workflows/autoblack.yml +++ /dev/null @@ -1,25 +0,0 @@ -# GitHub Action that uses Black to reformat Python code (if needed) when doing a git push. -# If all Python code in the repo is compliant with Black then this Action does nothing. -# Otherwise, Black is run and its changes are committed to the repo. -# https://github.com/cclauss/autoblack - -name: autoblack_push -on: [push] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 # Use v1, NOT v2 - - uses: actions/setup-python@v2 - - run: pip install black isort - - run: black --check . - - name: If needed, commit black changes to a new pull request - if: failure() - run: | - black . - isort --profile black . - git config --global user.name github-actions - git config --global user.email '${GITHUB_ACTOR}@users.noreply.github.com' - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY - git commit -am "fixup! Format Python code with psf/black push" - git push --force origin HEAD:$GITHUB_REF diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000000..76c6357fe0ca --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: "build" + +on: + pull_request: + schedule: + - cron: "0 0 * * *" # Run everyday + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools six wheel + python -m pip install mypy pytest-cov -r requirements.txt + # FIXME: #4052 fix mypy errors in the exclude directories and remove them below + - run: mypy --ignore-missing-imports + --exclude '(arithmetic_analysis|ciphers|conversions|data_structures|digital_image_processing|dynamic_programming|graphs|hashes|linear_algebra|maths|matrix|other|project_euler|scripts|searches|strings*)/$' . + - name: Run tests + run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . + - if: ${{ success() }} + run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml deleted file mode 100644 index e336f697708c..000000000000 --- a/.github/workflows/codespell.yml +++ /dev/null @@ -1,17 +0,0 @@ -# GitHub Action to automate the identification of common misspellings in text files -# https://github.com/codespell-project/codespell -name: codespell -on: [push, pull_request] -jobs: - codespell: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - run: pip install codespell - - run: | - SKIP="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" - codespell --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim --skip=$SKIP --quiet-level=2 - - name: Codespell comment - if: ${{ failure() }} - uses: plettich/python_codespell_action@master diff --git a/.github/workflows/directory_writer.yml b/.github/workflows/directory_writer.yml index 6547d1c18c79..be8154a32696 100644 --- a/.github/workflows/directory_writer.yml +++ b/.github/workflows/directory_writer.yml @@ -8,8 +8,6 @@ jobs: steps: - uses: actions/checkout@v1 # v1, NOT v2 - uses: actions/setup-python@v2 - with: - python-version: 3.x - name: Write DIRECTORY.md run: | scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7002d2d0a21e..17fdad1204e9 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -7,7 +7,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + - uses: actions/cache@v2 + with: + path: | + ~/.cache/pre-commit + ~/.cache/pip + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 + - uses: psf/black@stable - name: Install pre-commit run: | python -m pip install --upgrade pip diff --git a/.github/workflows/project_euler.yml b/.github/workflows/project_euler.yml new file mode 100644 index 000000000000..995295fcaa9a --- /dev/null +++ b/.github/workflows/project_euler.yml @@ -0,0 +1,35 @@ +on: + pull_request: + # Run only if a file is changed within the project_euler directory and related files + paths: + - "project_euler/**" + - ".github/workflows/project_euler.yml" + - "scripts/validate_solutions.py" + schedule: + - cron: "0 0 * * *" # Run everyday + +name: "Project Euler" + +jobs: + project-euler: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install pytest and pytest-cov + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pytest pytest-cov + - run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ + validate-solutions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - name: Install pytest and requests + run: | + python -m pip install --upgrade pip + python -m pip install --upgrade pytest requests + - run: pytest scripts/validate_solutions.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 4793f54f7af8..000000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Mark stale issues and pull requests -on: - schedule: - - cron: "0 0 * * *" -jobs: - stale: - runs-on: ubuntu-latest - steps: - - uses: actions/stale@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - stale-issue-message: > - Please reopen this issue once you add more information and updates here. - If this is not the case and you need some help, feel free to seek help - from our [Gitter](https://gitter.im/TheAlgorithms) or ping one of the - reviewers. Thank you for your contributions! - stale-pr-message: > - Please reopen this pull request once you commit the changes requested - or make improvements on the code. If this is not the case and you need - some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms) - or ping one of the reviewers. Thank you for your contributions! - stale-issue-label: 'no-issue-activity' - stale-pr-label: 'no-pr-activity' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01da6cad0335..b48da86ee57d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.4.0 hooks: - id: check-executables-have-shebangs - id: check-yaml @@ -13,17 +13,17 @@ repos: )$ - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: stable + rev: 20.8b1 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.5.3 + rev: 5.7.0 hooks: - id: isort args: - --profile=black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.9.0 hooks: - id: flake8 args: @@ -38,17 +38,17 @@ repos: # args: # - --ignore-missing-imports - repo: https://github.com/codespell-project/codespell - rev: v1.17.1 + rev: v2.0.0 hooks: - id: codespell args: - - --ignore-words-list=ans,fo,followings,hist,iff,secant,som,tim - - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" + - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim + - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" - --quiet-level=2 exclude: | (?x)^( - other/dictionary.txt | - other/words | + strings/dictionary.txt | + strings/words.txt | project_euler/problem_022/p022_names.txt )$ - repo: local diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f31dae8467d6..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -os: linux -dist: focal -language: python -python: 3.8 -cache: pip -before_install: pip install --upgrade pip setuptools six -jobs: - include: - - name: Build - install: pip install pytest-cov -r requirements.txt - script: - - pytest --doctest-modules --ignore=project_euler/ --durations=10 --cov-report=term-missing:skip-covered --cov=. . - - name: Project Euler - install: - - pip install pytest-cov - script: - - pytest --doctest-modules --durations=10 --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/ - - name: Project Euler Solution - install: - - pip install pytest - script: - - pytest --tb=short --durations=10 project_euler/validate_solutions.py -after_success: - - scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md -notifications: - webhooks: https://www.travisbuddy.com/ - on_success: never diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e248d09f11c3..76ee1312f345 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,7 +15,7 @@ We are very happy that you consider implementing algorithms and data structure f - Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged - You submitted work fulfils or mostly fulfils our styles and standards -**New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity. +**New implementation** is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity but **identical implementation** of an existing implementation is not allowed. Please check whether the solution is already implemented or not before submitting your pull request. **Improving comments** and **writing proper tests** are also highly welcome. @@ -49,6 +49,19 @@ Algorithms should: Algorithms in this repo should not be how-to examples for existing Python packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing Python packages but each algorithm in this repo should add unique value. +#### Pre-commit plugin +Use [pre-commit](https://pre-commit.com/#installation) to automatically format your code to match our coding style: + +```bash +python3 -m pip install pre-commit # required only once +pre-commit install +``` +That's it! The plugin will run every time you commit any changes. If there are any errors found during the run, fix them and commit those changes. You can even run the plugin manually on all files: + +```bash +pre-commit run --all-files --show-diff-on-failure +``` + #### Coding Style We want your work to be readable by others; therefore, we encourage you to note the following: @@ -64,14 +77,14 @@ We want your work to be readable by others; therefore, we encourage you to note - Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ formatter is now hosted by the Python Software Foundation. To use it, ```bash - pip3 install black # only required the first time + python3 -m pip install black # only required the first time black . ``` - All submissions will need to pass the test __flake8 . --ignore=E203,W503 --max-line-length=88__ before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request. ```bash - pip3 install flake8 # only required the first time + python3 -m pip install flake8 # only required the first time flake8 . --ignore=E203,W503 --max-line-length=88 --show-source ``` @@ -142,13 +155,15 @@ We want your work to be readable by others; therefore, we encourage you to note return a + b ``` + Instructions on how to install mypy can be found [here](https://github.com/python/mypy). Please use the command `mypy --ignore-missing-imports .` to test all files or `mypy --ignore-missing-imports path/to/file.py` to test a specific file. + - [__List comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. - Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. - If you need a third party module that is not in the file __requirements.txt__, please add it to that file as part of your submission. #### Other Requirements for Submissions - +- If you are submitting code in the `project_euler/` directory, please also read [the dedicated Guideline](https://github.com/TheAlgorithms/Python/blob/master/project_euler/README.md) before contributing to our Project Euler library. - The file extension for code files should be `.py`. Jupyter Notebooks should be submitted to [TheAlgorithms/Jupyter](https://github.com/TheAlgorithms/Jupyter). - Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts. - Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure. diff --git a/DIRECTORY.md b/DIRECTORY.md index 4e67ad5156d9..42a6c49c735f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -26,8 +26,15 @@ ## Bit Manipulation * [Binary And Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_and_operator.py) + * [Binary Count Setbits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_setbits.py) + * [Binary Count Trailing Zeros](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_trailing_zeros.py) * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) + * [Binary Shifts](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_shifts.py) + * [Binary Twos Complement](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_twos_complement.py) * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) + * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) + * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) + * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) ## Blockchain * [Chinese Remainder Theorem](https://github.com/TheAlgorithms/Python/blob/master/blockchain/chinese_remainder_theorem.py) @@ -38,6 +45,8 @@ * [Quine Mc Cluskey](https://github.com/TheAlgorithms/Python/blob/master/boolean_algebra/quine_mc_cluskey.py) ## Cellular Automata + * [Conways Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/conways_game_of_life.py) + * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/game_of_life.py) * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) ## Ciphers @@ -46,18 +55,21 @@ * [Atbash](https://github.com/TheAlgorithms/Python/blob/master/ciphers/atbash.py) * [Base16](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base16.py) * [Base32](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base32.py) - * [Base64 Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_cipher.py) + * [Base64 Encoding](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base64_encoding.py) * [Base85](https://github.com/TheAlgorithms/Python/blob/master/ciphers/base85.py) + * [Beaufort Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/beaufort_cipher.py) * [Brute Force Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/brute_force_caesar_cipher.py) * [Caesar Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/caesar_cipher.py) * [Cryptomath Module](https://github.com/TheAlgorithms/Python/blob/master/ciphers/cryptomath_module.py) * [Decrypt Caesar With Chi Squared](https://github.com/TheAlgorithms/Python/blob/master/ciphers/decrypt_caesar_with_chi_squared.py) * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) + * [Diffie Hellman](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie_hellman.py) * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) * [Enigma Machine2](https://github.com/TheAlgorithms/Python/blob/master/ciphers/enigma_machine2.py) * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) * [Mixed Keyword Cypher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mixed_keyword_cypher.py) + * [Mono Alphabetic Ciphers](https://github.com/TheAlgorithms/Python/blob/master/ciphers/mono_alphabetic_ciphers.py) * [Morse Code Implementation](https://github.com/TheAlgorithms/Python/blob/master/ciphers/morse_code_implementation.py) * [Onepad Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/onepad_cipher.py) * [Playfair Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/playfair_cipher.py) @@ -86,6 +98,7 @@ ## Computer Vision * [Harriscorner](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/harriscorner.py) + * [Meanthreshold](https://github.com/TheAlgorithms/Python/blob/master/computer_vision/meanthreshold.py) ## Conversions * [Binary To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/binary_to_decimal.py) @@ -96,9 +109,12 @@ * [Decimal To Hexadecimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_hexadecimal.py) * [Decimal To Octal](https://github.com/TheAlgorithms/Python/blob/master/conversions/decimal_to_octal.py) * [Hexadecimal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/hexadecimal_to_decimal.py) + * [Molecular Chemistry](https://github.com/TheAlgorithms/Python/blob/master/conversions/molecular_chemistry.py) + * [Octal To Decimal](https://github.com/TheAlgorithms/Python/blob/master/conversions/octal_to_decimal.py) * [Prefix Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/prefix_conversions.py) - * [Roman To Integer](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_to_integer.py) + * [Roman Numerals](https://github.com/TheAlgorithms/Python/blob/master/conversions/roman_numerals.py) * [Temperature Conversions](https://github.com/TheAlgorithms/Python/blob/master/conversions/temperature_conversions.py) + * [Weight Conversion](https://github.com/TheAlgorithms/Python/blob/master/conversions/weight_conversion.py) ## Data Structures * Binary Tree @@ -111,6 +127,7 @@ * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) + * [Merge Two Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/merge_two_binary_trees.py) * [Non Recursive Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/non_recursive_segment_tree.py) * [Number Of Possible Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/number_of_possible_binary_trees.py) * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) @@ -139,9 +156,11 @@ * [Circular Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/circular_linked_list.py) * [Deque Doubly](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/deque_doubly.py) * [Doubly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list.py) + * [Doubly Linked List Two](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/doubly_linked_list_two.py) * [From Sequence](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/from_sequence.py) * [Has Loop](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/has_loop.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/is_palindrome.py) + * [Merge Two Lists](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/merge_two_lists.py) * [Middle Element Of Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/middle_element_of_linked_list.py) * [Print Reverse](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/print_reverse.py) * [Singly Linked List](https://github.com/TheAlgorithms/Python/blob/master/data_structures/linked_list/singly_linked_list.py) @@ -157,6 +176,7 @@ * Stacks * [Balanced Parentheses](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/balanced_parentheses.py) * [Dijkstras Two Stack Algorithm](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/dijkstras_two_stack_algorithm.py) + * [Evaluate Postfix Notations](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/evaluate_postfix_notations.py) * [Infix To Postfix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_postfix_conversion.py) * [Infix To Prefix Conversion](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/infix_to_prefix_conversion.py) * [Linked Stack](https://github.com/TheAlgorithms/Python/blob/master/data_structures/stacks/linked_stack.py) @@ -199,8 +219,11 @@ * [Heaps Algorithm](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm.py) * [Heaps Algorithm Iterative](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/heaps_algorithm_iterative.py) * [Inversions](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/inversions.py) + * [Kth Order Statistic](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/kth_order_statistic.py) + * [Max Difference Pair](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_difference_pair.py) * [Max Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/max_subarray_sum.py) * [Mergesort](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/mergesort.py) + * [Peak](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/peak.py) * [Power](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/power.py) * [Strassen Matrix Multiplication](https://github.com/TheAlgorithms/Python/blob/master/divide_and_conquer/strassen_matrix_multiplication.py) @@ -208,7 +231,6 @@ * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) - * [Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) * [Edit Distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) * [Factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) * [Fast Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) @@ -227,19 +249,30 @@ * [Max Non Adjacent Sum](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_non_adjacent_sum.py) * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) + * [Minimum Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_coin_change.py) * [Minimum Cost Path](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_cost_path.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) + * [Minimum Steps To One](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_steps_to_one.py) * [Optimal Binary Search Tree](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/optimal_binary_search_tree.py) * [Rod Cutting](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/rod_cutting.py) * [Subset Generation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/subset_generation.py) * [Sum Of Subset](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/sum_of_subset.py) +## Electronics + * [Electric Power](https://github.com/TheAlgorithms/Python/blob/master/electronics/electric_power.py) + * [Ohms Law](https://github.com/TheAlgorithms/Python/blob/master/electronics/ohms_law.py) + ## File Transfer * [Receive File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/receive_file.py) * [Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/send_file.py) * Tests * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) +## Fractals + * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/fractals/koch_snowflake.py) + * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/fractals/mandelbrot.py) + * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/fractals/sierpinski_triangle.py) + ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) @@ -260,6 +293,7 @@ * [Basic Graphs](https://github.com/TheAlgorithms/Python/blob/master/graphs/basic_graphs.py) * [Bellman Ford](https://github.com/TheAlgorithms/Python/blob/master/graphs/bellman_ford.py) * [Bfs Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_shortest_path.py) + * [Bfs Zero One Shortest Path](https://github.com/TheAlgorithms/Python/blob/master/graphs/bfs_zero_one_shortest_path.py) * [Bidirectional A Star](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_a_star.py) * [Bidirectional Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/bidirectional_breadth_first_search.py) * [Breadth First Search](https://github.com/TheAlgorithms/Python/blob/master/graphs/breadth_first_search.py) @@ -289,6 +323,7 @@ * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) * [Karger](https://github.com/TheAlgorithms/Python/blob/master/graphs/karger.py) + * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/graphs/markov_chain.py) * [Minimum Spanning Tree Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_boruvka.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) * [Minimum Spanning Tree Kruskal2](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal2.py) @@ -304,10 +339,6 @@ * [Test Min Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_kruskal.py) * [Test Min Spanning Tree Prim](https://github.com/TheAlgorithms/Python/blob/master/graphs/tests/test_min_spanning_tree_prim.py) -## Greedy Method - * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/greedy_knapsack.py) - * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/greedy_method/test_knapsack.py) - ## Hashes * [Adler32](https://github.com/TheAlgorithms/Python/blob/master/hashes/adler32.py) * [Chaos Machine](https://github.com/TheAlgorithms/Python/blob/master/hashes/chaos_machine.py) @@ -318,8 +349,16 @@ * [Sdbm](https://github.com/TheAlgorithms/Python/blob/master/hashes/sdbm.py) * [Sha1](https://github.com/TheAlgorithms/Python/blob/master/hashes/sha1.py) +## Knapsack + * [Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/greedy_knapsack.py) + * [Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/knapsack.py) + * Tests + * [Test Greedy Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/tests/test_greedy_knapsack.py) + * [Test Knapsack](https://github.com/TheAlgorithms/Python/blob/master/knapsack/tests/test_knapsack.py) + ## Linear Algebra * Src + * [Conjugate Gradient](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/conjugate_gradient.py) * [Lib](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/lib.py) * [Polynom For Points](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/polynom_for_points.py) * [Power Iteration](https://github.com/TheAlgorithms/Python/blob/master/linear_algebra/src/power_iteration.py) @@ -331,6 +370,8 @@ * [Astar](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/astar.py) * [Data Transformations](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/data_transformations.py) * [Decision Tree](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/decision_tree.py) + * Forecasting + * [Run](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/forecasting/run.py) * [Gaussian Naive Bayes](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gaussian_naive_bayes.py) * [Gradient Boosting Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_boosting_regressor.py) * [Gradient Descent](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/gradient_descent.py) @@ -340,14 +381,13 @@ * [Linear Discriminant Analysis](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_discriminant_analysis.py) * [Linear Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/linear_regression.py) * [Logistic Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/logistic_regression.py) - * Lstm - * [Lstm Prediction](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/lstm/lstm_prediction.py) * [Multilayer Perceptron Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/multilayer_perceptron_classifier.py) * [Polymonial Regression](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/polymonial_regression.py) * [Random Forest Classifier](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_classifier.py) * [Random Forest Regressor](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/random_forest_regressor.py) * [Scoring Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/scoring_functions.py) * [Sequential Minimum Optimization](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/sequential_minimum_optimization.py) + * [Similarity Search](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/similarity_search.py) * [Support Vector Machines](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/support_vector_machines.py) * [Word Frequency Functions](https://github.com/TheAlgorithms/Python/blob/master/machine_learning/word_frequency_functions.py) @@ -369,6 +409,8 @@ * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) * [Binary Exp Mod](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) + * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_2.py) + * [Binary Exponentiation 3](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_3.py) * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) * [Binomial Distribution](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_distribution.py) * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/maths/bisection.py) @@ -376,9 +418,12 @@ * [Chudnovsky Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/chudnovsky_algorithm.py) * [Collatz Sequence](https://github.com/TheAlgorithms/Python/blob/master/maths/collatz_sequence.py) * [Combinations](https://github.com/TheAlgorithms/Python/blob/master/maths/combinations.py) + * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) * [Entropy](https://github.com/TheAlgorithms/Python/blob/master/maths/entropy.py) + * [Euclidean Distance](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_distance.py) + * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_gcd.py) + * [Euler Method](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_method.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) - * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) * [Factorial Iterative](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_iterative.py) * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) @@ -395,7 +440,9 @@ * [Gamma](https://github.com/TheAlgorithms/Python/blob/master/maths/gamma.py) * [Gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) + * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.py) * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) + * [Integration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/maths/integration_by_simpson_approx.py) * [Is Square Free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) * [Kadanes](https://github.com/TheAlgorithms/Python/blob/master/maths/kadanes.py) @@ -403,11 +450,14 @@ * [Krishnamurthy Number](https://github.com/TheAlgorithms/Python/blob/master/maths/krishnamurthy_number.py) * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) + * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_subarray_sum.py) * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) * [Line Length](https://github.com/TheAlgorithms/Python/blob/master/maths/line_length.py) * [Lucas Lehmer Primality Test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) * [Lucas Series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) * [Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/matrix_exponentiation.py) + * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/maths/max_sum_sliding_window.py) + * [Median Of Two Arrays](https://github.com/TheAlgorithms/Python/blob/master/maths/median_of_two_arrays.py) * [Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/maths/miller_rabin.py) * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) @@ -426,6 +476,7 @@ * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) + * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/maths/primelib.py) * [Pythagoras](https://github.com/TheAlgorithms/Python/blob/master/maths/pythagoras.py) * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) @@ -435,10 +486,13 @@ * [Runge Kutta](https://github.com/TheAlgorithms/Python/blob/master/maths/runge_kutta.py) * [Segmented Sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) * Series + * [Arithmetic Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/arithmetic_mean.py) + * [Geometric Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_mean.py) * [Geometric Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_series.py) * [Harmonic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic_series.py) * [P Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/p_series.py) * [Sieve Of Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/sieve_of_eratosthenes.py) + * [Sigmoid](https://github.com/TheAlgorithms/Python/blob/master/maths/sigmoid.py) * [Simpson Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/simpson_rule.py) * [Softmax](https://github.com/TheAlgorithms/Python/blob/master/maths/softmax.py) * [Square Root](https://github.com/TheAlgorithms/Python/blob/master/maths/square_root.py) @@ -447,6 +501,9 @@ * [Sum Of Geometric Progression](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_geometric_progression.py) * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/triplet_sum.py) + * [Two Pointer](https://github.com/TheAlgorithms/Python/blob/master/maths/two_pointer.py) + * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/two_sum.py) * [Ugly Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/ugly_numbers.py) * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) @@ -469,45 +526,30 @@ * [Minimum Cut](https://github.com/TheAlgorithms/Python/blob/master/networking_flow/minimum_cut.py) ## Neural Network + * [2 Hidden Layers Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/2_hidden_layers_neural_network.py) * [Back Propagation Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/back_propagation_neural_network.py) * [Convolution Neural Network](https://github.com/TheAlgorithms/Python/blob/master/neural_network/convolution_neural_network.py) * [Perceptron](https://github.com/TheAlgorithms/Python/blob/master/neural_network/perceptron.py) ## Other * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) - * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) - * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/other/autocomplete_using_trie.py) - * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) - * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) - * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) + * [Davis–Putnam–Logemann–Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davis–putnam–logemann–loveland.py) * [Dijkstra Bankers Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/dijkstra_bankers_algorithm.py) * [Doomsday](https://github.com/TheAlgorithms/Python/blob/master/other/doomsday.py) - * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) - * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [Gauss Easter](https://github.com/TheAlgorithms/Python/blob/master/other/gauss_easter.py) + * [Graham Scan](https://github.com/TheAlgorithms/Python/blob/master/other/graham_scan.py) * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) - * [Integeration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/other/integeration_by_simpson_approx.py) - * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) * [Lfu Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lfu_cache.py) * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) * [Lru Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lru_cache.py) * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) - * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/other/markov_chain.py) - * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/other/max_sum_sliding_window.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) - * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) - * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) * [Scoring Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/scoring_algorithm.py) * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) - * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) - * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/other/triplet_sum.py) - * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) - * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) ## Project Euler * Problem 001 @@ -620,6 +662,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_036/sol1.py) * Problem 037 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_037/sol1.py) + * Problem 038 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_038/sol1.py) * Problem 039 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_039/sol1.py) * Problem 040 @@ -642,6 +686,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_048/sol1.py) * Problem 049 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_049/sol1.py) + * Problem 050 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_050/sol1.py) * Problem 051 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_051/sol1.py) * Problem 052 @@ -655,28 +701,66 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_055/sol1.py) * Problem 056 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_056/sol1.py) + * Problem 057 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_057/sol1.py) + * Problem 058 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_058/sol1.py) + * Problem 059 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_059/sol1.py) * Problem 062 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_062/sol1.py) * Problem 063 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_063/sol1.py) + * Problem 064 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_064/sol1.py) + * Problem 065 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_065/sol1.py) * Problem 067 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_067/sol1.py) * Problem 069 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_069/sol1.py) + * Problem 070 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_070/sol1.py) * Problem 071 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_071/sol1.py) * Problem 072 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_072/sol2.py) * Problem 074 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_074/sol1.py) + * [Sol2](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_074/sol2.py) + * Problem 075 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_075/sol1.py) * Problem 076 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_076/sol1.py) + * Problem 077 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_077/sol1.py) * Problem 080 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_080/sol1.py) + * Problem 081 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_081/sol1.py) + * Problem 085 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_085/sol1.py) + * Problem 086 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_086/sol1.py) + * Problem 087 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_087/sol1.py) + * Problem 089 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_089/sol1.py) + * Problem 091 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_091/sol1.py) * Problem 097 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_097/sol1.py) * Problem 099 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_099/sol1.py) + * Problem 101 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_101/sol1.py) + * Problem 102 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_102/sol1.py) + * Problem 107 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_107/sol1.py) + * Problem 109 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_109/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 113 @@ -685,21 +769,45 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) * Problem 120 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) + * Problem 121 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_121/sol1.py) + * Problem 123 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_123/sol1.py) * Problem 125 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_125/sol1.py) + * Problem 129 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_129/sol1.py) + * Problem 135 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_135/sol1.py) * Problem 173 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_173/sol1.py) + * Problem 174 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_174/sol1.py) + * Problem 180 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_180/sol1.py) + * Problem 188 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_188/sol1.py) * Problem 191 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_191/sol1.py) + * Problem 203 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_203/sol1.py) + * Problem 206 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_206/sol1.py) + * Problem 207 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_207/sol1.py) * Problem 234 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_234/sol1.py) + * Problem 301 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_301/sol1.py) * Problem 551 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_551/sol1.py) - * [Validate Solutions](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py) ## Quantum + * [Deutsch Jozsa](https://github.com/TheAlgorithms/Python/blob/master/quantum/deutsch_jozsa.py) + * [Half Adder](https://github.com/TheAlgorithms/Python/blob/master/quantum/half_adder.py) * [Not Gate](https://github.com/TheAlgorithms/Python/blob/master/quantum/not_gate.py) * [Quantum Entanglement](https://github.com/TheAlgorithms/Python/blob/master/quantum/quantum_entanglement.py) + * [Ripple Adder Classic](https://github.com/TheAlgorithms/Python/blob/master/quantum/ripple_adder_classic.py) * [Single Qubit Measure](https://github.com/TheAlgorithms/Python/blob/master/quantum/single_qubit_measure.py) ## Scheduling @@ -709,6 +817,7 @@ ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [Binary Tree Traversal](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_tree_traversal.py) * [Double Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search.py) * [Double Linear Search Recursion](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search_recursion.py) * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) @@ -738,13 +847,16 @@ * [Gnome Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/gnome_sort.py) * [Heap Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/heap_sort.py) * [Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/insertion_sort.py) + * [Intro Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/intro_sort.py) * [Iterative Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/iterative_merge_sort.py) * [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) * [Natural Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/natural_sort.py) + * [Odd Even Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_sort.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) + * [Patience Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/patience_sort.py) * [Pigeon Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeon_sort.py) * [Pigeonhole Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pigeonhole_sort.py) * [Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/quick_sort.py) @@ -757,6 +869,7 @@ * [Recursive Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_quick_sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [Slowsort](https://github.com/TheAlgorithms/Python/blob/master/sorts/slowsort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) * [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py) * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) @@ -767,11 +880,15 @@ ## Strings * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) + * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/anagrams.py) + * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/strings/autocomplete_using_trie.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) * [Can String Be Rearranged As Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](https://github.com/TheAlgorithms/Python/blob/master/strings/capitalize.py) * [Check Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/check_anagrams.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) + * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) + * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/is_palindrome.py) * [Jaro Winkler](https://github.com/TheAlgorithms/Python/blob/master/strings/jaro_winkler.py) * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) @@ -780,23 +897,25 @@ * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) + * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/palindrome.py) * [Prefix Function](https://github.com/TheAlgorithms/Python/blob/master/strings/prefix_function.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) + * [Reverse Letters](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_letters.py) * [Reverse Words](https://github.com/TheAlgorithms/Python/blob/master/strings/reverse_words.py) * [Split](https://github.com/TheAlgorithms/Python/blob/master/strings/split.py) * [Swap Case](https://github.com/TheAlgorithms/Python/blob/master/strings/swap_case.py) * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) + * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.py) * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) -## Traversals - * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) - ## Web Programming * [Co2 Emission](https://github.com/TheAlgorithms/Python/blob/master/web_programming/co2_emission.py) * [Covid Stats Via Xpath](https://github.com/TheAlgorithms/Python/blob/master/web_programming/covid_stats_via_xpath.py) * [Crawl Google Results](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_results.py) + * [Crawl Google Scholar Citation](https://github.com/TheAlgorithms/Python/blob/master/web_programming/crawl_google_scholar_citation.py) + * [Currency Converter](https://github.com/TheAlgorithms/Python/blob/master/web_programming/currency_converter.py) * [Current Stock Price](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_stock_price.py) * [Current Weather](https://github.com/TheAlgorithms/Python/blob/master/web_programming/current_weather.py) * [Daily Horoscope](https://github.com/TheAlgorithms/Python/blob/master/web_programming/daily_horoscope.py) @@ -807,6 +926,9 @@ * [Get Imdb Top 250 Movies Csv](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdb_top_250_movies_csv.py) * [Get Imdbtop](https://github.com/TheAlgorithms/Python/blob/master/web_programming/get_imdbtop.py) * [Instagram Crawler](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_crawler.py) + * [Instagram Pic](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_pic.py) + * [Instagram Video](https://github.com/TheAlgorithms/Python/blob/master/web_programming/instagram_video.py) * [Recaptcha Verification](https://github.com/TheAlgorithms/Python/blob/master/web_programming/recaptcha_verification.py) * [Slack Message](https://github.com/TheAlgorithms/Python/blob/master/web_programming/slack_message.py) + * [Test Fetch Github Info](https://github.com/TheAlgorithms/Python/blob/master/web_programming/test_fetch_github_info.py) * [World Covid19 Stats](https://github.com/TheAlgorithms/Python/blob/master/web_programming/world_covid19_stats.py) diff --git a/LICENSE.md b/LICENSE.md index 3b7951527ab3..c3c2857cd312 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 The Algorithms +Copyright (c) 2016-2021 The Algorithms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index ac98b6371682..f81031b53ebb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # The Algorithms - Python -[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python) +[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/TheAlgorithms/Python)  [![Gitter chat](https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square)](https://gitter.im/TheAlgorithms)  -[![Build Status](https://img.shields.io/travis/TheAlgorithms/Python.svg?label=Travis%20CI&logo=travis&style=flat-square)](https://travis-ci.com/TheAlgorithms/Python)  +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/TheAlgorithms/Python/build?label=CI&logo=github&style=flat-square)](https://github.com/TheAlgorithms/Python/actions)  [![LGTM](https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square)](https://lgtm.com/projects/g/TheAlgorithms/Python/alerts)  [![contributions welcome](https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square)](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md)  [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square)](https://www.paypal.me/TheAlgorithms/100)  ![](https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square)  -[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square)](https://github.com/pre-commit/pre-commit) -[![code style: black](https://img.shields.io/static/v1?label=code%20style&message=black&color=black&style=flat-square)](https://github.com/psf/black) - +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square)](https://github.com/pre-commit/pre-commit)  +[![code style: black](https://img.shields.io/static/v1?label=code%20style&message=black&color=black&style=flat-square)](https://github.com/psf/black)  + ### All algorithms implemented in Python (for education) diff --git a/Travis_CI_tests_are_failing.md b/Travis_CI_tests_are_failing.md deleted file mode 100644 index 10bf5a6655d2..000000000000 --- a/Travis_CI_tests_are_failing.md +++ /dev/null @@ -1,9 +0,0 @@ -# Travis CI test are failing -### How do I find out what is wrong with my pull request? -1. In your PR look for the failing test and click the `Details` link: ![Travis_CI_fail_1.png](images/Travis_CI_fail_1.png) -2. On the next page, click `The build failed` link: ![Travis_CI_fail_2.png](images/Travis_CI_fail_2.png) -3. Now scroll down and look for `red` text describing the error(s) in the test log. - -Pull requests will __not__ be merged if the Travis CI tests are failing. - -If anything is unclear, please read through [CONTRIBUTING.md](CONTRIBUTING.md) and attempt to run the failing tests on your computer before asking for assistance. diff --git a/arithmetic_analysis/in_static_equilibrium.py b/arithmetic_analysis/in_static_equilibrium.py index f08b39c3505c..9b2892151850 100644 --- a/arithmetic_analysis/in_static_equilibrium.py +++ b/arithmetic_analysis/in_static_equilibrium.py @@ -1,19 +1,14 @@ """ Checks if a system of forces is in static equilibrium. - -python/black : true -flake8 : passed -mypy : passed """ +from typing import List -from __future__ import annotations - -from numpy import array, cos, cross, radians, sin # type: ignore +from numpy import array, cos, cross, radians, sin def polar_force( magnitude: float, angle: float, radian_mode: bool = False -) -> list[float]: +) -> List[float]: """ Resolves force along rectangular components. (force, angle) => (force_x, force_y) diff --git a/arithmetic_analysis/lu_decomposition.py b/arithmetic_analysis/lu_decomposition.py index 763ba60f32b7..ef37d1b7b4ef 100644 --- a/arithmetic_analysis/lu_decomposition.py +++ b/arithmetic_analysis/lu_decomposition.py @@ -1,34 +1,64 @@ -"""Lower-Upper (LU) Decomposition.""" +"""Lower-Upper (LU) Decomposition. -# lower–upper (LU) decomposition - https://en.wikipedia.org/wiki/LU_decomposition -import numpy +Reference: +- https://en.wikipedia.org/wiki/LU_decomposition +""" +from typing import Tuple +import numpy as np +from numpy import ndarray -def LUDecompose(table): + +def lower_upper_decomposition(table: ndarray) -> Tuple[ndarray, ndarray]: + """Lower-Upper (LU) Decomposition + + Example: + + >>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) + >>> outcome = lower_upper_decomposition(matrix) + >>> outcome[0] + array([[1. , 0. , 0. ], + [0. , 1. , 0. ], + [2.5, 8. , 1. ]]) + >>> outcome[1] + array([[ 2. , -2. , 1. ], + [ 0. , 1. , 2. ], + [ 0. , 0. , -17.5]]) + + >>> matrix = np.array([[2, -2, 1], [0, 1, 2]]) + >>> lower_upper_decomposition(matrix) + Traceback (most recent call last): + ... + ValueError: 'table' has to be of square shaped array but got a 2x3 array: + [[ 2 -2 1] + [ 0 1 2]] + """ # Table that contains our data # Table has to be a square array so we need to check first - rows, columns = numpy.shape(table) - L = numpy.zeros((rows, columns)) - U = numpy.zeros((rows, columns)) + rows, columns = np.shape(table) if rows != columns: - return [] + raise ValueError( + f"'table' has to be of square shaped array but got a {rows}x{columns} " + + f"array:\n{table}" + ) + lower = np.zeros((rows, columns)) + upper = np.zeros((rows, columns)) for i in range(columns): for j in range(i): - sum = 0 + total = 0 for k in range(j): - sum += L[i][k] * U[k][j] - L[i][j] = (table[i][j] - sum) / U[j][j] - L[i][i] = 1 + total += lower[i][k] * upper[k][j] + lower[i][j] = (table[i][j] - total) / upper[j][j] + lower[i][i] = 1 for j in range(i, columns): - sum1 = 0 + total = 0 for k in range(i): - sum1 += L[i][k] * U[k][j] - U[i][j] = table[i][j] - sum1 - return L, U + total += lower[i][k] * upper[k][j] + upper[i][j] = table[i][j] - total + return lower, upper if __name__ == "__main__": - matrix = numpy.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) - L, U = LUDecompose(matrix) - print(L) - print(U) + import doctest + + doctest.testmod() diff --git a/arithmetic_analysis/newton_forward_interpolation.py b/arithmetic_analysis/newton_forward_interpolation.py index d32e3efbd1f2..66cde4b73c4f 100644 --- a/arithmetic_analysis/newton_forward_interpolation.py +++ b/arithmetic_analysis/newton_forward_interpolation.py @@ -1,10 +1,11 @@ # https://www.geeksforgeeks.org/newton-forward-backward-interpolation/ import math +from typing import List # for calculating u value -def ucal(u, p): +def ucal(u: float, p: int) -> float: """ >>> ucal(1, 2) 0 @@ -19,9 +20,9 @@ def ucal(u, p): return temp -def main(): +def main() -> None: n = int(input("enter the numbers of values: ")) - y = [] + y: List[List[float]] = [] for i in range(n): y.append([]) for i in range(n): diff --git a/arithmetic_analysis/newton_raphson.py b/arithmetic_analysis/newton_raphson.py index 948759a09a2a..146bb0aa5adf 100644 --- a/arithmetic_analysis/newton_raphson.py +++ b/arithmetic_analysis/newton_raphson.py @@ -4,11 +4,14 @@ # quickly find a good approximation for the root of a real-valued function from decimal import Decimal from math import * # noqa: F401, F403 +from typing import Union from sympy import diff -def newton_raphson(func: str, a: int, precision: int = 10 ** -10) -> float: +def newton_raphson( + func: str, a: Union[float, Decimal], precision: float = 10 ** -10 +) -> float: """Finds root from the point 'a' onwards by Newton-Raphson method >>> newton_raphson("sin(x)", 2) 3.1415926536808043 diff --git a/arithmetic_analysis/secant_method.py b/arithmetic_analysis/secant_method.py index b05d44c627d8..7eb1dd8f5c6b 100644 --- a/arithmetic_analysis/secant_method.py +++ b/arithmetic_analysis/secant_method.py @@ -1,28 +1,29 @@ -# Implementing Secant method in Python -# Author: dimgrichr - - -from math import exp - - -def f(x): - """ - >>> f(5) - 39.98652410600183 - """ - return 8 * x - 2 * exp(-x) - - -def SecantMethod(lower_bound, upper_bound, repeats): - """ - >>> SecantMethod(1, 3, 2) - 0.2139409276214589 - """ - x0 = lower_bound - x1 = upper_bound - for i in range(0, repeats): - x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) - return x1 - - -print(f"The solution is: {SecantMethod(1, 3, 2)}") +""" +Implementing Secant method in Python +Author: dimgrichr +""" +from math import exp + + +def f(x: float) -> float: + """ + >>> f(5) + 39.98652410600183 + """ + return 8 * x - 2 * exp(-x) + + +def secant_method(lower_bound: float, upper_bound: float, repeats: int) -> float: + """ + >>> secant_method(1, 3, 2) + 0.2139409276214589 + """ + x0 = lower_bound + x1 = upper_bound + for i in range(0, repeats): + x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) + return x1 + + +if __name__ == "__main__": + print(f"Example: {secant_method(1, 3, 2) = }") diff --git a/backtracking/all_combinations.py b/backtracking/all_combinations.py index 854dc5198422..76462837ce35 100644 --- a/backtracking/all_combinations.py +++ b/backtracking/all_combinations.py @@ -3,20 +3,27 @@ numbers out of 1 ... n. We use backtracking to solve this problem. Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))) """ +from typing import List -def generate_all_combinations(n: int, k: int) -> [[int]]: +def generate_all_combinations(n: int, k: int) -> List[List[int]]: """ >>> generate_all_combinations(n=4, k=2) [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] """ - result = [] + result: List[List[int]] = [] create_all_state(1, n, k, [], result) return result -def create_all_state(increment, total_number, level, current_list, total_list): +def create_all_state( + increment: int, + total_number: int, + level: int, + current_list: List[int], + total_list: List[List[int]], +) -> None: if level == 0: total_list.append(current_list[:]) return @@ -27,7 +34,7 @@ def create_all_state(increment, total_number, level, current_list, total_list): current_list.pop() -def print_all_state(total_list): +def print_all_state(total_list: List[List[int]]) -> None: for i in total_list: print(*i) diff --git a/backtracking/all_permutations.py b/backtracking/all_permutations.py index 5244fef97f93..a0032c5ca814 100644 --- a/backtracking/all_permutations.py +++ b/backtracking/all_permutations.py @@ -5,13 +5,19 @@ Time complexity: O(n! * n), where n denotes the length of the given sequence. """ +from typing import List, Union -def generate_all_permutations(sequence): +def generate_all_permutations(sequence: List[Union[int, str]]) -> None: create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))]) -def create_state_space_tree(sequence, current_sequence, index, index_used): +def create_state_space_tree( + sequence: List[Union[int, str]], + current_sequence: List[Union[int, str]], + index: int, + index_used: List[int], +) -> None: """ Creates a state space tree to iterate through each branch using DFS. We know that each state has exactly len(sequence) - index children. @@ -38,8 +44,8 @@ def create_state_space_tree(sequence, current_sequence, index, index_used): sequence = list(map(int, input().split())) """ -sequence = [3, 1, 2, 4] +sequence: List[Union[int, str]] = [3, 1, 2, 4] generate_all_permutations(sequence) -sequence = ["A", "B", "C"] -generate_all_permutations(sequence) +sequence_2: List[Union[int, str]] = ["A", "B", "C"] +generate_all_permutations(sequence_2) diff --git a/backtracking/all_subsequences.py b/backtracking/all_subsequences.py index 9086e3a3d659..99db4ea46589 100644 --- a/backtracking/all_subsequences.py +++ b/backtracking/all_subsequences.py @@ -1,12 +1,11 @@ -from typing import Any, List - """ - In this problem, we want to determine all possible subsequences - of the given sequence. We use backtracking to solve this problem. +In this problem, we want to determine all possible subsequences +of the given sequence. We use backtracking to solve this problem. - Time complexity: O(2^n), - where n denotes the length of the given sequence. +Time complexity: O(2^n), +where n denotes the length of the given sequence. """ +from typing import Any, List def generate_all_subsequences(sequence: List[Any]) -> None: @@ -32,15 +31,10 @@ def create_state_space_tree( current_subsequence.pop() -""" -remove the comment to take an input from the user - -print("Enter the elements") -sequence = list(map(int, input().split())) -""" - -sequence = [3, 1, 2, 4] -generate_all_subsequences(sequence) +if __name__ == "__main__": + seq: List[Any] = [3, 1, 2, 4] + generate_all_subsequences(seq) -sequence = ["A", "B", "C"] -generate_all_subsequences(sequence) + seq.clear() + seq.extend(["A", "B", "C"]) + generate_all_subsequences(seq) diff --git a/backtracking/coloring.py b/backtracking/coloring.py index ceaffe3fae76..3956b21a9182 100644 --- a/backtracking/coloring.py +++ b/backtracking/coloring.py @@ -5,11 +5,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring """ -from __future__ import annotations +from typing import List def valid_coloring( - neighbours: list[int], colored_vertices: list[int], color: int + neighbours: List[int], colored_vertices: List[int], color: int ) -> bool: """ For each neighbour check if coloring constraint is satisfied @@ -35,7 +35,7 @@ def valid_coloring( def util_color( - graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int + graph: List[List[int]], max_colors: int, colored_vertices: List[int], index: int ) -> bool: """ Pseudo-Code @@ -86,7 +86,7 @@ def util_color( return False -def color(graph: list[list[int]], max_colors: int) -> list[int]: +def color(graph: List[List[int]], max_colors: int) -> List[int]: """ Wrapper function to call subroutine called util_color which will either return True or False. diff --git a/backtracking/hamiltonian_cycle.py b/backtracking/hamiltonian_cycle.py index bf15cce4aca4..7be1ea350d7c 100644 --- a/backtracking/hamiltonian_cycle.py +++ b/backtracking/hamiltonian_cycle.py @@ -6,11 +6,11 @@ Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path """ -from __future__ import annotations +from typing import List def valid_connection( - graph: list[list[int]], next_ver: int, curr_ind: int, path: list[int] + graph: List[List[int]], next_ver: int, curr_ind: int, path: List[int] ) -> bool: """ Checks whether it is possible to add next into path by validating 2 statements @@ -47,7 +47,7 @@ def valid_connection( return not any(vertex == next_ver for vertex in path) -def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) -> bool: +def util_hamilton_cycle(graph: List[List[int]], path: List[int], curr_ind: int) -> bool: """ Pseudo-Code Base Case: @@ -108,7 +108,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) return False -def hamilton_cycle(graph: list[list[int]], start_index: int = 0) -> list[int]: +def hamilton_cycle(graph: List[List[int]], start_index: int = 0) -> List[int]: r""" Wrapper function to call subroutine called util_hamilton_cycle, which will either return array of vertices indicating hamiltonian cycle diff --git a/backtracking/knight_tour.py b/backtracking/knight_tour.py index 2413ba468838..8e6613e07d8b 100644 --- a/backtracking/knight_tour.py +++ b/backtracking/knight_tour.py @@ -1,9 +1,9 @@ # Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM -from __future__ import annotations +from typing import List, Tuple -def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]: +def get_valid_pos(position: Tuple[int, int], n: int) -> List[Tuple[int, int]]: """ Find all the valid positions a knight can move to from the current position. @@ -32,7 +32,7 @@ def get_valid_pos(position: tuple[int], n: int) -> list[tuple[int]]: return permissible_positions -def is_complete(board: list[list[int]]) -> bool: +def is_complete(board: List[List[int]]) -> bool: """ Check if the board (matrix) has been completely filled with non-zero values. @@ -46,7 +46,9 @@ def is_complete(board: list[list[int]]) -> bool: return not any(elem == 0 for row in board for elem in row) -def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int) -> bool: +def open_knight_tour_helper( + board: List[List[int]], pos: Tuple[int, int], curr: int +) -> bool: """ Helper function to solve knight tour problem. """ @@ -66,7 +68,7 @@ def open_knight_tour_helper(board: list[list[int]], pos: tuple[int], curr: int) return False -def open_knight_tour(n: int) -> list[list[int]]: +def open_knight_tour(n: int) -> List[List[int]]: """ Find the solution for the knight tour problem for a board of size n. Raises ValueError if the tour cannot be performed for the given size. diff --git a/backtracking/minimax.py b/backtracking/minimax.py index 91188090c899..dda29b47d6cc 100644 --- a/backtracking/minimax.py +++ b/backtracking/minimax.py @@ -1,18 +1,18 @@ -from __future__ import annotations - -import math +""" +Minimax helps to achieve maximum score in a game by checking all possible moves +depth is current depth in game tree. -""" Minimax helps to achieve maximum score in a game by checking all possible moves - depth is current depth in game tree. - nodeIndex is index of current node in scores[]. - if move is of maximizer return true else false - leaves of game tree is stored in scores[] - height is maximum height of Game tree +nodeIndex is index of current node in scores[]. +if move is of maximizer return true else false +leaves of game tree is stored in scores[] +height is maximum height of Game tree """ +import math +from typing import List def minimax( - depth: int, node_index: int, is_max: bool, scores: list[int], height: float + depth: int, node_index: int, is_max: bool, scores: List[int], height: float ) -> int: """ >>> import math @@ -32,10 +32,6 @@ def minimax( >>> height = math.log(len(scores), 2) >>> minimax(0, 0, True, scores, height) 12 - >>> minimax('1', 2, True, [], 2 ) - Traceback (most recent call last): - ... - TypeError: '<' not supported between instances of 'str' and 'int' """ if depth < 0: @@ -59,7 +55,7 @@ def minimax( ) -def main(): +def main() -> None: scores = [90, 23, 6, 33, 21, 65, 123, 34423] height = math.log(len(scores), 2) print("Optimal value : ", end="") diff --git a/backtracking/n_queens.py b/backtracking/n_queens.py index ca7beb830bba..29b8d819acf3 100644 --- a/backtracking/n_queens.py +++ b/backtracking/n_queens.py @@ -7,10 +7,12 @@ diagonal lines. """ +from typing import List + solution = [] -def isSafe(board, row, column): +def isSafe(board: List[List[int]], row: int, column: int) -> bool: """ This function returns a boolean value True if it is safe to place a queen there considering the current state of the board. @@ -38,7 +40,7 @@ def isSafe(board, row, column): return True -def solve(board, row): +def solve(board: List[List[int]], row: int) -> bool: """ It creates a state space tree and calls the safe function until it receives a False Boolean and terminates that branch and backtracks to the next @@ -53,7 +55,7 @@ def solve(board, row): solution.append(board) printboard(board) print() - return + return True for i in range(len(board)): """ For every row it iterates through each column to check if it is feasible to @@ -68,7 +70,7 @@ def solve(board, row): return False -def printboard(board): +def printboard(board: List[List[int]]) -> None: """ Prints the boards that have a successful combination. """ diff --git a/backtracking/n_queens_math.py b/backtracking/n_queens_math.py index 811611971616..a8651c5c362e 100644 --- a/backtracking/n_queens_math.py +++ b/backtracking/n_queens_math.py @@ -75,14 +75,14 @@ for another one or vice versa. """ -from __future__ import annotations +from typing import List def depth_first_search( - possible_board: list[int], - diagonal_right_collisions: list[int], - diagonal_left_collisions: list[int], - boards: list[list[str]], + possible_board: List[int], + diagonal_right_collisions: List[int], + diagonal_left_collisions: List[int], + boards: List[List[str]], n: int, ) -> None: """ @@ -94,40 +94,33 @@ def depth_first_search( ['. . Q . ', 'Q . . . ', '. . . Q ', '. Q . . '] """ - """ Get next row in the current board (possible_board) to fill it with a queen """ + # Get next row in the current board (possible_board) to fill it with a queen row = len(possible_board) - """ - If row is equal to the size of the board it means there are a queen in each row in - the current board (possible_board) - """ + # If row is equal to the size of the board it means there are a queen in each row in + # the current board (possible_board) if row == n: - """ - We convert the variable possible_board that looks like this: [1, 3, 0, 2] to - this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . '] - """ - possible_board = [". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board] - boards.append(possible_board) + # We convert the variable possible_board that looks like this: [1, 3, 0, 2] to + # this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . '] + boards.append([". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board]) return - """ We iterate each column in the row to find all possible results in each row """ + # We iterate each column in the row to find all possible results in each row for col in range(n): - """ - We apply that we learned previously. First we check that in the current board - (possible_board) there are not other same value because if there is it means - that there are a collision in vertical. Then we apply the two formulas we - learned before: - - 45º: y - x = b or 45: row - col = b - 135º: y + x = b or row + col = b. - - And we verify if the results of this two formulas not exist in their variables - respectively. (diagonal_right_collisions, diagonal_left_collisions) - - If any or these are True it means there is a collision so we continue to the - next value in the for loop. - """ + # We apply that we learned previously. First we check that in the current board + # (possible_board) there are not other same value because if there is it means + # that there are a collision in vertical. Then we apply the two formulas we + # learned before: + # + # 45º: y - x = b or 45: row - col = b + # 135º: y + x = b or row + col = b. + # + # And we verify if the results of this two formulas not exist in their variables + # respectively. (diagonal_right_collisions, diagonal_left_collisions) + # + # If any or these are True it means there is a collision so we continue to the + # next value in the for loop. if ( col in possible_board or row - col in diagonal_right_collisions @@ -135,7 +128,7 @@ def depth_first_search( ): continue - """ If it is False we call dfs function again and we update the inputs """ + # If it is False we call dfs function again and we update the inputs depth_first_search( possible_board + [col], diagonal_right_collisions + [row - col], @@ -146,10 +139,10 @@ def depth_first_search( def n_queens_solution(n: int) -> None: - boards = [] + boards: List[List[str]] = [] depth_first_search([], [], [], boards, n) - """ Print all the boards """ + # Print all the boards for board in boards: for column in board: print(column) diff --git a/backtracking/rat_in_maze.py b/backtracking/rat_in_maze.py index 788aeac13c09..cd2a8f41daa8 100644 --- a/backtracking/rat_in_maze.py +++ b/backtracking/rat_in_maze.py @@ -1,4 +1,7 @@ -def solve_maze(maze: list) -> bool: +from typing import List + + +def solve_maze(maze: List[List[int]]) -> bool: """ This method solves the "rat in maze" problem. In this problem we have some n by n matrix, a start point and an end point. @@ -67,7 +70,7 @@ def solve_maze(maze: list) -> bool: return solved -def run_maze(maze, i, j, solutions): +def run_maze(maze: List[List[int]], i: int, j: int, solutions: List[List[int]]) -> bool: """ This method is recursive starting from (i, j) and going in one of four directions: up, down, left, right. @@ -106,6 +109,7 @@ def run_maze(maze, i, j, solutions): solutions[i][j] = 0 return False + return False if __name__ == "__main__": diff --git a/backtracking/sudoku.py b/backtracking/sudoku.py index 614bdb8530ac..3bfaddd6e56f 100644 --- a/backtracking/sudoku.py +++ b/backtracking/sudoku.py @@ -1,20 +1,20 @@ -from typing import List, Tuple, Union +""" +Given a partially filled 9×9 2D array, the objective is to fill a 9×9 +square grid with digits numbered 1 to 9, so that every row, column, and +and each of the nine 3×3 sub-grids contains all of the digits. + +This can be solved using Backtracking and is similar to n-queens. +We check to see if a cell is safe or not and recursively call the +function on the next column to see if it returns True. if yes, we +have solved the puzzle. else, we backtrack and place another number +in that cell and repeat this process. +""" +from typing import List, Optional, Tuple Matrix = List[List[int]] -""" - Given a partially filled 9×9 2D array, the objective is to fill a 9×9 - square grid with digits numbered 1 to 9, so that every row, column, and - and each of the nine 3×3 sub-grids contains all of the digits. - - This can be solved using Backtracking and is similar to n-queens. - We check to see if a cell is safe or not and recursively call the - function on the next column to see if it returns True. if yes, we - have solved the puzzle. else, we backtrack and place another number - in that cell and repeat this process. -""" # assigning initial values to the grid -initial_grid = [ +initial_grid: Matrix = [ [3, 0, 6, 5, 0, 8, 4, 0, 0], [5, 2, 0, 0, 0, 0, 0, 0, 0], [0, 8, 7, 0, 0, 0, 0, 3, 1], @@ -27,7 +27,7 @@ ] # a grid with no solution -no_solution = [ +no_solution: Matrix = [ [5, 0, 6, 5, 0, 8, 4, 0, 3], [5, 2, 0, 0, 0, 0, 0, 0, 2], [1, 8, 7, 0, 0, 0, 0, 3, 1], @@ -80,7 +80,7 @@ def is_completed(grid: Matrix) -> bool: return all(all(cell != 0 for cell in row) for row in grid) -def find_empty_location(grid: Matrix) -> Tuple[int, int]: +def find_empty_location(grid: Matrix) -> Optional[Tuple[int, int]]: """ This function finds an empty location so that we can assign a number for that particular row and column. @@ -89,9 +89,10 @@ def find_empty_location(grid: Matrix) -> Tuple[int, int]: for j in range(9): if grid[i][j] == 0: return i, j + return None -def sudoku(grid: Matrix) -> Union[Matrix, bool]: +def sudoku(grid: Matrix) -> Optional[Matrix]: """ Takes a partially filled-in grid and attempts to assign values to all unassigned locations in such a way to meet the requirements @@ -107,25 +108,30 @@ def sudoku(grid: Matrix) -> Union[Matrix, bool]: [1, 3, 8, 9, 4, 7, 2, 5, 6], [6, 9, 2, 3, 5, 1, 8, 7, 4], [7, 4, 5, 2, 8, 6, 3, 1, 9]] - >>> sudoku(no_solution) - False + >>> sudoku(no_solution) is None + True """ if is_completed(grid): return grid - row, column = find_empty_location(grid) + location = find_empty_location(grid) + if location is not None: + row, column = location + else: + # If the location is ``None``, then the grid is solved. + return grid for digit in range(1, 10): if is_safe(grid, row, column, digit): grid[row][column] = digit - if sudoku(grid): + if sudoku(grid) is not None: return grid grid[row][column] = 0 - return False + return None def print_solution(grid: Matrix) -> None: @@ -141,11 +147,12 @@ def print_solution(grid: Matrix) -> None: if __name__ == "__main__": # make a copy of grid so that you can compare with the unmodified grid - for grid in (initial_grid, no_solution): - grid = list(map(list, grid)) - solution = sudoku(grid) - if solution: - print("grid after solving:") + for example_grid in (initial_grid, no_solution): + print("\nExample grid:\n" + "=" * 20) + print_solution(example_grid) + print("\nExample grid solution:") + solution = sudoku(example_grid) + if solution is not None: print_solution(solution) else: print("Cannot find a solution.") diff --git a/backtracking/sum_of_subsets.py b/backtracking/sum_of_subsets.py index 425ddcff927e..f695b8f7a80e 100644 --- a/backtracking/sum_of_subsets.py +++ b/backtracking/sum_of_subsets.py @@ -6,18 +6,26 @@ Summation of the chosen numbers must be equal to given number M and one number can be used only once. """ +from typing import List -def generate_sum_of_subsets_soln(nums, max_sum): - result = [] - path = [] +def generate_sum_of_subsets_soln(nums: List[int], max_sum: int) -> List[List[int]]: + result: List[List[int]] = [] + path: List[int] = [] num_index = 0 remaining_nums_sum = sum(nums) create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum) return result -def create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum): +def create_state_space_tree( + nums: List[int], + max_sum: int, + num_index: int, + path: List[int], + result: List[List[int]], + remaining_nums_sum: int, +) -> None: """ Creates a state space tree to iterate through each branch using DFS. It terminates the branching of a node when any of the two conditions diff --git a/bit_manipulation/binary_and_operator.py b/bit_manipulation/binary_and_operator.py index f1b910f8cc9b..191ff8eb44a4 100644 --- a/bit_manipulation/binary_and_operator.py +++ b/bit_manipulation/binary_and_operator.py @@ -1,7 +1,7 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm -def binary_and(a: int, b: int): +def binary_and(a: int, b: int) -> str: """ Take in 2 integers, convert them to binary, return a binary number that is the diff --git a/bit_manipulation/binary_count_setbits.py b/bit_manipulation/binary_count_setbits.py new file mode 100644 index 000000000000..3c92694533aa --- /dev/null +++ b/bit_manipulation/binary_count_setbits.py @@ -0,0 +1,41 @@ +def binary_count_setbits(a: int) -> int: + """ + Take in 1 integer, return a number that is + the number of 1's in binary representation of that number. + + >>> binary_count_setbits(25) + 3 + >>> binary_count_setbits(36) + 2 + >>> binary_count_setbits(16) + 1 + >>> binary_count_setbits(58) + 4 + >>> binary_count_setbits(4294967295) + 32 + >>> binary_count_setbits(0) + 0 + >>> binary_count_setbits(-10) + Traceback (most recent call last): + ... + ValueError: Input value must be a positive integer + >>> binary_count_setbits(0.8) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + >>> binary_count_setbits("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0: + raise ValueError("Input value must be a positive integer") + elif isinstance(a, float): + raise TypeError("Input value must be a 'int' type") + return bin(a).count("1") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_count_trailing_zeros.py b/bit_manipulation/binary_count_trailing_zeros.py new file mode 100644 index 000000000000..f401c4ab9266 --- /dev/null +++ b/bit_manipulation/binary_count_trailing_zeros.py @@ -0,0 +1,44 @@ +from math import log2 + + +def binary_count_trailing_zeros(a: int) -> int: + """ + Take in 1 integer, return a number that is + the number of trailing zeros in binary representation of that number. + + >>> binary_count_trailing_zeros(25) + 0 + >>> binary_count_trailing_zeros(36) + 2 + >>> binary_count_trailing_zeros(16) + 4 + >>> binary_count_trailing_zeros(58) + 1 + >>> binary_count_trailing_zeros(4294967296) + 32 + >>> binary_count_trailing_zeros(0) + 0 + >>> binary_count_trailing_zeros(-10) + Traceback (most recent call last): + ... + ValueError: Input value must be a positive integer + >>> binary_count_trailing_zeros(0.8) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + >>> binary_count_trailing_zeros("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if a < 0: + raise ValueError("Input value must be a positive integer") + elif isinstance(a, float): + raise TypeError("Input value must be a 'int' type") + return 0 if (a == 0) else int(log2(a & -a)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_or_operator.py b/bit_manipulation/binary_or_operator.py index e83a86d6a8bc..dabf5bcb09fd 100644 --- a/bit_manipulation/binary_or_operator.py +++ b/bit_manipulation/binary_or_operator.py @@ -1,7 +1,7 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm -def binary_or(a: int, b: int): +def binary_or(a: int, b: int) -> str: """ Take in 2 integers, convert them to binary, and return a binary number that is the result of a binary or operation on the integers provided. diff --git a/bit_manipulation/binary_shifts.py b/bit_manipulation/binary_shifts.py new file mode 100644 index 000000000000..fe62880f941c --- /dev/null +++ b/bit_manipulation/binary_shifts.py @@ -0,0 +1,111 @@ +# Information on binary shifts: +# https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types +# https://www.interviewcake.com/concept/java/bit-shift + + +def logical_left_shift(number: int, shift_amount: int) -> str: + """ + Take in 2 positive integers. + 'number' is the integer to be logically left shifted 'shift_amount' times. + i.e. (number << shift_amount) + Return the shifted binary representation. + + >>> logical_left_shift(0, 1) + '0b00' + >>> logical_left_shift(1, 1) + '0b10' + >>> logical_left_shift(1, 5) + '0b100000' + >>> logical_left_shift(17, 2) + '0b1000100' + >>> logical_left_shift(1983, 4) + '0b111101111110000' + >>> logical_left_shift(1, -1) + Traceback (most recent call last): + ... + ValueError: both inputs must be positive integers + """ + if number < 0 or shift_amount < 0: + raise ValueError("both inputs must be positive integers") + + binary_number = str(bin(number)) + binary_number += "0" * shift_amount + return binary_number + + +def logical_right_shift(number: int, shift_amount: int) -> str: + """ + Take in positive 2 integers. + 'number' is the integer to be logically right shifted 'shift_amount' times. + i.e. (number >>> shift_amount) + Return the shifted binary representation. + + >>> logical_right_shift(0, 1) + '0b0' + >>> logical_right_shift(1, 1) + '0b0' + >>> logical_right_shift(1, 5) + '0b0' + >>> logical_right_shift(17, 2) + '0b100' + >>> logical_right_shift(1983, 4) + '0b1111011' + >>> logical_right_shift(1, -1) + Traceback (most recent call last): + ... + ValueError: both inputs must be positive integers + """ + if number < 0 or shift_amount < 0: + raise ValueError("both inputs must be positive integers") + + binary_number = str(bin(number))[2:] + if shift_amount >= len(binary_number): + return "0b0" + shifted_binary_number = binary_number[: len(binary_number) - shift_amount] + return "0b" + shifted_binary_number + + +def arithmetic_right_shift(number: int, shift_amount: int) -> str: + """ + Take in 2 integers. + 'number' is the integer to be arithmetically right shifted 'shift_amount' times. + i.e. (number >> shift_amount) + Return the shifted binary representation. + + >>> arithmetic_right_shift(0, 1) + '0b00' + >>> arithmetic_right_shift(1, 1) + '0b00' + >>> arithmetic_right_shift(-1, 1) + '0b11' + >>> arithmetic_right_shift(17, 2) + '0b000100' + >>> arithmetic_right_shift(-17, 2) + '0b111011' + >>> arithmetic_right_shift(-1983, 4) + '0b111110000100' + """ + if number >= 0: # Get binary representation of positive number + binary_number = "0" + str(bin(number)).strip("-")[2:] + else: # Get binary (2's complement) representation of negative number + binary_number_length = len(bin(number)[3:]) # Find 2's complement of number + binary_number = bin(abs(number) - (1 << binary_number_length))[3:] + binary_number = ( + ("1" + "0" * (binary_number_length - len(binary_number)) + binary_number) + if number < 0 + else "0" + ) + + if shift_amount >= len(binary_number): + return "0b" + binary_number[0] * len(binary_number) + return ( + "0b" + + binary_number[0] * shift_amount + + binary_number[: len(binary_number) - shift_amount] + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_twos_complement.py b/bit_manipulation/binary_twos_complement.py new file mode 100644 index 000000000000..2c064ec142d7 --- /dev/null +++ b/bit_manipulation/binary_twos_complement.py @@ -0,0 +1,43 @@ +# Information on 2's complement: https://en.wikipedia.org/wiki/Two%27s_complement + + +def twos_complement(number: int) -> str: + """ + Take in a negative integer 'number'. + Return the two's complement representation of 'number'. + + >>> twos_complement(0) + '0b0' + >>> twos_complement(-1) + '0b11' + >>> twos_complement(-5) + '0b1011' + >>> twos_complement(-17) + '0b101111' + >>> twos_complement(-207) + '0b100110001' + >>> twos_complement(1) + Traceback (most recent call last): + ... + ValueError: input must be a negative integer + """ + if number > 0: + raise ValueError("input must be a negative integer") + binary_number_length = len(bin(number)[3:]) + twos_complement_number = bin(abs(number) - (1 << binary_number_length))[3:] + twos_complement_number = ( + ( + "1" + + "0" * (binary_number_length - len(twos_complement_number)) + + twos_complement_number + ) + if number < 0 + else "0" + ) + return "0b" + twos_complement_number + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_xor_operator.py b/bit_manipulation/binary_xor_operator.py index 0edf2ba6606d..6f8962192ad8 100644 --- a/bit_manipulation/binary_xor_operator.py +++ b/bit_manipulation/binary_xor_operator.py @@ -1,7 +1,7 @@ # https://www.tutorialspoint.com/python3/bitwise_operators_example.htm -def binary_xor(a: int, b: int): +def binary_xor(a: int, b: int) -> str: """ Take in 2 integers, convert them to binary, return a binary number that is the diff --git a/bit_manipulation/count_number_of_one_bits.py b/bit_manipulation/count_number_of_one_bits.py new file mode 100644 index 000000000000..51fd2b630483 --- /dev/null +++ b/bit_manipulation/count_number_of_one_bits.py @@ -0,0 +1,34 @@ +def get_set_bits_count(number: int) -> int: + """ + Count the number of set bits in a 32 bit integer + >>> get_set_bits_count(25) + 3 + >>> get_set_bits_count(37) + 3 + >>> get_set_bits_count(21) + 3 + >>> get_set_bits_count(58) + 4 + >>> get_set_bits_count(0) + 0 + >>> get_set_bits_count(256) + 1 + >>> get_set_bits_count(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + """ + if number < 0: + raise ValueError("the value of input must be positive") + result = 0 + while number: + if number % 2 == 1: + result += 1 + number = number >> 1 + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/reverse_bits.py b/bit_manipulation/reverse_bits.py new file mode 100644 index 000000000000..55608ae12908 --- /dev/null +++ b/bit_manipulation/reverse_bits.py @@ -0,0 +1,85 @@ +def get_reverse_bit_string(number: int) -> str: + """ + return the bit string of an integer + + >>> get_reverse_bit_string(9) + '10010000000000000000000000000000' + >>> get_reverse_bit_string(43) + '11010100000000000000000000000000' + >>> get_reverse_bit_string(2873) + '10011100110100000000000000000000' + >>> get_reverse_bit_string("this is not a number") + Traceback (most recent call last): + ... + TypeError: operation can not be conducted on a object of type str + """ + if not isinstance(number, int): + raise TypeError( + "operation can not be conducted on a object of type " + f"{type(number).__name__}" + ) + bit_string = "" + for _ in range(0, 32): + bit_string += str(number % 2) + number = number >> 1 + return bit_string + + +def reverse_bit(number: int) -> str: + """ + Take in an 32 bit integer, reverse its bits, + return a string of reverse bits + + result of a reverse_bit and operation on the integer provided. + + >>> reverse_bit(25) + '00000000000000000000000000011001' + >>> reverse_bit(37) + '00000000000000000000000000100101' + >>> reverse_bit(21) + '00000000000000000000000000010101' + >>> reverse_bit(58) + '00000000000000000000000000111010' + >>> reverse_bit(0) + '00000000000000000000000000000000' + >>> reverse_bit(256) + '00000000000000000000000100000000' + >>> reverse_bit(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + + >>> reverse_bit(1.1) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + + >>> reverse_bit("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if number < 0: + raise ValueError("the value of input must be positive") + elif isinstance(number, float): + raise TypeError("Input value must be a 'int' type") + elif isinstance(number, str): + raise TypeError("'<' not supported between instances of 'str' and 'int'") + result = 0 + # iterator over [1 to 32],since we are dealing with 32 bit integer + for _ in range(1, 33): + # left shift the bits by unity + result = result << 1 + # get the end bit + end_bit = number % 2 + # right shift the bits by unity + number = number >> 1 + # add that bit to our ans + result = result | end_bit + return get_reverse_bit_string(result) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/single_bit_manipulation_operations.py b/bit_manipulation/single_bit_manipulation_operations.py new file mode 100644 index 000000000000..e4a54028d9ee --- /dev/null +++ b/bit_manipulation/single_bit_manipulation_operations.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +"""Provide the functionality to manipulate a single bit.""" + + +def set_bit(number: int, position: int) -> int: + """ + Set the bit at position to 1. + + Details: perform bitwise or for given number and X. + Where X is a number with all the bits – zeroes and bit on given + position – one. + + >>> set_bit(0b1101, 1) # 0b1111 + 15 + >>> set_bit(0b0, 5) # 0b100000 + 32 + >>> set_bit(0b1111, 1) # 0b1111 + 15 + """ + return number | (1 << position) + + +def clear_bit(number: int, position: int) -> int: + """ + Set the bit at position to 0. + + Details: perform bitwise and for given number and X. + Where X is a number with all the bits – ones and bit on given + position – zero. + + >>> clear_bit(0b10010, 1) # 0b10000 + 16 + >>> clear_bit(0b0, 5) # 0b0 + 0 + """ + return number & ~(1 << position) + + +def flip_bit(number: int, position: int) -> int: + """ + Flip the bit at position. + + Details: perform bitwise xor for given number and X. + Where X is a number with all the bits – zeroes and bit on given + position – one. + + >>> flip_bit(0b101, 1) # 0b111 + 7 + >>> flip_bit(0b101, 0) # 0b100 + 4 + """ + return number ^ (1 << position) + + +def is_bit_set(number: int, position: int) -> bool: + """ + Is the bit at position set? + + Details: Shift the bit at position to be the first (smallest) bit. + Then check if the first bit is set by anding the shifted number with 1. + + >>> is_bit_set(0b1010, 0) + False + >>> is_bit_set(0b1010, 1) + True + >>> is_bit_set(0b1010, 2) + False + >>> is_bit_set(0b1010, 3) + True + >>> is_bit_set(0b0, 17) + False + """ + return ((number >> position) & 1) == 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/blockchain/chinese_remainder_theorem.py b/blockchain/chinese_remainder_theorem.py index b6a486f0b1ed..b50147ac1215 100644 --- a/blockchain/chinese_remainder_theorem.py +++ b/blockchain/chinese_remainder_theorem.py @@ -1,18 +1,21 @@ -# Chinese Remainder Theorem: -# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) +""" +Chinese Remainder Theorem: +GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -# If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b -# there exists integer n, such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are -# two such integers, then n1=n2(mod ab) +If GCD(a,b) = 1, then for any remainder ra modulo a and any remainder rb modulo b +there exists integer n, such that n = ra (mod a) and n = ra(mod b). If n1 and n2 are +two such integers, then n1=n2(mod ab) -# Algorithm : +Algorithm : -# 1. Use extended euclid algorithm to find x,y such that a*x + b*y = 1 -# 2. Take n = ra*by + rb*ax +1. Use extended euclid algorithm to find x,y such that a*x + b*y = 1 +2. Take n = ra*by + rb*ax +""" +from typing import Tuple # Extended Euclid -def extended_euclid(a, b): +def extended_euclid(a: int, b: int) -> Tuple[int, int]: """ >>> extended_euclid(10, 6) (-1, 2) @@ -29,7 +32,7 @@ def extended_euclid(a, b): # Uses ExtendedEuclid to find inverses -def chinese_remainder_theorem(n1, r1, n2, r2): +def chinese_remainder_theorem(n1: int, r1: int, n2: int, r2: int) -> int: """ >>> chinese_remainder_theorem(5,1,7,3) 31 @@ -51,7 +54,7 @@ def chinese_remainder_theorem(n1, r1, n2, r2): # ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid---------------- # This function find the inverses of a i.e., a^(-1) -def invert_modulo(a, n): +def invert_modulo(a: int, n: int) -> int: """ >>> invert_modulo(2, 5) 3 @@ -67,7 +70,7 @@ def invert_modulo(a, n): # Same a above using InvertingModulo -def chinese_remainder_theorem2(n1, r1, n2, r2): +def chinese_remainder_theorem2(n1: int, r1: int, n2: int, r2: int) -> int: """ >>> chinese_remainder_theorem2(5,1,7,3) 31 diff --git a/blockchain/diophantine_equation.py b/blockchain/diophantine_equation.py index 751b0efb7227..7df674cb1438 100644 --- a/blockchain/diophantine_equation.py +++ b/blockchain/diophantine_equation.py @@ -1,12 +1,14 @@ -# Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the -# diophantine equation a*x + b*y = c has a solution (where x and y are integers) -# iff gcd(a,b) divides c. +from typing import Tuple -# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) - -def diophantine(a, b, c): +def diophantine(a: int, b: int, c: int) -> Tuple[float, float]: """ + Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the + diophantine equation a*x + b*y = c has a solution (where x and y are integers) + iff gcd(a,b) divides c. + + GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + >>> diophantine(10,6,14) (-7.0, 14.0) @@ -26,19 +28,19 @@ def diophantine(a, b, c): return (r * x, r * y) -# Lemma : if n|ab and gcd(a,n) = 1, then n|b. - -# Finding All solutions of Diophantine Equations: +def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None: + """ + Lemma : if n|ab and gcd(a,n) = 1, then n|b. -# Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of Diophantine -# Equation a*x + b*y = c. a*x0 + b*y0 = c, then all the solutions have the form -# a(x0 + t*q) + b(y0 - t*p) = c, where t is an arbitrary integer. + Finding All solutions of Diophantine Equations: -# n is the number of solution you want, n = 2 by default + Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of + Diophantine Equation a*x + b*y = c. a*x0 + b*y0 = c, then all the + solutions have the form a(x0 + t*q) + b(y0 - t*p) = c, + where t is an arbitrary integer. + n is the number of solution you want, n = 2 by default -def diophantine_all_soln(a, b, c, n=2): - """ >>> diophantine_all_soln(10, 6, 14) -7.0 14.0 -4.0 9.0 @@ -67,13 +69,12 @@ def diophantine_all_soln(a, b, c, n=2): print(x, y) -# Euclid's Lemma : d divides a and b, if and only if d divides a-b and b - -# Euclid's Algorithm +def greatest_common_divisor(a: int, b: int) -> int: + """ + Euclid's Lemma : d divides a and b, if and only if d divides a-b and b + Euclid's Algorithm -def greatest_common_divisor(a, b): - """ >>> greatest_common_divisor(7,5) 1 @@ -94,12 +95,11 @@ def greatest_common_divisor(a, b): return b -# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers -# x and y, then d = gcd(a,b) - - -def extended_gcd(a, b): +def extended_gcd(a: int, b: int) -> Tuple[int, int, int]: """ + Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers + x and y, then d = gcd(a,b) + >>> extended_gcd(10, 6) (2, -1, 2) diff --git a/blockchain/modular_division.py b/blockchain/modular_division.py index 8fcf6e37cbed..4f7f50a92ad0 100644 --- a/blockchain/modular_division.py +++ b/blockchain/modular_division.py @@ -1,21 +1,23 @@ -# Modular Division : -# An efficient algorithm for dividing b by a modulo n. +from typing import Tuple -# GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) -# Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should -# return an integer x such that 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). +def modular_division(a: int, b: int, n: int) -> int: + """ + Modular Division : + An efficient algorithm for dividing b by a modulo n. -# Theorem: -# a has a multiplicative inverse modulo n iff gcd(a,n) = 1 + GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor ) + Given three integers a, b, and n, such that gcd(a,n)=1 and n>1, the algorithm should + return an integer x such that 0≤x≤n−1, and b/a=x(modn) (that is, b=ax(modn)). -# This find x = b*a^(-1) mod n -# Uses ExtendedEuclid to find the inverse of a + Theorem: + a has a multiplicative inverse modulo n iff gcd(a,n) = 1 -def modular_division(a, b, n): - """ + This find x = b*a^(-1) mod n + Uses ExtendedEuclid to find the inverse of a + >>> modular_division(4,8,5) 2 @@ -32,9 +34,10 @@ def modular_division(a, b, n): return x -# This function find the inverses of a i.e., a^(-1) -def invert_modulo(a, n): +def invert_modulo(a: int, n: int) -> int: """ + This function find the inverses of a i.e., a^(-1) + >>> invert_modulo(2, 5) 3 @@ -50,9 +53,11 @@ def invert_modulo(a, n): # ------------------ Finding Modular division using invert_modulo ------------------- -# This function used the above inversion of a to find x = (b*a^(-1))mod n -def modular_division2(a, b, n): + +def modular_division2(a: int, b: int, n: int) -> int: """ + This function used the above inversion of a to find x = (b*a^(-1))mod n + >>> modular_division2(4,8,5) 2 @@ -68,17 +73,15 @@ def modular_division2(a, b, n): return x -# Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x -# and y, then d = gcd(a,b) - - -def extended_gcd(a, b): +def extended_gcd(a: int, b: int) -> Tuple[int, int, int]: """ - >>> extended_gcd(10, 6) - (2, -1, 2) + Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers x + and y, then d = gcd(a,b) + >>> extended_gcd(10, 6) + (2, -1, 2) - >>> extended_gcd(7, 5) - (1, -2, 3) + >>> extended_gcd(7, 5) + (1, -2, 3) ** extended_gcd function is used when d = gcd(a,b) is required in output @@ -98,9 +101,9 @@ def extended_gcd(a, b): return (d, x, y) -# Extended Euclid -def extended_euclid(a, b): +def extended_euclid(a: int, b: int) -> Tuple[int, int]: """ + Extended Euclid >>> extended_euclid(10, 6) (-1, 2) @@ -115,12 +118,11 @@ def extended_euclid(a, b): return (y, x - k * y) -# Euclid's Lemma : d divides a and b, if and only if d divides a-b and b -# Euclid's Algorithm - - -def greatest_common_divisor(a, b): +def greatest_common_divisor(a: int, b: int) -> int: """ + Euclid's Lemma : d divides a and b, if and only if d divides a-b and b + Euclid's Algorithm + >>> greatest_common_divisor(7,5) 1 diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 19bac336f6c5..70cdf25a701d 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,10 +1,13 @@ +from typing import List + + def compare_string(string1: str, string2: str) -> str: """ >>> compare_string('0010','0110') '0_10' >>> compare_string('0110','1101') - -1 + 'X' """ l1 = list(string1) l2 = list(string2) @@ -14,12 +17,12 @@ def compare_string(string1: str, string2: str) -> str: count += 1 l1[i] = "_" if count > 1: - return -1 + return "X" else: return "".join(l1) -def check(binary: [str]) -> [str]: +def check(binary: List[str]) -> List[str]: """ >>> check(['0.00.01.5']) ['0.00.01.5'] @@ -31,7 +34,7 @@ def check(binary: [str]) -> [str]: for i in range(len(binary)): for j in range(i + 1, len(binary)): k = compare_string(binary[i], binary[j]) - if k != -1: + if k != "X": check1[i] = "*" check1[j] = "*" temp.append(k) @@ -43,7 +46,7 @@ def check(binary: [str]) -> [str]: binary = list(set(temp)) -def decimal_to_binary(no_of_variable: int, minterms: [float]) -> [str]: +def decimal_to_binary(no_of_variable: int, minterms: List[float]) -> List[str]: """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] @@ -79,7 +82,7 @@ def is_for_table(string1: str, string2: str, count: int) -> bool: return False -def selection(chart: [[int]], prime_implicants: [str]) -> [str]: +def selection(chart: List[List[int]], prime_implicants: List[str]) -> List[str]: """ >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] @@ -126,7 +129,9 @@ def selection(chart: [[int]], prime_implicants: [str]) -> [str]: chart[j][i] = 0 -def prime_implicant_chart(prime_implicants: [str], binary: [str]) -> [[int]]: +def prime_implicant_chart( + prime_implicants: List[str], binary: List[str] +) -> List[List[int]]: """ >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) [[1]] diff --git a/cellular_automata/conways_game_of_life.py b/cellular_automata/conways_game_of_life.py new file mode 100644 index 000000000000..321baa3a3794 --- /dev/null +++ b/cellular_automata/conways_game_of_life.py @@ -0,0 +1,100 @@ +""" +Conway's Game of Life implemented in Python. +https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life +""" + +from __future__ import annotations + +from typing import List + +from PIL import Image + +# Define glider example +GLIDER = [ + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], +] + +# Define blinker example +BLINKER = [[0, 1, 0], [0, 1, 0], [0, 1, 0]] + + +def new_generation(cells: List[List[int]]) -> List[List[int]]: + """ + Generates the next generation for a given state of Conway's Game of Life. + >>> new_generation(BLINKER) + [[0, 0, 0], [1, 1, 1], [0, 0, 0]] + """ + next_generation = [] + for i in range(len(cells)): + next_generation_row = [] + for j in range(len(cells[i])): + # Get the number of live neighbours + neighbour_count = 0 + if i > 0 and j > 0: + neighbour_count += cells[i - 1][j - 1] + if i > 0: + neighbour_count += cells[i - 1][j] + if i > 0 and j < len(cells[i]) - 1: + neighbour_count += cells[i - 1][j + 1] + if j > 0: + neighbour_count += cells[i][j - 1] + if j < len(cells[i]) - 1: + neighbour_count += cells[i][j + 1] + if i < len(cells) - 1 and j > 0: + neighbour_count += cells[i + 1][j - 1] + if i < len(cells) - 1: + neighbour_count += cells[i + 1][j] + if i < len(cells) - 1 and j < len(cells[i]) - 1: + neighbour_count += cells[i + 1][j + 1] + + # Rules of the game of life (excerpt from Wikipedia): + # 1. Any live cell with two or three live neighbours survives. + # 2. Any dead cell with three live neighbours becomes a live cell. + # 3. All other live cells die in the next generation. + # Similarly, all other dead cells stay dead. + alive = cells[i][j] == 1 + if ( + (alive and 2 <= neighbour_count <= 3) + or not alive + and neighbour_count == 3 + ): + next_generation_row.append(1) + else: + next_generation_row.append(0) + + next_generation.append(next_generation_row) + return next_generation + + +def generate_images(cells: list[list[int]], frames) -> list[Image.Image]: + """ + Generates a list of images of subsequent Game of Life states. + """ + images = [] + for _ in range(frames): + # Create output image + img = Image.new("RGB", (len(cells[0]), len(cells))) + pixels = img.load() + + # Save cells to image + for x in range(len(cells)): + for y in range(len(cells[0])): + colour = 255 - cells[y][x] * 255 + pixels[x, y] = (colour, colour, colour) + + # Save image + images.append(img) + cells = new_generation(cells) + return images + + +if __name__ == "__main__": + images = generate_images(GLIDER, 16) + images[0].save("out.gif", save_all=True, append_images=images[1:]) diff --git a/other/game_of_life.py b/cellular_automata/game_of_life.py similarity index 100% rename from other/game_of_life.py rename to cellular_automata/game_of_life.py diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py index 92710ec44b0e..e6684fb1e6fc 100644 --- a/ciphers/a1z26.py +++ b/ciphers/a1z26.py @@ -7,7 +7,7 @@ """ -def encode(plain: str) -> list: +def encode(plain: str) -> list[int]: """ >>> encode("myname") [13, 25, 14, 1, 13, 5] @@ -15,7 +15,7 @@ def encode(plain: str) -> list: return [ord(elem) - 96 for elem in plain] -def decode(encoded: list) -> str: +def decode(encoded: list[int]) -> str: """ >>> decode([13, 25, 14, 1, 13, 5]) 'myname' @@ -23,8 +23,8 @@ def decode(encoded: list) -> str: return "".join(chr(elem + 96) for elem in encoded) -def main(): - encoded = encode(input("->").strip().lower()) +def main() -> None: + encoded = encode(input("-> ").strip().lower()) print("Encoded: ", encoded) print("Decoded:", decode(encoded)) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index cf8c0d5f4c1d..d3b806ba1eeb 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -9,26 +9,6 @@ ) -def main(): - """ - >>> key = get_random_key() - >>> msg = "This is a test!" - >>> decrypt_message(key, encrypt_message(key, msg)) == msg - True - """ - message = input("Enter message: ").strip() - key = int(input("Enter key [2000 - 9000]: ").strip()) - mode = input("Encrypt/Decrypt [E/D]: ").strip().lower() - - if mode.startswith("e"): - mode = "encrypt" - translated = encrypt_message(key, message) - elif mode.startswith("d"): - mode = "decrypt" - translated = decrypt_message(key, message) - print(f"\n{mode.title()}ed text: \n{translated}") - - def check_keys(keyA: int, keyB: int, mode: str) -> None: if mode == "encrypt": if keyA == 1: @@ -80,7 +60,7 @@ def decrypt_message(key: int, message: str) -> str: keyA, keyB = divmod(key, len(SYMBOLS)) check_keys(keyA, keyB, "decrypt") plainText = "" - modInverseOfkeyA = cryptomath.findModInverse(keyA, len(SYMBOLS)) + modInverseOfkeyA = cryptomath.find_mod_inverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: symIndex = SYMBOLS.find(symbol) @@ -98,6 +78,26 @@ def get_random_key() -> int: return keyA * len(SYMBOLS) + keyB +def main() -> None: + """ + >>> key = get_random_key() + >>> msg = "This is a test!" + >>> decrypt_message(key, encrypt_message(key, msg)) == msg + True + """ + message = input("Enter message: ").strip() + key = int(input("Enter key [2000 - 9000]: ").strip()) + mode = input("Encrypt/Decrypt [E/D]: ").strip().lower() + + if mode.startswith("e"): + mode = "encrypt" + translated = encrypt_message(key, message) + elif mode.startswith("d"): + mode = "decrypt" + translated = decrypt_message(key, message) + print(f"\n{mode.title()}ed text: \n{translated}") + + if __name__ == "__main__": import doctest diff --git a/ciphers/atbash.py b/ciphers/atbash.py index c17d1e34f37a..5c2aea610bff 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -61,6 +61,6 @@ def benchmark() -> None: if __name__ == "__main__": - for sequence in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): - print(f"{sequence} encrypted in atbash: {atbash(sequence)}") + for example in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): + print(f"{example} encrypted in atbash: {atbash(example)}") benchmark() diff --git a/ciphers/base16.py b/ciphers/base16.py index 0210315d54e6..f27ea4628e54 100644 --- a/ciphers/base16.py +++ b/ciphers/base16.py @@ -1,13 +1,22 @@ import base64 -def main(): - inp = input("->") +def encode_to_b16(inp: str) -> bytes: + """ + Encodes a given utf-8 string into base-16. + >>> encode_to_b16('Hello World!') + b'48656C6C6F20576F726C6421' + >>> encode_to_b16('HELLO WORLD!') + b'48454C4C4F20574F524C4421' + >>> encode_to_b16('') + b'' + """ encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) b16encoded = base64.b16encode(encoded) # b16encoded the encoded string - print(b16encoded) - print(base64.b16decode(b16encoded).decode("utf-8")) # decoded it + return b16encoded if __name__ == "__main__": - main() + import doctest + + doctest.testmod() diff --git a/ciphers/base32.py b/ciphers/base32.py index 5bba8c4dd685..da289a7210e8 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -1,7 +1,7 @@ import base64 -def main(): +def main() -> None: inp = input("->") encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) b32encoded = base64.b32encode(encoded) # b32encoded the encoded string diff --git a/ciphers/base64_cipher.py b/ciphers/base64_cipher.py deleted file mode 100644 index 1dbe74a20fe7..000000000000 --- a/ciphers/base64_cipher.py +++ /dev/null @@ -1,89 +0,0 @@ -def encode_base64(text: str) -> str: - r""" - >>> encode_base64('WELCOME to base64 encoding 😁') - 'V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==' - >>> encode_base64('AÅᐃ𐀏🤓') - 'QcOF4ZCD8JCAj/CfpJM=' - >>> encode_base64('A'*60) - 'QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB\r\nQUFB' - """ - base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - - byte_text = bytes(text, "utf-8") # put text in bytes for unicode support - r = "" # the result - c = -len(byte_text) % 3 # the length of padding - p = "=" * c # the padding - s = byte_text + b"\x00" * c # the text to encode - - i = 0 - while i < len(s): - if i > 0 and ((i / 3 * 4) % 76) == 0: - r = r + "\r\n" # for unix newline, put "\n" - - n = (s[i] << 16) + (s[i + 1] << 8) + s[i + 2] - - n1 = (n >> 18) & 63 - n2 = (n >> 12) & 63 - n3 = (n >> 6) & 63 - n4 = n & 63 - - r += base64_chars[n1] + base64_chars[n2] + base64_chars[n3] + base64_chars[n4] - i += 3 - - return r[0 : len(r) - len(p)] + p - - -def decode_base64(text: str) -> str: - r""" - >>> decode_base64('V0VMQ09NRSB0byBiYXNlNjQgZW5jb2Rpbmcg8J+YgQ==') - 'WELCOME to base64 encoding 😁' - >>> decode_base64('QcOF4ZCD8JCAj/CfpJM=') - 'AÅᐃ𐀏🤓' - >>> decode_base64("QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUF" - ... "BQUFBQUFBQUFB\r\nQUFB") - 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' - """ - base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - s = "" - - for i in text: - if i in base64_chars: - s += i - c = "" - else: - if i == "=": - c += "=" - - p = "" - if c == "=": - p = "A" - else: - if c == "==": - p = "AA" - - r = b"" - s = s + p - - i = 0 - while i < len(s): - n = ( - (base64_chars.index(s[i]) << 18) - + (base64_chars.index(s[i + 1]) << 12) - + (base64_chars.index(s[i + 2]) << 6) - + base64_chars.index(s[i + 3]) - ) - - r += bytes([(n >> 16) & 255]) + bytes([(n >> 8) & 255]) + bytes([n & 255]) - - i += 4 - - return str(r[0 : len(r) - len(p)], "utf-8") - - -def main(): - print(encode_base64("WELCOME to base64 encoding 😁")) - print(decode_base64(encode_base64("WELCOME to base64 encoding 😁"))) - - -if __name__ == "__main__": - main() diff --git a/ciphers/base64_encoding.py b/ciphers/base64_encoding.py new file mode 100644 index 000000000000..634afcb89873 --- /dev/null +++ b/ciphers/base64_encoding.py @@ -0,0 +1,142 @@ +B64_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + + +def base64_encode(data: bytes) -> bytes: + """Encodes data according to RFC4648. + + The data is first transformed to binary and appended with binary digits so that its + length becomes a multiple of 6, then each 6 binary digits will match a character in + the B64_CHARSET string. The number of appended binary digits would later determine + how many "=" sign should be added, the padding. + For every 2 binary digits added, a "=" sign is added in the output. + We can add any binary digits to make it a multiple of 6, for instance, consider the + following example: + "AA" -> 0010100100101001 -> 001010 010010 1001 + As can be seen above, 2 more binary digits should be added, so there's 4 + possibilities here: 00, 01, 10 or 11. + That being said, Base64 encoding can be used in Steganography to hide data in these + appended digits. + + >>> from base64 import b64encode + >>> a = b"This pull request is part of Hacktoberfest20!" + >>> b = b"https://tools.ietf.org/html/rfc4648" + >>> c = b"A" + >>> base64_encode(a) == b64encode(a) + True + >>> base64_encode(b) == b64encode(b) + True + >>> base64_encode(c) == b64encode(c) + True + >>> base64_encode("abc") + Traceback (most recent call last): + ... + TypeError: a bytes-like object is required, not 'str' + """ + # Make sure the supplied data is a bytes-like object + if not isinstance(data, bytes): + raise TypeError( + f"a bytes-like object is required, not '{data.__class__.__name__}'" + ) + + binary_stream = "".join(bin(byte)[2:].zfill(8) for byte in data) + + padding_needed = len(binary_stream) % 6 != 0 + + if padding_needed: + # The padding that will be added later + padding = b"=" * ((6 - len(binary_stream) % 6) // 2) + + # Append binary_stream with arbitrary binary digits (0's by default) to make its + # length a multiple of 6. + binary_stream += "0" * (6 - len(binary_stream) % 6) + else: + padding = b"" + + # Encode every 6 binary digits to their corresponding Base64 character + return ( + "".join( + B64_CHARSET[int(binary_stream[index : index + 6], 2)] + for index in range(0, len(binary_stream), 6) + ).encode() + + padding + ) + + +def base64_decode(encoded_data: str) -> bytes: + """Decodes data according to RFC4648. + + This does the reverse operation of base64_encode. + We first transform the encoded data back to a binary stream, take off the + previously appended binary digits according to the padding, at this point we + would have a binary stream whose length is multiple of 8, the last step is + to convert every 8 bits to a byte. + + >>> from base64 import b64decode + >>> a = "VGhpcyBwdWxsIHJlcXVlc3QgaXMgcGFydCBvZiBIYWNrdG9iZXJmZXN0MjAh" + >>> b = "aHR0cHM6Ly90b29scy5pZXRmLm9yZy9odG1sL3JmYzQ2NDg=" + >>> c = "QQ==" + >>> base64_decode(a) == b64decode(a) + True + >>> base64_decode(b) == b64decode(b) + True + >>> base64_decode(c) == b64decode(c) + True + >>> base64_decode("abc") + Traceback (most recent call last): + ... + AssertionError: Incorrect padding + """ + # Make sure encoded_data is either a string or a bytes-like object + if not isinstance(encoded_data, bytes) and not isinstance(encoded_data, str): + raise TypeError( + "argument should be a bytes-like object or ASCII string, not " + f"'{encoded_data.__class__.__name__}'" + ) + + # In case encoded_data is a bytes-like object, make sure it contains only + # ASCII characters so we convert it to a string object + if isinstance(encoded_data, bytes): + try: + encoded_data = encoded_data.decode("utf-8") + except UnicodeDecodeError: + raise ValueError("base64 encoded data should only contain ASCII characters") + + padding = encoded_data.count("=") + + # Check if the encoded string contains non base64 characters + if padding: + assert all( + char in B64_CHARSET for char in encoded_data[:-padding] + ), "Invalid base64 character(s) found." + else: + assert all( + char in B64_CHARSET for char in encoded_data + ), "Invalid base64 character(s) found." + + # Check the padding + assert len(encoded_data) % 4 == 0 and padding < 3, "Incorrect padding" + + if padding: + # Remove padding if there is one + encoded_data = encoded_data[:-padding] + + binary_stream = "".join( + bin(B64_CHARSET.index(char))[2:].zfill(6) for char in encoded_data + )[: -padding * 2] + else: + binary_stream = "".join( + bin(B64_CHARSET.index(char))[2:].zfill(6) for char in encoded_data + ) + + data = [ + int(binary_stream[index : index + 8], 2) + for index in range(0, len(binary_stream), 8) + ] + + return bytes(data) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/ciphers/base85.py b/ciphers/base85.py index ebfd0480f794..9740299b9771 100644 --- a/ciphers/base85.py +++ b/ciphers/base85.py @@ -1,7 +1,7 @@ import base64 -def main(): +def main() -> None: inp = input("->") encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) a85encoded = base64.a85encode(encoded) # a85encoded the encoded string diff --git a/ciphers/beaufort_cipher.py b/ciphers/beaufort_cipher.py new file mode 100644 index 000000000000..8eae847a7ff7 --- /dev/null +++ b/ciphers/beaufort_cipher.py @@ -0,0 +1,82 @@ +""" +Author: Mohit Radadiya +""" + +from string import ascii_uppercase + +dict1 = {char: i for i, char in enumerate(ascii_uppercase)} +dict2 = {i: char for i, char in enumerate(ascii_uppercase)} + + +# This function generates the key in +# a cyclic manner until it's length isn't +# equal to the length of original text +def generate_key(message: str, key: str) -> str: + """ + >>> generate_key("THE GERMAN ATTACK","SECRET") + 'SECRETSECRETSECRE' + """ + x = len(message) + i = 0 + while True: + if x == i: + i = 0 + if len(key) == len(message): + break + key += key[i] + i += 1 + return key + + +# This function returns the encrypted text +# generated with the help of the key +def cipher_text(message: str, key_new: str) -> str: + """ + >>> cipher_text("THE GERMAN ATTACK","SECRETSECRETSECRE") + 'BDC PAYUWL JPAIYI' + """ + cipher_text = "" + i = 0 + for letter in message: + if letter == " ": + cipher_text += " " + else: + x = (dict1[letter] - dict1[key_new[i]]) % 26 + i += 1 + cipher_text += dict2[x] + return cipher_text + + +# This function decrypts the encrypted text +# and returns the original text +def original_text(cipher_text: str, key_new: str) -> str: + """ + >>> original_text("BDC PAYUWL JPAIYI","SECRETSECRETSECRE") + 'THE GERMAN ATTACK' + """ + or_txt = "" + i = 0 + for letter in cipher_text: + if letter == " ": + or_txt += " " + else: + x = (dict1[letter] + dict1[key_new[i]] + 26) % 26 + i += 1 + or_txt += dict2[x] + return or_txt + + +def main() -> None: + message = "THE GERMAN ATTACK" + key = "SECRET" + key_new = generate_key(message, key) + s = cipher_text(message, key_new) + print(f"Encrypted Text = {s}") + print(f"Original Text = {original_text(s, key_new)}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 13a165245403..8ab6e77307b4 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -43,7 +43,7 @@ def decrypt(message: str) -> None: print(f"Decryption using Key #{key}: {translated}") -def main(): +def main() -> None: message = input("Encrypted message: ") message = message.upper() decrypt(message) diff --git a/ciphers/caesar_cipher.py b/ciphers/caesar_cipher.py index 7bda519767a1..4b2f76c7d873 100644 --- a/ciphers/caesar_cipher.py +++ b/ciphers/caesar_cipher.py @@ -1,7 +1,8 @@ from string import ascii_letters +from typing import Dict, Optional -def encrypt(input_string: str, key: int, alphabet=None) -> str: +def encrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: """ encrypt ======= @@ -79,7 +80,7 @@ def encrypt(input_string: str, key: int, alphabet=None) -> str: return result -def decrypt(input_string: str, key: int, alphabet=None) -> str: +def decrypt(input_string: str, key: int, alphabet: Optional[str] = None) -> str: """ decrypt ======= @@ -144,7 +145,7 @@ def decrypt(input_string: str, key: int, alphabet=None) -> str: return encrypt(input_string, key, alphabet) -def brute_force(input_string: str, alphabet=None) -> dict: +def brute_force(input_string: str, alphabet: Optional[str] = None) -> Dict[int, str]: """ brute_force =========== @@ -193,34 +194,21 @@ def brute_force(input_string: str, alphabet=None) -> dict: # Set default alphabet to lower and upper case english chars alpha = alphabet or ascii_letters - # The key during testing (will increase) - key = 1 - - # The encoded result - result = "" - # To store data on all the combinations brute_force_data = {} # Cycle through each combination - while key <= len(alpha): - # Decrypt the message - result = decrypt(input_string, key, alpha) - - # Update the data - brute_force_data[key] = result - - # Reset result and increase the key - result = "" - key += 1 + for key in range(1, len(alpha) + 1): + # Decrypt the message and store the result in the data + brute_force_data[key] = decrypt(input_string, key, alpha) return brute_force_data -def main(): +if __name__ == "__main__": while True: print(f'\n{"-" * 10}\n Menu\n{"-" * 10}') - print(*["1.Encrpyt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") + print(*["1.Encrypt", "2.Decrypt", "3.BruteForce", "4.Quit"], sep="\n") # get user input choice = input("\nWhat would you like to do?: ").strip() or "4" @@ -248,7 +236,3 @@ def main(): elif choice == "4": print("Goodbye.") break - - -if __name__ == "__main__": - main() diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index ffeac1617f64..be8764ff38c3 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -4,9 +4,9 @@ def gcd(a: int, b: int) -> int: return b -def findModInverse(a: int, m: int) -> int: +def find_mod_inverse(a: int, m: int) -> int: if gcd(a, m) != 1: - return None + raise ValueError(f"mod inverse of {a!r} and {m!r} does not exist") u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 41b4a12ba453..e7faeae73773 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -from typing import Tuple +from typing import Optional def decrypt_caesar_with_chi_squared( ciphertext: str, - cipher_alphabet: str = None, - frequencies_dict: str = None, + cipher_alphabet: Optional[list[str]] = None, + frequencies_dict: Optional[dict[str, float]] = None, case_sensetive: bool = False, -) -> Tuple[int, float, str]: +) -> tuple[int, float, str]: """ Basic Usage =========== @@ -123,9 +123,9 @@ def decrypt_caesar_with_chi_squared( AttributeError: 'int' object has no attribute 'lower' """ alphabet_letters = cipher_alphabet or [chr(i) for i in range(97, 123)] - frequencies_dict = frequencies_dict or {} - if frequencies_dict == {}: + # If the argument is None or the user provided an empty dictionary + if not frequencies_dict: # Frequencies of letters in the english language (how much they show up) frequencies = { "a": 0.08497, @@ -163,7 +163,7 @@ def decrypt_caesar_with_chi_squared( ciphertext = ciphertext.lower() # Chi squared statistic values - chi_squared_statistic_values = {} + chi_squared_statistic_values: dict[int, tuple[float, str]] = {} # cycle through all of the shifts for shift in range(len(alphabet_letters)): @@ -215,22 +215,22 @@ def decrypt_caesar_with_chi_squared( chi_squared_statistic += chi_letter_value # Add the data to the chi_squared_statistic_values dictionary - chi_squared_statistic_values[shift] = [ + chi_squared_statistic_values[shift] = ( chi_squared_statistic, decrypted_with_shift, - ] + ) # Get the most likely cipher by finding the cipher with the smallest chi squared # statistic - most_likely_cipher = min( + most_likely_cipher: int = min( chi_squared_statistic_values, key=chi_squared_statistic_values.get - ) + ) # type: ignore # First argument to `min` is not optional # Get all the data from the most likely cipher (key, decoded message) - most_likely_cipher_chi_squared_value = chi_squared_statistic_values[ - most_likely_cipher - ][0] - decoded_most_likely_cipher = chi_squared_statistic_values[most_likely_cipher][1] + ( + most_likely_cipher_chi_squared_value, + decoded_most_likely_cipher, + ) = chi_squared_statistic_values[most_likely_cipher] # Return the data on the most likely shift return ( diff --git a/ciphers/diffie.py b/ciphers/diffie.py index 44b12bf9d103..a23a8104afe2 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,4 +1,7 @@ -def find_primitive(n: int) -> int: +from typing import Optional + + +def find_primitive(n: int) -> Optional[int]: for r in range(1, n): li = [] for x in range(n - 1): @@ -8,18 +11,22 @@ def find_primitive(n: int) -> int: li.append(val) else: return r + return None if __name__ == "__main__": q = int(input("Enter a prime number q: ")) a = find_primitive(q) - a_private = int(input("Enter private key of A: ")) - a_public = pow(a, a_private, q) - b_private = int(input("Enter private key of B: ")) - b_public = pow(a, b_private, q) + if a is None: + print(f"Cannot find the primitive for the value: {a!r}") + else: + a_private = int(input("Enter private key of A: ")) + a_public = pow(a, a_private, q) + b_private = int(input("Enter private key of B: ")) + b_public = pow(a, b_private, q) - a_secret = pow(b_public, a_private, q) - b_secret = pow(a_public, b_private, q) + a_secret = pow(b_public, a_private, q) + b_secret = pow(a_public, b_private, q) - print("The key value generated by A is: ", a_secret) - print("The key value generated by B is: ", b_secret) + print("The key value generated by A is: ", a_secret) + print("The key value generated by B is: ", b_secret) diff --git a/ciphers/diffie_hellman.py b/ciphers/diffie_hellman.py new file mode 100644 index 000000000000..ea35b67b483e --- /dev/null +++ b/ciphers/diffie_hellman.py @@ -0,0 +1,271 @@ +from binascii import hexlify +from hashlib import sha256 +from os import urandom + +# RFC 3526 - More Modular Exponential (MODP) Diffie-Hellman groups for +# Internet Key Exchange (IKE) https://tools.ietf.org/html/rfc3526 + +primes = { + # 1536-bit + 5: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 2048-bit + 14: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 3072-bit + 15: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 4096-bit + 16: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 6144-bit + 17: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DCC4024FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 8192-bit + 18: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, +} + + +class DiffieHellman: + """ + Class to represent the Diffie-Hellman key exchange protocol + + + >>> alice = DiffieHellman() + >>> bob = DiffieHellman() + + >>> alice_private = alice.get_private_key() + >>> alice_public = alice.generate_public_key() + + >>> bob_private = bob.get_private_key() + >>> bob_public = bob.generate_public_key() + + >>> # generating shared key using the DH object + >>> alice_shared = alice.generate_shared_key(bob_public) + >>> bob_shared = bob.generate_shared_key(alice_public) + + >>> assert alice_shared == bob_shared + + >>> # generating shared key using static methods + >>> alice_shared = DiffieHellman.generate_shared_key_static( + ... alice_private, bob_public + ... ) + >>> bob_shared = DiffieHellman.generate_shared_key_static( + ... bob_private, alice_public + ... ) + + >>> assert alice_shared == bob_shared + """ + + # Current minimum recommendation is 2048 bit (group 14) + def __init__(self, group: int = 14) -> None: + if group not in primes: + raise ValueError("Unsupported Group") + self.prime = primes[group]["prime"] + self.generator = primes[group]["generator"] + + self.__private_key = int(hexlify(urandom(32)), base=16) + + def get_private_key(self) -> str: + return hex(self.__private_key)[2:] + + def generate_public_key(self) -> str: + public_key = pow(self.generator, self.__private_key, self.prime) + return hex(public_key)[2:] + + def is_valid_public_key(self, key: int) -> bool: + # check if the other public key is valid based on NIST SP800-56 + if 2 <= key and key <= self.prime - 2: + if pow(key, (self.prime - 1) // 2, self.prime) == 1: + return True + return False + + def generate_shared_key(self, other_key_str: str) -> str: + other_key = int(other_key_str, base=16) + if not self.is_valid_public_key(other_key): + raise ValueError("Invalid public key") + shared_key = pow(other_key, self.__private_key, self.prime) + return sha256(str(shared_key).encode()).hexdigest() + + @staticmethod + def is_valid_public_key_static( + local_private_key_str: str, remote_public_key_str: str, prime: int + ) -> bool: + # check if the other public key is valid based on NIST SP800-56 + if 2 <= remote_public_key_str and remote_public_key_str <= prime - 2: + if pow(remote_public_key_str, (prime - 1) // 2, prime) == 1: + return True + return False + + @staticmethod + def generate_shared_key_static( + local_private_key_str: str, remote_public_key_str: str, group: int = 14 + ) -> str: + local_private_key = int(local_private_key_str, base=16) + remote_public_key = int(remote_public_key_str, base=16) + prime = primes[group]["prime"] + if not DiffieHellman.is_valid_public_key_static( + local_private_key, remote_public_key, prime + ): + raise ValueError("Invalid public key") + shared_key = pow(remote_public_key, local_private_key, prime) + return sha256(str(shared_key).encode()).hexdigest() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 52cf69074187..f557b0e0dc91 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -2,24 +2,18 @@ import random import sys -from . import cryptomath_module as cryptoMath -from . import rabin_miller as rabinMiller +from . import cryptomath_module as cryptomath +from . import rabin_miller min_primitive_root = 3 -def main(): - print("Making key files...") - makeKeyFiles("elgamal", 2048) - print("Key files generation successful") - - # I have written my code naively same as definition of primitive root # however every time I run this program, memory exceeded... # so I used 4.80 Algorithm in # Handbook of Applied Cryptography(CRC Press, ISBN : 0-8493-8523-7, October 1996) # and it seems to run nicely! -def primitiveRoot(p_val: int) -> int: +def primitive_root(p_val: int) -> int: print("Generating primitive root of p") while True: g = random.randrange(3, p_val) @@ -30,20 +24,20 @@ def primitiveRoot(p_val: int) -> int: return g -def generateKey(keySize: int) -> ((int, int, int, int), (int, int)): +def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, int]]: print("Generating prime p...") - p = rabinMiller.generateLargePrime(keySize) # select large prime number. - e_1 = primitiveRoot(p) # one primitive root on modulo p. + p = rabin_miller.generateLargePrime(key_size) # select large prime number. + e_1 = primitive_root(p) # one primitive root on modulo p. d = random.randrange(3, p) # private_key -> have to be greater than 2 for safety. - e_2 = cryptoMath.findModInverse(pow(e_1, d, p), p) + e_2 = cryptomath.find_mod_inverse(pow(e_1, d, p), p) - publicKey = (keySize, e_1, e_2, p) - privateKey = (keySize, d) + public_key = (key_size, e_1, e_2, p) + private_key = (key_size, d) - return publicKey, privateKey + return public_key, private_key -def makeKeyFiles(name: str, keySize: int): +def make_key_files(name: str, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( "%s_privkey.txt" % name ): @@ -55,7 +49,7 @@ def makeKeyFiles(name: str, keySize: int): ) sys.exit() - publicKey, privateKey = generateKey(keySize) + publicKey, privateKey = generate_key(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as fo: fo.write( @@ -67,5 +61,11 @@ def makeKeyFiles(name: str, keySize: int): fo.write("%d,%d" % (privateKey[0], privateKey[1])) +def main() -> None: + print("Making key files...") + make_key_files("elgamal", 2048) + print("Key files generation successful") + + if __name__ == "__main__": main() diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 4344db0056fd..f4ce5a075f46 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -15,6 +15,10 @@ Created by TrapinchO """ +RotorPositionT = tuple[int, int, int] +RotorSelectionT = tuple[str, str, str] + + # used alphabet -------------------------- # from string.ascii_uppercase abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -63,7 +67,9 @@ rotor9 = "KOAEGVDHXPQZMLFTYWJNBRCIUS" -def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: +def _validator( + rotpos: RotorPositionT, rotsel: RotorSelectionT, pb: str +) -> tuple[RotorPositionT, RotorSelectionT, dict[str, str]]: """ Checks if the values can be used for the 'enigma' function @@ -99,12 +105,12 @@ def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: ) # Validates string and returns dict - pb = _plugboard(pb) + pbdict = _plugboard(pb) - return rotpos, rotsel, pb + return rotpos, rotsel, pbdict -def _plugboard(pbstring: str) -> dict: +def _plugboard(pbstring: str) -> dict[str, str]: """ https://en.wikipedia.org/wiki/Enigma_machine#Plugboard @@ -145,17 +151,17 @@ def _plugboard(pbstring: str) -> dict: # Created the dictionary pb = {} - for i in range(0, len(pbstring) - 1, 2): - pb[pbstring[i]] = pbstring[i + 1] - pb[pbstring[i + 1]] = pbstring[i] + for j in range(0, len(pbstring) - 1, 2): + pb[pbstring[j]] = pbstring[j + 1] + pb[pbstring[j + 1]] = pbstring[j] return pb def enigma( text: str, - rotor_position: tuple, - rotor_selection: tuple = (rotor1, rotor2, rotor3), + rotor_position: RotorPositionT, + rotor_selection: RotorSelectionT = (rotor1, rotor2, rotor3), plugb: str = "", ) -> str: """ diff --git a/ciphers/hill_cipher.py b/ciphers/hill_cipher.py index 3dabcd3fceab..8237abf6aa5d 100644 --- a/ciphers/hill_cipher.py +++ b/ciphers/hill_cipher.py @@ -155,8 +155,8 @@ def make_decrypt_key(self): """ >>> hill_cipher = HillCipher(numpy.array([[2, 5], [1, 6]])) >>> hill_cipher.make_decrypt_key() - array([[ 6., 25.], - [ 5., 26.]]) + array([[ 6, 25], + [ 5, 26]]) """ det = round(numpy.linalg.det(self.encrypt_key)) diff --git a/ciphers/mono_alphabetic_ciphers.py b/ciphers/mono_alphabetic_ciphers.py new file mode 100644 index 000000000000..0a29d6442896 --- /dev/null +++ b/ciphers/mono_alphabetic_ciphers.py @@ -0,0 +1,59 @@ +LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + +def translate_message(key, message, mode): + """ + >>> translate_message("QWERTYUIOPASDFGHJKLZXCVBNM","Hello World","encrypt") + 'Pcssi Bidsm' + """ + chars_a = LETTERS if mode == "decrypt" else key + chars_b = key if mode == "decrypt" else LETTERS + translated = "" + # loop through each symbol in the message + for symbol in message: + if symbol.upper() in chars_a: + # encrypt/decrypt the symbol + sym_index = chars_a.find(symbol.upper()) + if symbol.isupper(): + translated += chars_b[sym_index].upper() + else: + translated += chars_b[sym_index].lower() + else: + # symbol is not in LETTERS, just add it + translated += symbol + return translated + + +def encrypt_message(key: str, message: str) -> str: + """ + >>> encrypt_message("QWERTYUIOPASDFGHJKLZXCVBNM", "Hello World") + 'Pcssi Bidsm' + """ + return translate_message(key, message, "encrypt") + + +def decrypt_message(key: str, message: str) -> str: + """ + >>> decrypt_message("QWERTYUIOPASDFGHJKLZXCVBNM", "Hello World") + 'Itssg Vgksr' + """ + return translate_message(key, message, "decrypt") + + +def main(): + message = "Hello World" + key = "QWERTYUIOPASDFGHJKLZXCVBNM" + mode = "decrypt" # set to 'encrypt' or 'decrypt' + + if mode == "encrypt": + translated = encrypt_message(key, message) + elif mode == "decrypt": + translated = decrypt_message(key, message) + print(f"Using the key {key}, the {mode}ed message is: {translated}") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + main() diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 57c916a44d4b..0df37d6ea3ff 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -118,7 +118,7 @@ def encryptAndWriteToFile( for i in range(len(encryptedBlocks)): encryptedBlocks[i] = str(encryptedBlocks[i]) encryptedContent = ",".join(encryptedBlocks) - encryptedContent = "{}_{}_{}".format(len(message), blockSize, encryptedContent) + encryptedContent = f"{len(message)}_{blockSize}_{encryptedContent}" with open(messageFilename, "w") as fo: fo.write(encryptedContent) return encryptedContent diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 5693aa637ee9..584066d8970f 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,19 +1,18 @@ import os import random import sys -from typing import Tuple from . import cryptomath_module as cryptoMath from . import rabin_miller as rabinMiller -def main(): +def main() -> None: print("Making key files...") makeKeyFiles("rsa", 1024) print("Key files generation successful.") -def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: +def generateKey(keySize: int) -> tuple[tuple[int, int], tuple[int, int]]: print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) print("Generating prime q...") @@ -27,14 +26,14 @@ def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: break print("Calculating d that is mod inverse of e...") - d = cryptoMath.findModInverse(e, (p - 1) * (q - 1)) + d = cryptoMath.find_mod_inverse(e, (p - 1) * (q - 1)) publicKey = (n, e) privateKey = (n, d) return (publicKey, privateKey) -def makeKeyFiles(name: int, keySize: int) -> None: +def makeKeyFiles(name: str, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists( "%s_privkey.txt" % (name) ): @@ -49,11 +48,11 @@ def makeKeyFiles(name: int, keySize: int) -> None: publicKey, privateKey = generateKey(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as out_file: - out_file.write("{},{},{}".format(keySize, publicKey[0], publicKey[1])) + out_file.write(f"{keySize},{publicKey[0]},{publicKey[1]}") print("Writing private key to file %s_privkey.txt..." % name) with open("%s_privkey.txt" % name, "w") as out_file: - out_file.write("{},{},{}".format(keySize, privateKey[0], privateKey[1])) + out_file.write(f"{keySize},{privateKey[0]},{privateKey[1]}") if __name__ == "__main__": diff --git a/ciphers/simple_substitution_cipher.py b/ciphers/simple_substitution_cipher.py index f5b711e616af..646ea449fc06 100644 --- a/ciphers/simple_substitution_cipher.py +++ b/ciphers/simple_substitution_cipher.py @@ -18,7 +18,7 @@ def main(): mode = "decrypt" translated = decryptMessage(key, message) - print("\n{}ion: \n{}".format(mode.title(), translated)) + print(f"\n{mode.title()}ion: \n{translated}") def checkValidKey(key: str) -> None: diff --git a/ciphers/xor_cipher.py b/ciphers/xor_cipher.py index 818dec64131a..32a350d4e61c 100644 --- a/ciphers/xor_cipher.py +++ b/ciphers/xor_cipher.py @@ -141,14 +141,14 @@ def encrypt_file(self, file: str, key: int = 0) -> bool: assert isinstance(file, str) and isinstance(key, int) try: - with open(file, "r") as fin: + with open(file) as fin: with open("encrypt.out", "w+") as fout: # actual encrypt-process for line in fin: fout.write(self.encrypt_string(line, key)) - except IOError: + except OSError: return False return True @@ -166,14 +166,14 @@ def decrypt_file(self, file: str, key: int) -> bool: assert isinstance(file, str) and isinstance(key, int) try: - with open(file, "r") as fin: + with open(file) as fin: with open("decrypt.out", "w+") as fout: # actual encrypt-process for line in fin: fout.write(self.decrypt_string(line, key)) - except IOError: + except OSError: return False return True @@ -183,7 +183,7 @@ def decrypt_file(self, file: str, key: int) -> bool: # crypt = XORCipher() # key = 67 -# # test enrcypt +# # test encrypt # print(crypt.encrypt("hallo welt",key)) # # test decrypt # print(crypt.decrypt(crypt.encrypt("hallo welt",key), key)) diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 1a6610915e65..7d705af7428e 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -157,11 +157,12 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: entry_msg = "Provide a string that I will generate its BWT transform: " s = input(entry_msg).strip() result = bwt_transform(s) - bwt_output_msg = "Burrows Wheeler transform for string '{}' results in '{}'" - print(bwt_output_msg.format(s, result["bwt_string"])) + print( + f"Burrows Wheeler transform for string '{s}' results " + f"in '{result['bwt_string']}'" + ) original_string = reverse_bwt(result["bwt_string"], result["idx_original_string"]) - fmt = ( - "Reversing Burrows Wheeler transform for entry '{}' we get original" - " string '{}'" + print( + f"Reversing Burrows Wheeler transform for entry '{result['bwt_string']}' " + f"we get original string '{original_string}'" ) - print(fmt.format(result["bwt_string"], original_string)) diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index 3ac8573c43d8..6743dc42d56e 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -17,16 +17,16 @@ def read_file_binary(file_path: str) -> str: with open(file_path, "rb") as binary_file: data = binary_file.read() for dat in data: - curr_byte = "{0:08b}".format(dat) + curr_byte = f"{dat:08b}" result += curr_byte return result - except IOError: + except OSError: print("File not accessible") sys.exit() def add_key_to_lexicon( - lexicon: dict, curr_string: str, index: int, last_match_id: int + lexicon: dict, curr_string: str, index: int, last_match_id: str ) -> None: """ Adds new strings (curr_string + "0", curr_string + "1") to the lexicon @@ -105,7 +105,7 @@ def write_file_binary(file_path: str, to_write: str) -> None: for elem in result_byte_array: opened_file.write(int(elem, 2).to_bytes(1, byteorder="big")) - except IOError: + except OSError: print("File not accessible") sys.exit() diff --git a/compression/lempel_ziv_decompress.py b/compression/lempel_ziv_decompress.py index 05c26740bf62..4d3c2c0d2cf3 100644 --- a/compression/lempel_ziv_decompress.py +++ b/compression/lempel_ziv_decompress.py @@ -16,10 +16,10 @@ def read_file_binary(file_path: str) -> str: with open(file_path, "rb") as binary_file: data = binary_file.read() for dat in data: - curr_byte = "{0:08b}".format(dat) + curr_byte = f"{dat:08b}" result += curr_byte return result - except IOError: + except OSError: print("File not accessible") sys.exit() @@ -76,7 +76,7 @@ def write_file_binary(file_path: str, to_write: str) -> None: for elem in result_byte_array[:-1]: opened_file.write(int(elem, 2).to_bytes(1, byteorder="big")) - except IOError: + except OSError: print("File not accessible") sys.exit() diff --git a/computer_vision/meanthreshold.py b/computer_vision/meanthreshold.py new file mode 100644 index 000000000000..76657933d6a9 --- /dev/null +++ b/computer_vision/meanthreshold.py @@ -0,0 +1,30 @@ +from PIL import Image + +""" +Mean thresholding algorithm for image processing +https://en.wikipedia.org/wiki/Thresholding_(image_processing) +""" + + +def mean_threshold(image: Image) -> Image: + """ + image: is a grayscale PIL image object + """ + height, width = image.size + mean = 0 + pixels = image.load() + for i in range(width): + for j in range(height): + pixel = pixels[j, i] + mean += pixel + mean //= width * height + + for j in range(width): + for i in range(height): + pixels[i, j] = 255 if pixels[i, j] > mean else 0 + return image + + +if __name__ == "__main__": + image = mean_threshold(Image.open("path_to_image").convert("L")) + image.save("output_image_path") diff --git a/conversions/molecular_chemistry.py b/conversions/molecular_chemistry.py new file mode 100644 index 000000000000..8c68459965b0 --- /dev/null +++ b/conversions/molecular_chemistry.py @@ -0,0 +1,92 @@ +""" +Functions useful for doing molecular chemistry: +* molarity_to_normality +* moles_to_pressure +* moles_to_volume +* pressure_and_volume_to_temperature +""" + + +def molarity_to_normality(nfactor: int, moles: float, volume: float) -> float: + """ + Convert molarity to normality. + Volume is taken in litres. + + Wikipedia reference: https://en.wikipedia.org/wiki/Equivalent_concentration + Wikipedia reference: https://en.wikipedia.org/wiki/Molar_concentration + + >>> molarity_to_normality(2, 3.1, 0.31) + 20 + >>> molarity_to_normality(4, 11.4, 5.7) + 8 + """ + return round((float(moles / volume) * nfactor)) + + +def moles_to_pressure(volume: float, moles: float, temperature: float) -> float: + """ + Convert moles to pressure. + Ideal gas laws are used. + Temperature is taken in kelvin. + Volume is taken in litres. + Pressure has atm as SI unit. + + Wikipedia reference: https://en.wikipedia.org/wiki/Gas_laws + Wikipedia reference: https://en.wikipedia.org/wiki/Pressure + Wikipedia reference: https://en.wikipedia.org/wiki/Temperature + + >>> moles_to_pressure(0.82, 3, 300) + 90 + >>> moles_to_pressure(8.2, 5, 200) + 10 + """ + return round(float((moles * 0.0821 * temperature) / (volume))) + + +def moles_to_volume(pressure: float, moles: float, temperature: float) -> float: + """ + Convert moles to volume. + Ideal gas laws are used. + Temperature is taken in kelvin. + Volume is taken in litres. + Pressure has atm as SI unit. + + Wikipedia reference: https://en.wikipedia.org/wiki/Gas_laws + Wikipedia reference: https://en.wikipedia.org/wiki/Pressure + Wikipedia reference: https://en.wikipedia.org/wiki/Temperature + + >>> moles_to_volume(0.82, 3, 300) + 90 + >>> moles_to_volume(8.2, 5, 200) + 10 + """ + return round(float((moles * 0.0821 * temperature) / (pressure))) + + +def pressure_and_volume_to_temperature( + pressure: float, moles: float, volume: float +) -> float: + """ + Convert pressure and volume to temperature. + Ideal gas laws are used. + Temperature is taken in kelvin. + Volume is taken in litres. + Pressure has atm as SI unit. + + Wikipedia reference: https://en.wikipedia.org/wiki/Gas_laws + Wikipedia reference: https://en.wikipedia.org/wiki/Pressure + Wikipedia reference: https://en.wikipedia.org/wiki/Temperature + + >>> pressure_and_volume_to_temperature(0.82, 1, 2) + 20 + >>> pressure_and_volume_to_temperature(8.2, 5, 3) + 60 + """ + return round(float((pressure * volume) / (0.0821 * moles))) + + +if __name__ == "__main__": + + import doctest + + doctest.testmod() diff --git a/conversions/octal_to_decimal b/conversions/octal_to_decimal.py similarity index 77% rename from conversions/octal_to_decimal rename to conversions/octal_to_decimal.py index a5b027e3ae8d..5a7373fef7e3 100644 --- a/conversions/octal_to_decimal +++ b/conversions/octal_to_decimal.py @@ -9,10 +9,16 @@ def oct_to_decimal(oct_string: str) -> int: >>> oct_to_decimal("-45") -37 >>> oct_to_decimal("2-0Fm") + Traceback (most recent call last): + ... ValueError: Non-octal value was passed to the function >>> oct_to_decimal("") - ValueError: Empty string value was passed to the function + Traceback (most recent call last): + ... + ValueError: Empty string was passed to the function >>> oct_to_decimal("19") + Traceback (most recent call last): + ... ValueError: Non-octal value was passed to the function """ oct_string = str(oct_string).strip() @@ -21,7 +27,7 @@ def oct_to_decimal(oct_string: str) -> int: is_negative = oct_string[0] == "-" if is_negative: oct_string = oct_string[1:] - if not all(0 <= int(char) <= 7 for char in oct_string): + if not oct_string.isdigit() or not all(0 <= int(char) <= 7 for char in oct_string): raise ValueError("Non-octal value was passed to the function") decimal_number = 0 for char in oct_string: diff --git a/conversions/roman_to_integer.py b/conversions/roman_numerals.py similarity index 50% rename from conversions/roman_to_integer.py rename to conversions/roman_numerals.py index ce52b6fb7cbb..9933e6a78a4d 100644 --- a/conversions/roman_to_integer.py +++ b/conversions/roman_numerals.py @@ -21,6 +21,38 @@ def roman_to_int(roman: str) -> int: return total +def int_to_roman(number: int) -> str: + """ + Given a integer, convert it to an roman numeral. + https://en.wikipedia.org/wiki/Roman_numerals + >>> tests = {"III": 3, "CLIV": 154, "MIX": 1009, "MMD": 2500, "MMMCMXCIX": 3999} + >>> all(int_to_roman(value) == key for key, value in tests.items()) + True + """ + ROMAN = [ + (1000, "M"), + (900, "CM"), + (500, "D"), + (400, "CD"), + (100, "C"), + (90, "XC"), + (50, "L"), + (40, "XL"), + (10, "X"), + (9, "IX"), + (5, "V"), + (4, "IV"), + (1, "I"), + ] + result = [] + for (arabic, roman) in ROMAN: + (factor, number) = divmod(number, arabic) + result.append(roman * factor) + if number == 0: + break + return "".join(result) + + if __name__ == "__main__": import doctest diff --git a/conversions/weight_conversion.py b/conversions/weight_conversion.py new file mode 100644 index 000000000000..85515f2f6f88 --- /dev/null +++ b/conversions/weight_conversion.py @@ -0,0 +1,287 @@ +""" +Conversion of weight units. + +__author__ = "Anubhav Solanki" +__license__ = "MIT" +__version__ = "1.0.0" +__maintainer__ = "Anubhav Solanki" +__email__ = "anubhavsolanki0@gmail.com" + +USAGE : +-> Import this file into their respective project. +-> Use the function weight_conversion() for conversion of weight units. +-> Parameters : + -> from_type : From which type you want to convert + -> to_type : To which type you want to convert + -> value : the value which you want to convert + +REFERENCES : + +-> Wikipedia reference: https://en.wikipedia.org/wiki/Kilogram +-> Wikipedia reference: https://en.wikipedia.org/wiki/Gram +-> Wikipedia reference: https://en.wikipedia.org/wiki/Millimetre +-> Wikipedia reference: https://en.wikipedia.org/wiki/Tonne +-> Wikipedia reference: https://en.wikipedia.org/wiki/Long_ton +-> Wikipedia reference: https://en.wikipedia.org/wiki/Short_ton +-> Wikipedia reference: https://en.wikipedia.org/wiki/Pound +-> Wikipedia reference: https://en.wikipedia.org/wiki/Ounce +-> Wikipedia reference: https://en.wikipedia.org/wiki/Fineness#Karat +-> Wikipedia reference: https://en.wikipedia.org/wiki/Dalton_(unit) +""" + +KILOGRAM_CHART = { + "kilogram": 1, + "gram": pow(10, 3), + "milligram": pow(10, 6), + "metric-ton": pow(10, -3), + "long-ton": 0.0009842073, + "short-ton": 0.0011023122, + "pound": 2.2046244202, + "ounce": 35.273990723, + "carrat": 5000, + "atomic-mass-unit": 6.022136652e26, +} + +WEIGHT_TYPE_CHART = { + "kilogram": 1, + "gram": pow(10, -3), + "milligram": pow(10, -6), + "metric-ton": pow(10, 3), + "long-ton": 1016.04608, + "short-ton": 907.184, + "pound": 0.453592, + "ounce": 0.0283495, + "carrat": 0.0002, + "atomic-mass-unit": 1.660540199e-27, +} + + +def weight_conversion(from_type: str, to_type: str, value: float) -> float: + """ + Conversion of weight unit with the help of KILOGRAM_CHART + + "kilogram" : 1, + "gram" : pow(10, 3), + "milligram" : pow(10, 6), + "metric-ton" : pow(10, -3), + "long-ton" : 0.0009842073, + "short-ton" : 0.0011023122, + "pound" : 2.2046244202, + "ounce" : 35.273990723, + "carrat" : 5000, + "atomic-mass-unit" : 6.022136652E+26 + + >>> weight_conversion("kilogram","kilogram",4) + 4 + >>> weight_conversion("kilogram","gram",1) + 1000 + >>> weight_conversion("kilogram","milligram",4) + 4000000 + >>> weight_conversion("kilogram","metric-ton",4) + 0.004 + >>> weight_conversion("kilogram","long-ton",3) + 0.0029526219 + >>> weight_conversion("kilogram","short-ton",1) + 0.0011023122 + >>> weight_conversion("kilogram","pound",4) + 8.8184976808 + >>> weight_conversion("kilogram","ounce",4) + 141.095962892 + >>> weight_conversion("kilogram","carrat",3) + 15000 + >>> weight_conversion("kilogram","atomic-mass-unit",1) + 6.022136652e+26 + >>> weight_conversion("gram","kilogram",1) + 0.001 + >>> weight_conversion("gram","gram",3) + 3.0 + >>> weight_conversion("gram","milligram",2) + 2000.0 + >>> weight_conversion("gram","metric-ton",4) + 4e-06 + >>> weight_conversion("gram","long-ton",3) + 2.9526219e-06 + >>> weight_conversion("gram","short-ton",3) + 3.3069366000000003e-06 + >>> weight_conversion("gram","pound",3) + 0.0066138732606 + >>> weight_conversion("gram","ounce",1) + 0.035273990723 + >>> weight_conversion("gram","carrat",2) + 10.0 + >>> weight_conversion("gram","atomic-mass-unit",1) + 6.022136652e+23 + >>> weight_conversion("milligram","kilogram",1) + 1e-06 + >>> weight_conversion("milligram","gram",2) + 0.002 + >>> weight_conversion("milligram","milligram",3) + 3.0 + >>> weight_conversion("milligram","metric-ton",3) + 3e-09 + >>> weight_conversion("milligram","long-ton",3) + 2.9526219e-09 + >>> weight_conversion("milligram","short-ton",1) + 1.1023122e-09 + >>> weight_conversion("milligram","pound",3) + 6.6138732605999995e-06 + >>> weight_conversion("milligram","ounce",2) + 7.054798144599999e-05 + >>> weight_conversion("milligram","carrat",1) + 0.005 + >>> weight_conversion("milligram","atomic-mass-unit",1) + 6.022136652e+20 + >>> weight_conversion("metric-ton","kilogram",2) + 2000 + >>> weight_conversion("metric-ton","gram",2) + 2000000 + >>> weight_conversion("metric-ton","milligram",3) + 3000000000 + >>> weight_conversion("metric-ton","metric-ton",2) + 2.0 + >>> weight_conversion("metric-ton","long-ton",3) + 2.9526219 + >>> weight_conversion("metric-ton","short-ton",2) + 2.2046244 + >>> weight_conversion("metric-ton","pound",3) + 6613.8732606 + >>> weight_conversion("metric-ton","ounce",4) + 141095.96289199998 + >>> weight_conversion("metric-ton","carrat",4) + 20000000 + >>> weight_conversion("metric-ton","atomic-mass-unit",1) + 6.022136652e+29 + >>> weight_conversion("long-ton","kilogram",4) + 4064.18432 + >>> weight_conversion("long-ton","gram",4) + 4064184.32 + >>> weight_conversion("long-ton","milligram",3) + 3048138240.0 + >>> weight_conversion("long-ton","metric-ton",4) + 4.06418432 + >>> weight_conversion("long-ton","long-ton",3) + 2.999999907217152 + >>> weight_conversion("long-ton","short-ton",1) + 1.119999989746176 + >>> weight_conversion("long-ton","pound",3) + 6720.000000049448 + >>> weight_conversion("long-ton","ounce",1) + 35840.000000060514 + >>> weight_conversion("long-ton","carrat",4) + 20320921.599999998 + >>> weight_conversion("long-ton","atomic-mass-unit",4) + 2.4475073353955697e+30 + >>> weight_conversion("short-ton","kilogram",3) + 2721.5519999999997 + >>> weight_conversion("short-ton","gram",3) + 2721552.0 + >>> weight_conversion("short-ton","milligram",1) + 907184000.0 + >>> weight_conversion("short-ton","metric-ton",4) + 3.628736 + >>> weight_conversion("short-ton","long-ton",3) + 2.6785713457296 + >>> weight_conversion("short-ton","short-ton",3) + 2.9999999725344 + >>> weight_conversion("short-ton","pound",2) + 4000.0000000294335 + >>> weight_conversion("short-ton","ounce",4) + 128000.00000021611 + >>> weight_conversion("short-ton","carrat",4) + 18143680.0 + >>> weight_conversion("short-ton","atomic-mass-unit",1) + 5.463186016507968e+29 + >>> weight_conversion("pound","kilogram",4) + 1.814368 + >>> weight_conversion("pound","gram",2) + 907.184 + >>> weight_conversion("pound","milligram",3) + 1360776.0 + >>> weight_conversion("pound","metric-ton",3) + 0.001360776 + >>> weight_conversion("pound","long-ton",2) + 0.0008928571152432 + >>> weight_conversion("pound","short-ton",1) + 0.0004999999954224 + >>> weight_conversion("pound","pound",3) + 3.0000000000220752 + >>> weight_conversion("pound","ounce",1) + 16.000000000027015 + >>> weight_conversion("pound","carrat",1) + 2267.96 + >>> weight_conversion("pound","atomic-mass-unit",4) + 1.0926372033015936e+27 + >>> weight_conversion("ounce","kilogram",3) + 0.0850485 + >>> weight_conversion("ounce","gram",3) + 85.0485 + >>> weight_conversion("ounce","milligram",4) + 113398.0 + >>> weight_conversion("ounce","metric-ton",4) + 0.000113398 + >>> weight_conversion("ounce","long-ton",4) + 0.0001116071394054 + >>> weight_conversion("ounce","short-ton",4) + 0.0001249999988556 + >>> weight_conversion("ounce","pound",1) + 0.0625000000004599 + >>> weight_conversion("ounce","ounce",2) + 2.000000000003377 + >>> weight_conversion("ounce","carrat",1) + 141.7475 + >>> weight_conversion("ounce","atomic-mass-unit",1) + 1.70724563015874e+25 + >>> weight_conversion("carrat","kilogram",1) + 0.0002 + >>> weight_conversion("carrat","gram",4) + 0.8 + >>> weight_conversion("carrat","milligram",2) + 400.0 + >>> weight_conversion("carrat","metric-ton",2) + 4.0000000000000003e-07 + >>> weight_conversion("carrat","long-ton",3) + 5.9052438e-07 + >>> weight_conversion("carrat","short-ton",4) + 8.818497600000002e-07 + >>> weight_conversion("carrat","pound",1) + 0.00044092488404000004 + >>> weight_conversion("carrat","ounce",2) + 0.0141095962892 + >>> weight_conversion("carrat","carrat",4) + 4.0 + >>> weight_conversion("carrat","atomic-mass-unit",4) + 4.8177093216e+23 + >>> weight_conversion("atomic-mass-unit","kilogram",4) + 6.642160796e-27 + >>> weight_conversion("atomic-mass-unit","gram",2) + 3.321080398e-24 + >>> weight_conversion("atomic-mass-unit","milligram",2) + 3.3210803980000002e-21 + >>> weight_conversion("atomic-mass-unit","metric-ton",3) + 4.9816205970000004e-30 + >>> weight_conversion("atomic-mass-unit","long-ton",3) + 4.9029473573977584e-30 + >>> weight_conversion("atomic-mass-unit","short-ton",1) + 1.830433719948128e-30 + >>> weight_conversion("atomic-mass-unit","pound",3) + 1.0982602420317504e-26 + >>> weight_conversion("atomic-mass-unit","ounce",2) + 1.1714775914938915e-25 + >>> weight_conversion("atomic-mass-unit","carrat",2) + 1.660540199e-23 + >>> weight_conversion("atomic-mass-unit","atomic-mass-unit",2) + 1.999999998903455 + """ + if to_type not in KILOGRAM_CHART or from_type not in WEIGHT_TYPE_CHART: + raise ValueError( + f"Invalid 'from_type' or 'to_type' value: {from_type!r}, {to_type!r}\n" + f"Supported values are: {', '.join(WEIGHT_TYPE_CHART)}" + ) + return value * KILOGRAM_CHART[to_type] * WEIGHT_TYPE_CHART[from_type] + + +if __name__ == "__main__": + + import doctest + + doctest.testmod() diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 3362610b9303..e0d3e4d438a8 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -8,84 +8,85 @@ import math import random +from typing import Any, List, Optional class my_queue: - def __init__(self): - self.data = [] - self.head = 0 - self.tail = 0 + def __init__(self) -> None: + self.data: List[Any] = [] + self.head: int = 0 + self.tail: int = 0 - def is_empty(self): + def is_empty(self) -> bool: return self.head == self.tail - def push(self, data): + def push(self, data: Any) -> None: self.data.append(data) self.tail = self.tail + 1 - def pop(self): + def pop(self) -> Any: ret = self.data[self.head] self.head = self.head + 1 return ret - def count(self): + def count(self) -> int: return self.tail - self.head - def print(self): + def print(self) -> None: print(self.data) print("**************") print(self.data[self.head : self.tail]) class my_node: - def __init__(self, data): + def __init__(self, data: Any) -> None: self.data = data - self.left = None - self.right = None - self.height = 1 + self.left: Optional[my_node] = None + self.right: Optional[my_node] = None + self.height: int = 1 - def get_data(self): + def get_data(self) -> Any: return self.data - def get_left(self): + def get_left(self) -> Optional["my_node"]: return self.left - def get_right(self): + def get_right(self) -> Optional["my_node"]: return self.right - def get_height(self): + def get_height(self) -> int: return self.height - def set_data(self, data): + def set_data(self, data: Any) -> None: self.data = data return - def set_left(self, node): + def set_left(self, node: Optional["my_node"]) -> None: self.left = node return - def set_right(self, node): + def set_right(self, node: Optional["my_node"]) -> None: self.right = node return - def set_height(self, height): + def set_height(self, height: int) -> None: self.height = height return -def get_height(node): +def get_height(node: Optional["my_node"]) -> int: if node is None: return 0 return node.get_height() -def my_max(a, b): +def my_max(a: int, b: int) -> int: if a > b: return a return b -def right_rotation(node): +def right_rotation(node: my_node) -> my_node: r""" A B / \ / \ @@ -98,6 +99,7 @@ def right_rotation(node): """ print("left rotation node:", node.get_data()) ret = node.get_left() + assert ret is not None node.set_left(ret.get_right()) ret.set_right(node) h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 @@ -107,12 +109,13 @@ def right_rotation(node): return ret -def left_rotation(node): +def left_rotation(node: my_node) -> my_node: """ a mirror symmetry rotation of the left_rotation """ print("right rotation node:", node.get_data()) ret = node.get_right() + assert ret is not None node.set_right(ret.get_left()) ret.set_left(node) h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 @@ -122,7 +125,7 @@ def left_rotation(node): return ret -def lr_rotation(node): +def lr_rotation(node: my_node) -> my_node: r""" A A Br / \ / \ / \ @@ -133,16 +136,20 @@ def lr_rotation(node): UB Bl RR = right_rotation LR = left_rotation """ - node.set_left(left_rotation(node.get_left())) + left_child = node.get_left() + assert left_child is not None + node.set_left(left_rotation(left_child)) return right_rotation(node) -def rl_rotation(node): - node.set_right(right_rotation(node.get_right())) +def rl_rotation(node: my_node) -> my_node: + right_child = node.get_right() + assert right_child is not None + node.set_right(right_rotation(right_child)) return left_rotation(node) -def insert_node(node, data): +def insert_node(node: Optional["my_node"], data: Any) -> Optional["my_node"]: if node is None: return my_node(data) if data < node.get_data(): @@ -150,8 +157,10 @@ def insert_node(node, data): if ( get_height(node.get_left()) - get_height(node.get_right()) == 2 ): # an unbalance detected + left_child = node.get_left() + assert left_child is not None if ( - data < node.get_left().get_data() + data < left_child.get_data() ): # new node is the left child of the left child node = right_rotation(node) else: @@ -159,7 +168,9 @@ def insert_node(node, data): else: node.set_right(insert_node(node.get_right(), data)) if get_height(node.get_right()) - get_height(node.get_left()) == 2: - if data < node.get_right().get_data(): + right_child = node.get_right() + assert right_child is not None + if data < right_child.get_data(): node = rl_rotation(node) else: node = left_rotation(node) @@ -168,52 +179,59 @@ def insert_node(node, data): return node -def get_rightMost(root): - while root.get_right() is not None: - root = root.get_right() +def get_rightMost(root: my_node) -> Any: + while True: + right_child = root.get_right() + if right_child is None: + break + root = right_child return root.get_data() -def get_leftMost(root): - while root.get_left() is not None: - root = root.get_left() +def get_leftMost(root: my_node) -> Any: + while True: + left_child = root.get_left() + if left_child is None: + break + root = left_child return root.get_data() -def del_node(root, data): +def del_node(root: my_node, data: Any) -> Optional["my_node"]: + left_child = root.get_left() + right_child = root.get_right() if root.get_data() == data: - if root.get_left() is not None and root.get_right() is not None: - temp_data = get_leftMost(root.get_right()) + if left_child is not None and right_child is not None: + temp_data = get_leftMost(right_child) root.set_data(temp_data) - root.set_right(del_node(root.get_right(), temp_data)) - elif root.get_left() is not None: - root = root.get_left() + root.set_right(del_node(right_child, temp_data)) + elif left_child is not None: + root = left_child + elif right_child is not None: + root = right_child else: - root = root.get_right() + return None elif root.get_data() > data: - if root.get_left() is None: + if left_child is None: print("No such data") return root else: - root.set_left(del_node(root.get_left(), data)) - elif root.get_data() < data: - if root.get_right() is None: + root.set_left(del_node(left_child, data)) + else: # root.get_data() < data + if right_child is None: return root else: - root.set_right(del_node(root.get_right(), data)) - if root is None: - return root - if get_height(root.get_right()) - get_height(root.get_left()) == 2: - if get_height(root.get_right().get_right()) > get_height( - root.get_right().get_left() - ): + root.set_right(del_node(right_child, data)) + + if get_height(right_child) - get_height(left_child) == 2: + assert right_child is not None + if get_height(right_child.get_right()) > get_height(right_child.get_left()): root = left_rotation(root) else: root = rl_rotation(root) - elif get_height(root.get_right()) - get_height(root.get_left()) == -2: - if get_height(root.get_left().get_left()) > get_height( - root.get_left().get_right() - ): + elif get_height(right_child) - get_height(left_child) == -2: + assert left_child is not None + if get_height(left_child.get_left()) > get_height(left_child.get_right()): root = right_rotation(root) else: root = lr_rotation(root) @@ -256,25 +274,26 @@ class AVLtree: ************************************* """ - def __init__(self): - self.root = None + def __init__(self) -> None: + self.root: Optional[my_node] = None - def get_height(self): - # print("yyy") + def get_height(self) -> int: return get_height(self.root) - def insert(self, data): + def insert(self, data: Any) -> None: print("insert:" + str(data)) self.root = insert_node(self.root, data) - def del_node(self, data): + def del_node(self, data: Any) -> None: print("delete:" + str(data)) if self.root is None: print("Tree is empty!") return self.root = del_node(self.root, data) - def __str__(self): # a level traversale, gives a more intuitive look on the tree + def __str__( + self, + ) -> str: # a level traversale, gives a more intuitive look on the tree output = "" q = my_queue() q.push(self.root) @@ -308,7 +327,7 @@ def __str__(self): # a level traversale, gives a more intuitive look on the tre return output -def _test(): +def _test() -> None: import doctest doctest.testmod() diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index f1e46e33cd24..a05e28a7bd54 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -8,21 +8,22 @@ python binary_search_tree_recursive.py """ import unittest +from typing import Iterator, Optional class Node: - def __init__(self, label: int, parent): + def __init__(self, label: int, parent: Optional["Node"]) -> None: self.label = label self.parent = parent - self.left = None - self.right = None + self.left: Optional[Node] = None + self.right: Optional[Node] = None class BinarySearchTree: - def __init__(self): - self.root = None + def __init__(self) -> None: + self.root: Optional[Node] = None - def empty(self): + def empty(self) -> None: """ Empties the tree @@ -46,7 +47,7 @@ def is_empty(self) -> bool: """ return self.root is None - def put(self, label: int): + def put(self, label: int) -> None: """ Put a new node in the tree @@ -65,7 +66,9 @@ def put(self, label: int): """ self.root = self._put(self.root, label) - def _put(self, node: Node, label: int, parent: Node = None) -> Node: + def _put( + self, node: Optional[Node], label: int, parent: Optional[Node] = None + ) -> Node: if node is None: node = Node(label, parent) else: @@ -95,7 +98,7 @@ def search(self, label: int) -> Node: """ return self._search(self.root, label) - def _search(self, node: Node, label: int) -> Node: + def _search(self, node: Optional[Node], label: int) -> Node: if node is None: raise Exception(f"Node with label {label} does not exist") else: @@ -106,7 +109,7 @@ def _search(self, node: Node, label: int) -> Node: return node - def remove(self, label: int): + def remove(self, label: int) -> None: """ Removes a node in the tree @@ -122,13 +125,7 @@ def remove(self, label: int): Exception: Node with label 3 does not exist """ node = self.search(label) - if not node.right and not node.left: - self._reassign_nodes(node, None) - elif not node.right and node.left: - self._reassign_nodes(node, node.left) - elif node.right and not node.left: - self._reassign_nodes(node, node.right) - else: + if node.right and node.left: lowest_node = self._get_lowest_node(node.right) lowest_node.left = node.left lowest_node.right = node.right @@ -136,8 +133,14 @@ def remove(self, label: int): if node.right: node.right.parent = lowest_node self._reassign_nodes(node, lowest_node) + elif not node.right and node.left: + self._reassign_nodes(node, node.left) + elif node.right and not node.left: + self._reassign_nodes(node, node.right) + else: + self._reassign_nodes(node, None) - def _reassign_nodes(self, node: Node, new_children: Node): + def _reassign_nodes(self, node: Node, new_children: Optional[Node]) -> None: if new_children: new_children.parent = node.parent @@ -192,7 +195,7 @@ def get_max_label(self) -> int: >>> t.get_max_label() 10 """ - if self.is_empty(): + if self.root is None: raise Exception("Binary search tree is empty") node = self.root @@ -216,7 +219,7 @@ def get_min_label(self) -> int: >>> t.get_min_label() 8 """ - if self.is_empty(): + if self.root is None: raise Exception("Binary search tree is empty") node = self.root @@ -225,7 +228,7 @@ def get_min_label(self) -> int: return node.label - def inorder_traversal(self) -> list: + def inorder_traversal(self) -> Iterator[Node]: """ Return the inorder traversal of the tree @@ -241,13 +244,13 @@ def inorder_traversal(self) -> list: """ return self._inorder_traversal(self.root) - def _inorder_traversal(self, node: Node) -> list: + def _inorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: if node is not None: yield from self._inorder_traversal(node.left) yield node yield from self._inorder_traversal(node.right) - def preorder_traversal(self) -> list: + def preorder_traversal(self) -> Iterator[Node]: """ Return the preorder traversal of the tree @@ -263,7 +266,7 @@ def preorder_traversal(self) -> list: """ return self._preorder_traversal(self.root) - def _preorder_traversal(self, node: Node) -> list: + def _preorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: if node is not None: yield node yield from self._preorder_traversal(node.left) @@ -272,7 +275,7 @@ def _preorder_traversal(self, node: Node) -> list: class BinarySearchTreeTest(unittest.TestCase): @staticmethod - def _get_binary_search_tree(): + def _get_binary_search_tree() -> BinarySearchTree: r""" 8 / \ @@ -298,7 +301,7 @@ def _get_binary_search_tree(): return t - def test_put(self): + def test_put(self) -> None: t = BinarySearchTree() assert t.is_empty() @@ -306,6 +309,7 @@ def test_put(self): r""" 8 """ + assert t.root is not None assert t.root.parent is None assert t.root.label == 8 @@ -315,6 +319,7 @@ def test_put(self): \ 10 """ + assert t.root.right is not None assert t.root.right.parent == t.root assert t.root.right.label == 10 @@ -324,6 +329,7 @@ def test_put(self): / \ 3 10 """ + assert t.root.left is not None assert t.root.left.parent == t.root assert t.root.left.label == 3 @@ -335,6 +341,7 @@ def test_put(self): \ 6 """ + assert t.root.left.right is not None assert t.root.left.right.parent == t.root.left assert t.root.left.right.label == 6 @@ -346,13 +353,14 @@ def test_put(self): / \ 1 6 """ + assert t.root.left.left is not None assert t.root.left.left.parent == t.root.left assert t.root.left.left.label == 1 with self.assertRaises(Exception): t.put(1) - def test_search(self): + def test_search(self) -> None: t = self._get_binary_search_tree() node = t.search(6) @@ -364,7 +372,7 @@ def test_search(self): with self.assertRaises(Exception): t.search(2) - def test_remove(self): + def test_remove(self) -> None: t = self._get_binary_search_tree() t.remove(13) @@ -379,6 +387,9 @@ def test_remove(self): \ 5 """ + assert t.root is not None + assert t.root.right is not None + assert t.root.right.right is not None assert t.root.right.right.right is None assert t.root.right.right.left is None @@ -394,6 +405,9 @@ def test_remove(self): \ 5 """ + assert t.root.left is not None + assert t.root.left.right is not None + assert t.root.left.right.left is not None assert t.root.left.right.right is None assert t.root.left.right.left.label == 4 @@ -407,6 +421,8 @@ def test_remove(self): \ 5 """ + assert t.root.left.left is not None + assert t.root.left.right.right is not None assert t.root.left.left.label == 1 assert t.root.left.right.label == 4 assert t.root.left.right.right.label == 5 @@ -422,6 +438,7 @@ def test_remove(self): / \ \ 1 5 14 """ + assert t.root is not None assert t.root.left.label == 4 assert t.root.left.right.label == 5 assert t.root.left.left.label == 1 @@ -437,13 +454,15 @@ def test_remove(self): / \ 1 14 """ + assert t.root.left is not None + assert t.root.left.left is not None assert t.root.left.label == 5 assert t.root.left.right is None assert t.root.left.left.label == 1 assert t.root.left.parent == t.root assert t.root.left.left.parent == t.root.left - def test_remove_2(self): + def test_remove_2(self) -> None: t = self._get_binary_search_tree() t.remove(3) @@ -456,6 +475,12 @@ def test_remove_2(self): / \ / 5 7 13 """ + assert t.root is not None + assert t.root.left is not None + assert t.root.left.left is not None + assert t.root.left.right is not None + assert t.root.left.right.left is not None + assert t.root.left.right.right is not None assert t.root.left.label == 4 assert t.root.left.right.label == 6 assert t.root.left.left.label == 1 @@ -466,25 +491,25 @@ def test_remove_2(self): assert t.root.left.left.parent == t.root.left assert t.root.left.right.left.parent == t.root.left.right - def test_empty(self): + def test_empty(self) -> None: t = self._get_binary_search_tree() t.empty() assert t.root is None - def test_is_empty(self): + def test_is_empty(self) -> None: t = self._get_binary_search_tree() assert not t.is_empty() t.empty() assert t.is_empty() - def test_exists(self): + def test_exists(self) -> None: t = self._get_binary_search_tree() assert t.exists(6) assert not t.exists(-1) - def test_get_max_label(self): + def test_get_max_label(self) -> None: t = self._get_binary_search_tree() assert t.get_max_label() == 14 @@ -493,7 +518,7 @@ def test_get_max_label(self): with self.assertRaises(Exception): t.get_max_label() - def test_get_min_label(self): + def test_get_min_label(self) -> None: t = self._get_binary_search_tree() assert t.get_min_label() == 1 @@ -502,20 +527,20 @@ def test_get_min_label(self): with self.assertRaises(Exception): t.get_min_label() - def test_inorder_traversal(self): + def test_inorder_traversal(self) -> None: t = self._get_binary_search_tree() inorder_traversal_nodes = [i.label for i in t.inorder_traversal()] assert inorder_traversal_nodes == [1, 3, 4, 5, 6, 7, 8, 10, 13, 14] - def test_preorder_traversal(self): + def test_preorder_traversal(self) -> None: t = self._get_binary_search_tree() preorder_traversal_nodes = [i.label for i in t.preorder_traversal()] assert preorder_traversal_nodes == [8, 3, 1, 6, 4, 5, 7, 10, 14, 13] -def binary_search_tree_example(): +def binary_search_tree_example() -> None: r""" Example 8 diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 5bc79e74efcd..9066db294613 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +from typing import List, Union class SegmentTree: @@ -37,7 +38,7 @@ def right(self, idx: int) -> int: return idx * 2 + 1 def build( - self, idx: int, left_element: int, right_element: int, A: list[int] + self, idx: int, left_element: int, right_element: int, A: List[int] ) -> None: if left_element == right_element: self.segment_tree[idx] = A[left_element - 1] @@ -88,7 +89,7 @@ def update( # query with O(lg n) def query( self, idx: int, left_element: int, right_element: int, a: int, b: int - ) -> int: + ) -> Union[int, float]: """ query(1, 1, size, a, b) for query max of [a,b] >>> A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] @@ -118,8 +119,8 @@ def query( q2 = self.query(self.right(idx), mid + 1, right_element, a, b) return max(q1, q2) - def __str__(self) -> None: - return [self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)] + def __str__(self) -> str: + return str([self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)]) if __name__ == "__main__": diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py new file mode 100644 index 000000000000..6b202adb3cf5 --- /dev/null +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -0,0 +1,93 @@ +#!/usr/local/bin/python3 +""" +Problem Description: Given two binary tree, return the merged tree. +The rule for merging is that if two nodes overlap, then put the value sum of +both nodes to the new value of the merged node. Otherwise, the NOT null node +will be used as the node of new tree. +""" +from typing import Optional + + +class Node: + """ + A binary node has value variable and pointers to its left and right node. + """ + + def __init__(self, value: int = 0) -> None: + self.value = value + self.left: Optional[Node] = None + self.right: Optional[Node] = None + + +def merge_two_binary_trees(tree1: Optional[Node], tree2: Optional[Node]) -> Node: + """ + Returns root node of the merged tree. + + >>> tree1 = Node(5) + >>> tree1.left = Node(6) + >>> tree1.right = Node(7) + >>> tree1.left.left = Node(2) + >>> tree2 = Node(4) + >>> tree2.left = Node(5) + >>> tree2.right = Node(8) + >>> tree2.left.right = Node(1) + >>> tree2.right.right = Node(4) + >>> merged_tree = merge_two_binary_trees(tree1, tree2) + >>> print_preorder(merged_tree) + 9 + 11 + 2 + 1 + 15 + 4 + """ + if tree1 is None: + return tree2 + if tree2 is None: + return tree1 + + tree1.value = tree1.value + tree2.value + tree1.left = merge_two_binary_trees(tree1.left, tree2.left) + tree1.right = merge_two_binary_trees(tree1.right, tree2.right) + return tree1 + + +def print_preorder(root: Optional[Node]) -> None: + """ + Print pre-order traversal of the tree. + + >>> root = Node(1) + >>> root.left = Node(2) + >>> root.right = Node(3) + >>> print_preorder(root) + 1 + 2 + 3 + >>> print_preorder(root.right) + 3 + """ + if root: + print(root.value) + print_preorder(root.left) + print_preorder(root.right) + + +if __name__ == "__main__": + tree1 = Node(1) + tree1.left = Node(2) + tree1.right = Node(3) + tree1.left.left = Node(4) + + tree2 = Node(2) + tree2.left = Node(4) + tree2.right = Node(6) + tree2.left.right = Node(9) + tree2.right.right = Node(5) + + print("Tree1 is: ") + print_preorder(tree1) + print("Tree2 is: ") + print_preorder(tree2) + merged_tree = merge_two_binary_trees(tree1, tree2) + print("Merged Tree is: ") + print_preorder(merged_tree) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 064e5aded7b4..c914079e0a8d 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -49,7 +49,7 @@ def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None: :param arr: list of elements for the segment tree :param fnc: commutative function for combine two elements - >>> SegmentTree(['a', 'b', 'c'], lambda a, b: '{}{}'.format(a, b)).query(0, 2) + >>> SegmentTree(['a', 'b', 'c'], lambda a, b: f'{a}{b}').query(0, 2) 'abc' >>> SegmentTree([(1, 2), (2, 3), (3, 4)], ... lambda a, b: (a[0] + b[0], a[1] + b[1])).query(0, 2) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 5d721edfa45b..de971a712fc1 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -475,11 +475,13 @@ def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: - return "'{} {}'".format(self.label, (self.color and "red") or "blk") + return f"'{self.label} {(self.color and 'red') or 'blk'}'" return pformat( { - "%s %s" - % (self.label, (self.color and "red") or "blk"): (self.left, self.right) + f"{self.label} {(self.color and 'red') or 'blk'}": ( + self.left, + self.right, + ) }, indent=1, ) diff --git a/data_structures/binary_tree/segment_tree_other.py b/data_structures/binary_tree/segment_tree_other.py index df98eeffb3c6..90afd7ca8b71 100644 --- a/data_structures/binary_tree/segment_tree_other.py +++ b/data_structures/binary_tree/segment_tree_other.py @@ -7,7 +7,7 @@ from queue import Queue -class SegmentTreeNode(object): +class SegmentTreeNode: def __init__(self, start, end, val, left=None, right=None): self.start = start self.end = end @@ -17,10 +17,10 @@ def __init__(self, start, end, val, left=None, right=None): self.right = right def __str__(self): - return "val: %s, start: %s, end: %s" % (self.val, self.start, self.end) + return f"val: {self.val}, start: {self.start}, end: {self.end}" -class SegmentTree(object): +class SegmentTree: """ >>> import operator >>> num_arr = SegmentTree([2, 1, 5, 3, 4], operator.add) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 26648f7aba61..a09dcc928143 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -3,6 +3,7 @@ from __future__ import annotations from random import random +from typing import Optional, Tuple class Node: @@ -11,13 +12,13 @@ class Node: Treap is a binary tree by value and heap by priority """ - def __init__(self, value: int = None): + def __init__(self, value: Optional[int] = None): self.value = value self.prior = random() - self.left = None - self.right = None + self.left: Optional[Node] = None + self.right: Optional[Node] = None - def __repr__(self): + def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: @@ -27,14 +28,14 @@ def __repr__(self): {f"{self.value}: {self.prior:.5}": (self.left, self.right)}, indent=1 ) - def __str__(self): + def __str__(self) -> str: value = str(self.value) + " " left = str(self.left or "") right = str(self.right or "") return value + left + right -def split(root: Node, value: int) -> tuple[Node, Node]: +def split(root: Optional[Node], value: int) -> Tuple[Optional[Node], Optional[Node]]: """ We split current tree into 2 trees with value: @@ -42,9 +43,9 @@ def split(root: Node, value: int) -> tuple[Node, Node]: Right tree contains all values greater or equal, than split value """ if root is None: # None tree is split into 2 Nones - return (None, None) + return None, None elif root.value is None: - return (None, None) + return None, None else: if value < root.value: """ @@ -54,16 +55,16 @@ def split(root: Node, value: int) -> tuple[Node, Node]: Right tree's left son: right part of that split """ left, root.left = split(root.left, value) - return (left, root) + return left, root else: """ Just symmetric to previous case """ root.right, right = split(root.right, value) - return (root, right) + return root, right -def merge(left: Node, right: Node) -> Node: +def merge(left: Optional[Node], right: Optional[Node]) -> Optional[Node]: """ We merge 2 trees into one. Note: all left tree's values must be less than all right tree's @@ -85,7 +86,7 @@ def merge(left: Node, right: Node) -> Node: return right -def insert(root: Node, value: int) -> Node: +def insert(root: Optional[Node], value: int) -> Optional[Node]: """ Insert element @@ -98,7 +99,7 @@ def insert(root: Node, value: int) -> Node: return merge(merge(left, node), right) -def erase(root: Node, value: int) -> Node: +def erase(root: Optional[Node], value: int) -> Optional[Node]: """ Erase element @@ -111,7 +112,7 @@ def erase(root: Node, value: int) -> Node: return merge(left, right) -def inorder(root: Node): +def inorder(root: Optional[Node]) -> None: """ Just recursive print of a tree """ @@ -123,7 +124,7 @@ def inorder(root: Node): inorder(root.right) -def interactTreap(root, args): +def interactTreap(root: Optional[Node], args: str) -> Optional[Node]: """ Commands: + value to add value into treap @@ -160,7 +161,7 @@ def interactTreap(root, args): return root -def main(): +def main() -> None: """After each command, program prints treap""" root = None print( diff --git a/data_structures/heap/heap.py b/data_structures/heap/heap.py index b901c54a4284..8592362c23b9 100644 --- a/data_structures/heap/heap.py +++ b/data_structures/heap/heap.py @@ -1,101 +1,138 @@ -#!/usr/bin/python3 +from typing import Iterable, List, Optional -class Heap(object): - """ +class Heap: + """A Max Heap Implementation + >>> unsorted = [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5] >>> h = Heap() - >>> h.build_heap(unsorted) - >>> h.display() + >>> h.build_max_heap(unsorted) + >>> print(h) [209, 201, 25, 103, 107, 15, 1, 9, 7, 11, 5] >>> - >>> h.get_max() + >>> h.extract_max() 209 - >>> h.display() + >>> print(h) [201, 107, 25, 103, 11, 15, 1, 9, 7, 5] >>> >>> h.insert(100) - >>> h.display() + >>> print(h) [201, 107, 25, 103, 100, 15, 1, 9, 7, 5, 11] >>> >>> h.heap_sort() - >>> h.display() + >>> print(h) [1, 5, 7, 9, 11, 15, 25, 100, 103, 107, 201] - >>> """ - def __init__(self): - self.h = [] - self.curr_size = 0 + def __init__(self) -> None: + self.h: List[float] = [] + self.heap_size: int = 0 + + def __repr__(self) -> str: + return str(self.h) - def get_left_child_index(self, i): - left_child_index = 2 * i + 1 - if left_child_index < self.curr_size: + def parent_index(self, child_idx: int) -> Optional[int]: + """ return the parent index of given child """ + if child_idx > 0: + return (child_idx - 1) // 2 + return None + + def left_child_idx(self, parent_idx: int) -> Optional[int]: + """ + return the left child index if the left child exists. + if not, return None. + """ + left_child_index = 2 * parent_idx + 1 + if left_child_index < self.heap_size: return left_child_index return None - def get_right_child(self, i): - right_child_index = 2 * i + 2 - if right_child_index < self.curr_size: + def right_child_idx(self, parent_idx: int) -> Optional[int]: + """ + return the right child index if the right child exists. + if not, return None. + """ + right_child_index = 2 * parent_idx + 2 + if right_child_index < self.heap_size: return right_child_index return None - def max_heapify(self, index): - if index < self.curr_size: - largest = index - lc = self.get_left_child_index(index) - rc = self.get_right_child(index) - if lc is not None and self.h[lc] > self.h[largest]: - largest = lc - if rc is not None and self.h[rc] > self.h[largest]: - largest = rc - if largest != index: - self.h[largest], self.h[index] = self.h[index], self.h[largest] - self.max_heapify(largest) - - def build_heap(self, collection): - self.curr_size = len(collection) + def max_heapify(self, index: int) -> None: + """ + correct a single violation of the heap property in a subtree's root. + """ + if index < self.heap_size: + violation: int = index + left_child = self.left_child_idx(index) + right_child = self.right_child_idx(index) + # check which child is larger than its parent + if left_child is not None and self.h[left_child] > self.h[violation]: + violation = left_child + if right_child is not None and self.h[right_child] > self.h[violation]: + violation = right_child + # if violation indeed exists + if violation != index: + # swap to fix the violation + self.h[violation], self.h[index] = self.h[index], self.h[violation] + # fix the subsequent violation recursively if any + self.max_heapify(violation) + + def build_max_heap(self, collection: Iterable[float]) -> None: + """ build max heap from an unsorted array""" self.h = list(collection) - if self.curr_size <= 1: - return - for i in range(self.curr_size // 2 - 1, -1, -1): - self.max_heapify(i) - - def get_max(self): - if self.curr_size >= 2: + self.heap_size = len(self.h) + if self.heap_size > 1: + # max_heapify from right to left but exclude leaves (last level) + for i in range(self.heap_size // 2 - 1, -1, -1): + self.max_heapify(i) + + def max(self) -> float: + """ return the max in the heap """ + if self.heap_size >= 1: + return self.h[0] + else: + raise Exception("Empty heap") + + def extract_max(self) -> float: + """ get and remove max from heap """ + if self.heap_size >= 2: me = self.h[0] self.h[0] = self.h.pop(-1) - self.curr_size -= 1 + self.heap_size -= 1 self.max_heapify(0) return me - elif self.curr_size == 1: - self.curr_size -= 1 + elif self.heap_size == 1: + self.heap_size -= 1 return self.h.pop(-1) - return None - - def heap_sort(self): - size = self.curr_size + else: + raise Exception("Empty heap") + + def insert(self, value: float) -> None: + """ insert a new value into the max heap """ + self.h.append(value) + idx = (self.heap_size - 1) // 2 + self.heap_size += 1 + while idx >= 0: + self.max_heapify(idx) + idx = (idx - 1) // 2 + + def heap_sort(self) -> None: + size = self.heap_size for j in range(size - 1, 0, -1): self.h[0], self.h[j] = self.h[j], self.h[0] - self.curr_size -= 1 + self.heap_size -= 1 self.max_heapify(0) - self.curr_size = size + self.heap_size = size - def insert(self, data): - self.h.append(data) - curr = (self.curr_size - 1) // 2 - self.curr_size += 1 - while curr >= 0: - self.max_heapify(curr) - curr = (curr - 1) // 2 - def display(self): - print(self.h) +if __name__ == "__main__": + import doctest + # run doc test + doctest.testmod() -def main(): + # demo for unsorted in [ - [], [0], [2], [3, 5], @@ -110,26 +147,17 @@ def main(): [103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5], [-45, -2, -5], ]: - print("source unsorted list: %s" % unsorted) + print(f"unsorted array: {unsorted}") - h = Heap() - h.build_heap(unsorted) - print("after build heap: ", end=" ") - h.display() + heap = Heap() + heap.build_max_heap(unsorted) + print(f"after build heap: {heap}") - print("max value: %s" % h.get_max()) - print("delete max value: ", end=" ") - h.display() + print(f"max value: {heap.extract_max()}") + print(f"after max value removed: {heap}") - h.insert(100) - print("after insert new value 100: ", end=" ") - h.display() + heap.insert(100) + print(f"after new value 100 inserted: {heap}") - h.heap_sort() - print("heap sort: ", end=" ") - h.display() - print() - - -if __name__ == "__main__": - main() + heap.heap_sort() + print(f"heap-sorted array: {heap}\n") diff --git a/data_structures/linked_list/__init__.py b/data_structures/linked_list/__init__.py index 3ddfea5c5abf..a5f5537b1d96 100644 --- a/data_structures/linked_list/__init__.py +++ b/data_structures/linked_list/__init__.py @@ -1,19 +1,30 @@ +""" +Linked Lists consists of Nodes. +Nodes contain data and also may link to other nodes: + - Head Node: First node, the address of the + head node gives us access of the complete list + - Last node: points to null +""" + +from typing import Any + + class Node: - def __init__(self, item, next): + def __init__(self, item: Any, next: Any) -> None: self.item = item self.next = next class LinkedList: - def __init__(self): + def __init__(self) -> None: self.head = None self.size = 0 - def add(self, item): + def add(self, item: Any) -> None: self.head = Node(item, self.head) self.size += 1 - def remove(self): + def remove(self) -> Any: if self.is_empty(): return None else: @@ -22,10 +33,33 @@ def remove(self): self.size -= 1 return item - def is_empty(self): + def is_empty(self) -> bool: return self.head is None - def __len__(self): + def __str__(self) -> str: + """ + >>> linked_list = LinkedList() + >>> linked_list.add(23) + >>> linked_list.add(14) + >>> linked_list.add(9) + >>> print(linked_list) + 9 --> 14 --> 23 + """ + if not self.is_empty: + return "" + else: + iterate = self.head + item_str = "" + item_list = [] + while iterate: + item_list.append(str(iterate.item)) + iterate = iterate.next + + item_str = " --> ".join(item_list) + + return item_str + + def __len__(self) -> int: """ >>> linked_list = LinkedList() >>> len(linked_list) diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index b93fb8c4005e..c9ae8b3d1ba2 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -20,8 +20,8 @@ def __init__(self, link_p, element, link_n): self._next = link_n def has_next_and_prev(self): - return " Prev -> {0}, Next -> {1}".format( - self._prev is not None, self._next is not None + return ( + f" Prev -> {self._prev is not None}, Next -> {self._next is not None}" ) def __init__(self): diff --git a/data_structures/linked_list/doubly_linked_list.py b/data_structures/linked_list/doubly_linked_list.py index 1b4005f59fae..0eb3cf101a3e 100644 --- a/data_structures/linked_list/doubly_linked_list.py +++ b/data_structures/linked_list/doubly_linked_list.py @@ -1,89 +1,156 @@ """ -- A linked list is similar to an array, it holds values. However, links in a linked - list do not have indexes. -- This is an example of a double ended, doubly linked list. -- Each link references the next link and the previous one. -- A Doubly Linked List (DLL) contains an extra pointer, typically called previous - pointer, together with next pointer and data which are there in singly linked list. - - Advantages over SLL - It can be traversed in both forward and backward direction. - Delete operation is more efficient""" - +https://en.wikipedia.org/wiki/Doubly_linked_list +""" -class LinkedList: - """ - >>> linked_list = LinkedList() - >>> linked_list.insert_at_head("a") - >>> linked_list.insert_at_tail("b") - >>> linked_list.delete_tail() - 'b' - >>> linked_list.is_empty - False - >>> linked_list.delete_head() - 'a' - >>> linked_list.is_empty - True - """ - def __init__(self): - self.head = None # First node in list - self.tail = None # Last node in list +class Node: + def __init__(self, data): + self.data = data + self.previous = None + self.next = None def __str__(self): - current = self.head - nodes = [] - while current is not None: - nodes.append(current) - current = current.next - return " ".join(str(node) for node in nodes) - - def insert_at_head(self, data): - new_node = Node(data) - if self.is_empty: - self.tail = new_node - self.head = new_node - else: - self.head.previous = new_node - new_node.next = self.head - self.head = new_node + return f"{self.data}" - def delete_head(self) -> str: - if self.is_empty: - return "List is empty" - head_data = self.head.data - if self.head.next: - self.head = self.head.next - self.head.previous = None +class DoublyLinkedList: + def __init__(self): + self.head = None + self.tail = None + + def __iter__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_head('b') + >>> linked_list.insert_at_head('a') + >>> linked_list.insert_at_tail('c') + >>> tuple(linked_list) + ('a', 'b', 'c') + """ + node = self.head + while node: + yield node.data + node = node.next - else: # If there is no next previous node - self.head = None - self.tail = None + def __str__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_tail('a') + >>> linked_list.insert_at_tail('b') + >>> linked_list.insert_at_tail('c') + >>> str(linked_list) + 'a->b->c' + """ + return "->".join([str(item) for item in self]) + + def __len__(self): + """ + >>> linked_list = DoublyLinkedList() + >>> for i in range(0, 5): + ... linked_list.insert_at_nth(i, i + 1) + >>> len(linked_list) == 5 + True + """ + return len(tuple(iter(self))) - return head_data + def insert_at_head(self, data): + self.insert_at_nth(0, data) def insert_at_tail(self, data): + self.insert_at_nth(len(self), data) + + def insert_at_nth(self, index: int, data): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.insert_at_nth(-1, 666) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> linked_list.insert_at_nth(1, 666) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> linked_list.insert_at_nth(0, 2) + >>> linked_list.insert_at_nth(0, 1) + >>> linked_list.insert_at_nth(2, 4) + >>> linked_list.insert_at_nth(2, 3) + >>> str(linked_list) + '1->2->3->4' + >>> linked_list.insert_at_nth(5, 5) + Traceback (most recent call last): + .... + IndexError: list index out of range + """ + if not 0 <= index <= len(self): + raise IndexError("list index out of range") new_node = Node(data) - if self.is_empty: - self.tail = new_node + if self.head is None: + self.head = self.tail = new_node + elif index == 0: + self.head.previous = new_node + new_node.next = self.head self.head = new_node - else: + elif index == len(self): self.tail.next = new_node new_node.previous = self.tail self.tail = new_node - - def delete_tail(self) -> str: - if self.is_empty: - return "List is empty" - - tail_data = self.tail.data - if self.tail.previous: + else: + temp = self.head + for i in range(0, index): + temp = temp.next + temp.previous.next = new_node + new_node.previous = temp.previous + new_node.next = temp + temp.previous = new_node + + def delete_head(self): + return self.delete_at_nth(0) + + def delete_tail(self): + return self.delete_at_nth(len(self) - 1) + + def delete_at_nth(self, index: int): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.delete_at_nth(0) + Traceback (most recent call last): + .... + IndexError: list index out of range + >>> for i in range(0, 5): + ... linked_list.insert_at_nth(i, i + 1) + >>> linked_list.delete_at_nth(0) == 1 + True + >>> linked_list.delete_at_nth(3) == 5 + True + >>> linked_list.delete_at_nth(1) == 3 + True + >>> str(linked_list) + '2->4' + >>> linked_list.delete_at_nth(2) + Traceback (most recent call last): + .... + IndexError: list index out of range + """ + if not 0 <= index <= len(self) - 1: + raise IndexError("list index out of range") + delete_node = self.head # default first node + if len(self) == 1: + self.head = self.tail = None + elif index == 0: + self.head = self.head.next + self.head.previous = None + elif index == len(self) - 1: + delete_node = self.tail self.tail = self.tail.previous self.tail.next = None - else: # if there is no previous node - self.head = None - self.tail = None - - return tail_data + else: + temp = self.head + for i in range(0, index): + temp = temp.next + delete_node = temp + temp.next.previous = temp.previous + temp.previous.next = temp.next + return delete_node.data def delete(self, data) -> str: current = self.head @@ -105,16 +172,55 @@ def delete(self, data) -> str: current.next.previous = current.previous # 1 <--> 3 return data - @property - def is_empty(self): # return True if the list is empty - return self.head is None + def is_empty(self): + """ + >>> linked_list = DoublyLinkedList() + >>> linked_list.is_empty() + True + >>> linked_list.insert_at_tail(1) + >>> linked_list.is_empty() + False + """ + return len(self) == 0 -class Node: - def __init__(self, data): - self.data = data - self.previous = None - self.next = None - - def __str__(self): - return f"{self.data}" +def test_doubly_linked_list() -> None: + """ + >>> test_doubly_linked_list() + """ + linked_list = DoublyLinkedList() + assert linked_list.is_empty() is True + assert str(linked_list) == "" + + try: + linked_list.delete_head() + assert False # This should not happen. + except IndexError: + assert True # This should happen. + + try: + linked_list.delete_tail() + assert False # This should not happen. + except IndexError: + assert True # This should happen. + + for i in range(10): + assert len(linked_list) == i + linked_list.insert_at_nth(i, i + 1) + assert str(linked_list) == "->".join(str(i) for i in range(1, 11)) + + linked_list.insert_at_head(0) + linked_list.insert_at_tail(11) + assert str(linked_list) == "->".join(str(i) for i in range(0, 12)) + + assert linked_list.delete_head() == 0 + assert linked_list.delete_at_nth(9) == 10 + assert linked_list.delete_tail() == 11 + assert len(linked_list) == 9 + assert str(linked_list) == "->".join(str(i) for i in range(1, 10)) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/data_structures/linked_list/doubly_linked_list_two.py b/data_structures/linked_list/doubly_linked_list_two.py new file mode 100644 index 000000000000..184b6966b5a9 --- /dev/null +++ b/data_structures/linked_list/doubly_linked_list_two.py @@ -0,0 +1,253 @@ +""" +- A linked list is similar to an array, it holds values. However, links in a linked + list do not have indexes. +- This is an example of a double ended, doubly linked list. +- Each link references the next link and the previous one. +- A Doubly Linked List (DLL) contains an extra pointer, typically called previous + pointer, together with next pointer and data which are there in singly linked list. + - Advantages over SLL - It can be traversed in both forward and backward direction. + Delete operation is more efficient +""" + + +class Node: + def __init__(self, data: int, previous=None, next_node=None): + self.data = data + self.previous = previous + self.next = next_node + + def __str__(self) -> str: + return f"{self.data}" + + def get_data(self) -> int: + return self.data + + def get_next(self): + return self.next + + def get_previous(self): + return self.previous + + +class LinkedListIterator: + def __init__(self, head): + self.current = head + + def __iter__(self): + return self + + def __next__(self): + if not self.current: + raise StopIteration + else: + value = self.current.get_data() + self.current = self.current.get_next() + return value + + +class LinkedList: + def __init__(self): + self.head = None # First node in list + self.tail = None # Last node in list + + def __str__(self): + current = self.head + nodes = [] + while current is not None: + nodes.append(current.get_data()) + current = current.get_next() + return " ".join(str(node) for node in nodes) + + def __contains__(self, value: int): + current = self.head + while current: + if current.get_data() == value: + return True + current = current.get_next() + return False + + def __iter__(self): + return LinkedListIterator(self.head) + + def get_head_data(self): + if self.head: + return self.head.get_data() + return None + + def get_tail_data(self): + if self.tail: + return self.tail.get_data() + return None + + def set_head(self, node: Node) -> None: + + if self.head is None: + self.head = node + self.tail = node + else: + self.insert_before_node(self.head, node) + + def set_tail(self, node: Node) -> None: + if self.head is None: + self.set_head(node) + else: + self.insert_after_node(self.tail, node) + + def insert(self, value: int) -> None: + node = Node(value) + if self.head is None: + self.set_head(node) + else: + self.set_tail(node) + + def insert_before_node(self, node: Node, node_to_insert: Node) -> None: + node_to_insert.next = node + node_to_insert.previous = node.previous + + if node.get_previous() is None: + self.head = node_to_insert + else: + node.previous.next = node_to_insert + + node.previous = node_to_insert + + def insert_after_node(self, node: Node, node_to_insert: Node) -> None: + node_to_insert.previous = node + node_to_insert.next = node.next + + if node.get_next() is None: + self.tail = node_to_insert + else: + node.next.previous = node_to_insert + + node.next = node_to_insert + + def insert_at_position(self, position: int, value: int) -> None: + current_position = 1 + new_node = Node(value) + node = self.head + while node: + if current_position == position: + self.insert_before_node(node, new_node) + return None + current_position += 1 + node = node.next + self.insert_after_node(self.tail, new_node) + + def get_node(self, item: int) -> Node: + node = self.head + while node: + if node.get_data() == item: + return node + node = node.get_next() + raise Exception("Node not found") + + def delete_value(self, value): + node = self.get_node(value) + + if node is not None: + if node == self.head: + self.head = self.head.get_next() + + if node == self.tail: + self.tail = self.tail.get_previous() + + self.remove_node_pointers(node) + + @staticmethod + def remove_node_pointers(node: Node) -> None: + if node.get_next(): + node.next.previous = node.previous + + if node.get_previous(): + node.previous.next = node.next + + node.next = None + node.previous = None + + def is_empty(self): + return self.head is None + + +def create_linked_list() -> None: + """ + >>> new_linked_list = LinkedList() + >>> new_linked_list.get_head_data() is None + True + >>> new_linked_list.get_tail_data() is None + True + >>> new_linked_list.is_empty() + True + >>> new_linked_list.insert(10) + >>> new_linked_list.get_head_data() + 10 + >>> new_linked_list.get_tail_data() + 10 + >>> new_linked_list.insert_at_position(position=3, value=20) + >>> new_linked_list.get_head_data() + 10 + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.set_head(Node(1000)) + >>> new_linked_list.get_head_data() + 1000 + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.set_tail(Node(2000)) + >>> new_linked_list.get_head_data() + 1000 + >>> new_linked_list.get_tail_data() + 2000 + >>> for value in new_linked_list: + ... print(value) + 1000 + 10 + 20 + 2000 + >>> new_linked_list.is_empty() + False + >>> for value in new_linked_list: + ... print(value) + 1000 + 10 + 20 + 2000 + >>> 10 in new_linked_list + True + >>> new_linked_list.delete_value(value=10) + >>> 10 in new_linked_list + False + >>> new_linked_list.delete_value(value=2000) + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.delete_value(value=1000) + >>> new_linked_list.get_tail_data() + 20 + >>> new_linked_list.get_head_data() + 20 + >>> for value in new_linked_list: + ... print(value) + 20 + >>> new_linked_list.delete_value(value=20) + >>> for value in new_linked_list: + ... print(value) + >>> for value in range(1,10): + ... new_linked_list.insert(value=value) + >>> for value in new_linked_list: + ... print(value) + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + """ + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/data_structures/linked_list/merge_two_lists.py b/data_structures/linked_list/merge_two_lists.py new file mode 100644 index 000000000000..96ec6b8abc85 --- /dev/null +++ b/data_structures/linked_list/merge_two_lists.py @@ -0,0 +1,83 @@ +""" +Algorithm that merges two sorted linked lists into one sorted linked list. +""" +from __future__ import annotations + +from collections.abc import Iterable, Iterator +from dataclasses import dataclass +from typing import Optional + +test_data_odd = (3, 9, -11, 0, 7, 5, 1, -1) +test_data_even = (4, 6, 2, 0, 8, 10, 3, -2) + + +@dataclass +class Node: + data: int + next: Optional[Node] + + +class SortedLinkedList: + def __init__(self, ints: Iterable[int]) -> None: + self.head: Optional[Node] = None + for i in reversed(sorted(ints)): + self.head = Node(i, self.head) + + def __iter__(self) -> Iterator[int]: + """ + >>> tuple(SortedLinkedList(test_data_odd)) == tuple(sorted(test_data_odd)) + True + >>> tuple(SortedLinkedList(test_data_even)) == tuple(sorted(test_data_even)) + True + """ + node = self.head + while node: + yield node.data + node = node.next + + def __len__(self) -> int: + """ + >>> for i in range(3): + ... len(SortedLinkedList(range(i))) == i + True + True + True + >>> len(SortedLinkedList(test_data_odd)) + 8 + """ + return len(tuple(iter(self))) + + def __str__(self) -> str: + """ + >>> str(SortedLinkedList([])) + '' + >>> str(SortedLinkedList(test_data_odd)) + '-11 -> -1 -> 0 -> 1 -> 3 -> 5 -> 7 -> 9' + >>> str(SortedLinkedList(test_data_even)) + '-2 -> 0 -> 2 -> 3 -> 4 -> 6 -> 8 -> 10' + """ + return " -> ".join([str(node) for node in self]) + + +def merge_lists( + sll_one: SortedLinkedList, sll_two: SortedLinkedList +) -> SortedLinkedList: + """ + >>> SSL = SortedLinkedList + >>> merged = merge_lists(SSL(test_data_odd), SSL(test_data_even)) + >>> len(merged) + 16 + >>> str(merged) + '-11 -> -2 -> -1 -> 0 -> 0 -> 1 -> 2 -> 3 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10' + >>> list(merged) == list(sorted(test_data_odd + test_data_even)) + True + """ + return SortedLinkedList(list(sll_one) + list(sll_two)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + SSL = SortedLinkedList + print(merge_lists(SSL(test_data_odd), SSL(test_data_even))) diff --git a/data_structures/queue/linked_queue.py b/data_structures/queue/linked_queue.py index 614c60cd1ae2..8526ad311ed0 100644 --- a/data_structures/queue/linked_queue.py +++ b/data_structures/queue/linked_queue.py @@ -1,18 +1,18 @@ -""" A Queue using a Linked List like structure """ -from typing import Any, Optional +""" A Queue using a linked list like structure """ +from typing import Any class Node: - def __init__(self, data: Any, next: Optional["Node"] = None): - self.data: Any = data - self.next: Optional["Node"] = next + def __init__(self, data: Any) -> None: + self.data = data + self.next = None + + def __str__(self) -> str: + return f"{self.data}" class LinkedQueue: """ - Linked List Queue implementing put (to end of queue), - get (from front of queue) and is_empty - >>> queue = LinkedQueue() >>> queue.is_empty() True @@ -35,40 +35,117 @@ class LinkedQueue: >>> queue.get() Traceback (most recent call last): ... - IndexError: get from empty queue + IndexError: dequeue from empty queue """ def __init__(self) -> None: - self.front: Optional[Node] = None - self.rear: Optional[Node] = None + self.front = self.rear = None + + def __iter__(self): + node = self.front + while node: + yield node.data + node = node.next + + def __len__(self) -> int: + """ + >>> queue = LinkedQueue() + >>> for i in range(1, 6): + ... queue.put(i) + >>> len(queue) + 5 + >>> for i in range(1, 6): + ... assert len(queue) == 6 - i + ... _ = queue.get() + >>> len(queue) + 0 + """ + return len(tuple(iter(self))) + + def __str__(self) -> str: + """ + >>> queue = LinkedQueue() + >>> for i in range(1, 4): + ... queue.put(i) + >>> queue.put("Python") + >>> queue.put(3.14) + >>> queue.put(True) + >>> str(queue) + '1 <- 2 <- 3 <- Python <- 3.14 <- True' + """ + return " <- ".join(str(item) for item in self) def is_empty(self) -> bool: - """ returns boolean describing if queue is empty """ - return self.front is None + """ + >>> queue = LinkedQueue() + >>> queue.is_empty() + True + >>> for i in range(1, 6): + ... queue.put(i) + >>> queue.is_empty() + False + """ + return len(self) == 0 - def put(self, item: Any) -> None: - """ append item to rear of queue """ - node: Node = Node(item) + def put(self, item) -> None: + """ + >>> queue = LinkedQueue() + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: dequeue from empty queue + >>> for i in range(1, 6): + ... queue.put(i) + >>> str(queue) + '1 <- 2 <- 3 <- 4 <- 5' + """ + node = Node(item) if self.is_empty(): - # the queue contains just the single element - self.front = node - self.rear = node + self.front = self.rear = node else: - # not empty, so we add it to the rear of the queue assert isinstance(self.rear, Node) self.rear.next = node self.rear = node def get(self) -> Any: - """ returns and removes item at front of queue """ + """ + >>> queue = LinkedQueue() + >>> queue.get() + Traceback (most recent call last): + ... + IndexError: dequeue from empty queue + >>> queue = LinkedQueue() + >>> for i in range(1, 6): + ... queue.put(i) + >>> for i in range(1, 6): + ... assert queue.get() == i + >>> len(queue) + 0 + """ if self.is_empty(): - raise IndexError("get from empty queue") - else: - # "remove" element by having front point to the next one - assert isinstance(self.front, Node) - node: Node = self.front - self.front = node.next - if self.front is None: - self.rear = None - - return node.data + raise IndexError("dequeue from empty queue") + assert isinstance(self.front, Node) + node = self.front + self.front = self.front.next + if self.front is None: + self.rear = None + return node.data + + def clear(self) -> None: + """ + >>> queue = LinkedQueue() + >>> for i in range(1, 6): + ... queue.put(i) + >>> queue.clear() + >>> len(queue) + 0 + >>> str(queue) + '' + """ + self.front = self.rear = None + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/data_structures/stacks/__init__.py b/data_structures/stacks/__init__.py index f6995cf98977..e69de29bb2d1 100644 --- a/data_structures/stacks/__init__.py +++ b/data_structures/stacks/__init__.py @@ -1,22 +0,0 @@ -class Stack: - def __init__(self): - self.stack = [] - self.top = 0 - - def is_empty(self): - return self.top == 0 - - def push(self, item): - if self.top < len(self.stack): - self.stack[self.top] = item - else: - self.stack.append(item) - - self.top += 1 - - def pop(self): - if self.is_empty(): - return None - else: - self.top -= 1 - return self.stack[self.top] diff --git a/data_structures/stacks/balanced_parentheses.py b/data_structures/stacks/balanced_parentheses.py index 7aacd5969277..674f7ea436ed 100644 --- a/data_structures/stacks/balanced_parentheses.py +++ b/data_structures/stacks/balanced_parentheses.py @@ -1,23 +1,37 @@ from .stack import Stack -__author__ = "Omkar Pathak" - -def balanced_parentheses(parentheses): - """ Use a stack to check if a string of parentheses is balanced.""" - stack = Stack(len(parentheses)) - for parenthesis in parentheses: - if parenthesis == "(": - stack.push(parenthesis) - elif parenthesis == ")": - if stack.is_empty(): +def balanced_parentheses(parentheses: str) -> bool: + """Use a stack to check if a string of parentheses is balanced. + >>> balanced_parentheses("([]{})") + True + >>> balanced_parentheses("[()]{}{[()()]()}") + True + >>> balanced_parentheses("[(])") + False + >>> balanced_parentheses("1+2*3-4") + True + >>> balanced_parentheses("") + True + """ + stack = Stack() + bracket_pairs = {"(": ")", "[": "]", "{": "}"} + for bracket in parentheses: + if bracket in bracket_pairs: + stack.push(bracket) + elif bracket in (")", "]", "}"): + if stack.is_empty() or bracket_pairs[stack.pop()] != bracket: return False - stack.pop() return stack.is_empty() if __name__ == "__main__": + from doctest import testmod + + testmod() + examples = ["((()))", "((())", "(()))"] print("Balanced parentheses demonstration:\n") for example in examples: - print(example + ": " + str(balanced_parentheses(example))) + not_str = "" if balanced_parentheses(example) else "not " + print(f"{example} is {not_str}balanced") diff --git a/data_structures/stacks/evaluate_postfix_notations.py b/data_structures/stacks/evaluate_postfix_notations.py new file mode 100644 index 000000000000..a03cb43bb020 --- /dev/null +++ b/data_structures/stacks/evaluate_postfix_notations.py @@ -0,0 +1,49 @@ +""" +The Reverse Polish Nation also known as Polish postfix notation +or simply postfix notation. +https://en.wikipedia.org/wiki/Reverse_Polish_notation +Classic examples of simple stack implementations +Valid operators are +, -, *, /. +Each operand may be an integer or another expression. +""" + + +def evaluate_postfix(postfix_notation: list) -> int: + """ + >>> evaluate_postfix(["2", "1", "+", "3", "*"]) + 9 + >>> evaluate_postfix(["4", "13", "5", "/", "+"]) + 6 + >>> evaluate_postfix([]) + 0 + """ + if not postfix_notation: + return 0 + + operations = {"+", "-", "*", "/"} + stack = [] + + for token in postfix_notation: + if token in operations: + b, a = stack.pop(), stack.pop() + if token == "+": + stack.append(a + b) + elif token == "-": + stack.append(a - b) + elif token == "*": + stack.append(a * b) + else: + if a * b < 0 and a % b != 0: + stack.append(a // b + 1) + else: + stack.append(a // b) + else: + stack.append(int(token)) + + return stack.pop() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/data_structures/stacks/infix_to_postfix_conversion.py b/data_structures/stacks/infix_to_postfix_conversion.py index 4a1180c9d8e4..dedba8479ac8 100644 --- a/data_structures/stacks/infix_to_postfix_conversion.py +++ b/data_structures/stacks/infix_to_postfix_conversion.py @@ -1,57 +1,67 @@ -import string +""" +https://en.wikipedia.org/wiki/Infix_notation +https://en.wikipedia.org/wiki/Reverse_Polish_notation +https://en.wikipedia.org/wiki/Shunting-yard_algorithm +""" +from .balanced_parentheses import balanced_parentheses from .stack import Stack -__author__ = "Omkar Pathak" - -def is_operand(char): - return char in string.ascii_letters or char in string.digits - - -def precedence(char): - """Return integer value representing an operator's precedence, or +def precedence(char: str) -> int: + """ + Return integer value representing an operator's precedence, or order of operation. - https://en.wikipedia.org/wiki/Order_of_operations """ - dictionary = {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3} - return dictionary.get(char, -1) - + return {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3}.get(char, -1) -def infix_to_postfix(expression): - """Convert infix notation to postfix notation using the Shunting-yard - algorithm. - https://en.wikipedia.org/wiki/Shunting-yard_algorithm - https://en.wikipedia.org/wiki/Infix_notation - https://en.wikipedia.org/wiki/Reverse_Polish_notation +def infix_to_postfix(expression_str: str) -> str: + """ + >>> infix_to_postfix("(1*(2+3)+4))") + Traceback (most recent call last): + ... + ValueError: Mismatched parentheses + >>> infix_to_postfix("") + '' + >>> infix_to_postfix("3+2") + '3 2 +' + >>> infix_to_postfix("(3+4)*5-6") + '3 4 + 5 * 6 -' + >>> infix_to_postfix("(1+2)*3/4-5") + '1 2 + 3 * 4 / 5 -' + >>> infix_to_postfix("a+b*c+(d*e+f)*g") + 'a b c * + d e * f + g * +' + >>> infix_to_postfix("x^y/(5*z)+2") + 'x y ^ 5 z * / 2 +' """ - stack = Stack(len(expression)) + if not balanced_parentheses(expression_str): + raise ValueError("Mismatched parentheses") + stack = Stack() postfix = [] - for char in expression: - if is_operand(char): + for char in expression_str: + if char.isalpha() or char.isdigit(): postfix.append(char) - elif char not in {"(", ")"}: - while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): - postfix.append(stack.pop()) - stack.push(char) elif char == "(": stack.push(char) elif char == ")": while not stack.is_empty() and stack.peek() != "(": postfix.append(stack.pop()) - # Pop '(' from stack. If there is no '(', there is a mismatched - # parentheses. - if stack.peek() != "(": - raise ValueError("Mismatched parentheses") stack.pop() + else: + while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): + postfix.append(stack.pop()) + stack.push(char) while not stack.is_empty(): postfix.append(stack.pop()) return " ".join(postfix) if __name__ == "__main__": + from doctest import testmod + + testmod() expression = "a+b*(c^d-e)^(f+g*h)-i" print("Infix to Postfix Notation demonstration:\n") diff --git a/data_structures/stacks/linked_stack.py b/data_structures/stacks/linked_stack.py index 18ba87ddc221..1a2d07f20e7c 100644 --- a/data_structures/stacks/linked_stack.py +++ b/data_structures/stacks/linked_stack.py @@ -1,11 +1,14 @@ -""" A Stack using a Linked List like structure """ -from typing import Any, Optional +""" A Stack using a linked list like structure """ +from typing import Any class Node: - def __init__(self, data: Any, next: Optional["Node"] = None): - self.data: Any = data - self.next: Optional["Node"] = next + def __init__(self, data): + self.data = data + self.next = None + + def __str__(self): + return f"{self.data}" class LinkedStack: @@ -19,7 +22,7 @@ class LinkedStack: >>> stack.push(5) >>> stack.push(9) >>> stack.push('python') - >>> stack.is_empty(); + >>> stack.is_empty() False >>> stack.pop() 'python' @@ -39,29 +42,116 @@ class LinkedStack: """ def __init__(self) -> None: - self.top: Optional[Node] = None + self.top = None + + def __iter__(self): + node = self.top + while node: + yield node.data + node = node.next + + def __str__(self): + """ + >>> stack = LinkedStack() + >>> stack.push("c") + >>> stack.push("b") + >>> stack.push("a") + >>> str(stack) + 'a->b->c' + """ + return "->".join([str(item) for item in self]) + + def __len__(self): + """ + >>> stack = LinkedStack() + >>> len(stack) == 0 + True + >>> stack.push("c") + >>> stack.push("b") + >>> stack.push("a") + >>> len(stack) == 3 + True + """ + return len(tuple(iter(self))) def is_empty(self) -> bool: - """ returns boolean describing if stack is empty """ + """ + >>> stack = LinkedStack() + >>> stack.is_empty() + True + >>> stack.push(1) + >>> stack.is_empty() + False + """ return self.top is None def push(self, item: Any) -> None: - """ append item to top of stack """ - node: Node = Node(item) - if self.is_empty(): - self.top = node - else: - # each node points to the item "lower" in the stack + """ + >>> stack = LinkedStack() + >>> stack.push("Python") + >>> stack.push("Java") + >>> stack.push("C") + >>> str(stack) + 'C->Java->Python' + """ + node = Node(item) + if not self.is_empty(): node.next = self.top - self.top = node + self.top = node def pop(self) -> Any: - """ returns and removes item at top of stack """ + """ + >>> stack = LinkedStack() + >>> stack.pop() + Traceback (most recent call last): + ... + IndexError: pop from empty stack + >>> stack.push("c") + >>> stack.push("b") + >>> stack.push("a") + >>> stack.pop() == 'a' + True + >>> stack.pop() == 'b' + True + >>> stack.pop() == 'c' + True + """ if self.is_empty(): raise IndexError("pop from empty stack") - else: - # "remove" element by having top point to the next one - assert isinstance(self.top, Node) - node: Node = self.top - self.top = node.next - return node.data + assert isinstance(self.top, Node) + pop_node = self.top + self.top = self.top.next + return pop_node.data + + def peek(self) -> Any: + """ + >>> stack = LinkedStack() + >>> stack.push("Java") + >>> stack.push("C") + >>> stack.push("Python") + >>> stack.peek() + 'Python' + """ + if self.is_empty(): + raise IndexError("peek from empty stack") + return self.top.data + + def clear(self) -> None: + """ + >>> stack = LinkedStack() + >>> stack.push("Java") + >>> stack.push("C") + >>> stack.push("Python") + >>> str(stack) + 'Python->C->Java' + >>> stack.clear() + >>> len(stack) == 0 + True + """ + self.top = None + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/divide_and_conquer/convex_hull.py b/divide_and_conquer/convex_hull.py index cf2c7f835798..9c096f671385 100644 --- a/divide_and_conquer/convex_hull.py +++ b/divide_and_conquer/convex_hull.py @@ -13,6 +13,8 @@ """ +from typing import Iterable, List, Set, Union + class Point: """ @@ -81,7 +83,9 @@ def __hash__(self): return hash(self.x) -def _construct_points(list_of_tuples): +def _construct_points( + list_of_tuples: Union[List[Point], List[List[float]], Iterable[List[float]]] +) -> List[Point]: """ constructs a list of points from an array-like object of numbers @@ -110,20 +114,23 @@ def _construct_points(list_of_tuples): [] """ - points = [] + points: List[Point] = [] if list_of_tuples: for p in list_of_tuples: - try: - points.append(Point(p[0], p[1])) - except (IndexError, TypeError): - print( - f"Ignoring deformed point {p}. All points" - " must have at least 2 coordinates." - ) + if isinstance(p, Point): + points.append(p) + else: + try: + points.append(Point(p[0], p[1])) + except (IndexError, TypeError): + print( + f"Ignoring deformed point {p}. All points" + " must have at least 2 coordinates." + ) return points -def _validate_input(points): +def _validate_input(points: Union[List[Point], List[List[float]]]) -> List[Point]: """ validates an input instance before a convex-hull algorithms uses it @@ -165,33 +172,18 @@ def _validate_input(points): ValueError: Expecting an iterable object but got an non-iterable type 1 """ + if not hasattr(points, "__iter__"): + raise ValueError( + f"Expecting an iterable object but got an non-iterable type {points}" + ) + if not points: raise ValueError(f"Expecting a list of points but got {points}") - if isinstance(points, set): - points = list(points) - - try: - if hasattr(points, "__iter__") and not isinstance(points[0], Point): - if isinstance(points[0], (list, tuple)): - points = _construct_points(points) - else: - raise ValueError( - "Expecting an iterable of type Point, list or tuple. " - f"Found objects of type {type(points[0])} instead" - ) - elif not hasattr(points, "__iter__"): - raise ValueError( - f"Expecting an iterable object but got an non-iterable type {points}" - ) - except TypeError: - print("Expecting an iterable of type Point, list or tuple.") - raise - - return points + return _construct_points(points) -def _det(a, b, c): +def _det(a: Point, b: Point, c: Point) -> float: """ Computes the sign perpendicular distance of a 2d point c from a line segment ab. The sign indicates the direction of c relative to ab. @@ -226,7 +218,7 @@ def _det(a, b, c): return det -def convex_hull_bf(points): +def convex_hull_bf(points: List[Point]) -> List[Point]: """ Constructs the convex hull of a set of 2D points using a brute force algorithm. The algorithm basically considers all combinations of points (i, j) and uses the @@ -299,7 +291,7 @@ def convex_hull_bf(points): return sorted(convex_set) -def convex_hull_recursive(points): +def convex_hull_recursive(points: List[Point]) -> List[Point]: """ Constructs the convex hull of a set of 2D points using a divide-and-conquer strategy The algorithm exploits the geometric properties of the problem by repeatedly @@ -369,7 +361,9 @@ def convex_hull_recursive(points): return sorted(convex_set) -def _construct_hull(points, left, right, convex_set): +def _construct_hull( + points: List[Point], left: Point, right: Point, convex_set: Set[Point] +) -> None: """ Parameters @@ -411,6 +405,77 @@ def _construct_hull(points, left, right, convex_set): _construct_hull(candidate_points, extreme_point, right, convex_set) +def convex_hull_melkman(points: List[Point]) -> List[Point]: + """ + Constructs the convex hull of a set of 2D points using the melkman algorithm. + The algorithm works by iteratively inserting points of a simple polygonal chain + (meaning that no line segments between two consecutive points cross each other). + Sorting the points yields such a polygonal chain. + + For a detailed description, see http://cgm.cs.mcgill.ca/~athens/cs601/Melkman.html + + Runtime: O(n log n) - O(n) if points are already sorted in the input + + Parameters + --------- + points: array-like of object of Points, lists or tuples. + The set of 2d points for which the convex-hull is needed + + Returns + ------ + convex_set: list, the convex-hull of points sorted in non-decreasing order. + + See Also + -------- + + Examples + --------- + >>> convex_hull_melkman([[0, 0], [1, 0], [10, 1]]) + [(0.0, 0.0), (1.0, 0.0), (10.0, 1.0)] + >>> convex_hull_melkman([[0, 0], [1, 0], [10, 0]]) + [(0.0, 0.0), (10.0, 0.0)] + >>> convex_hull_melkman([[-1, 1],[-1, -1], [0, 0], [0.5, 0.5], [1, -1], [1, 1], + ... [-0.75, 1]]) + [(-1.0, -1.0), (-1.0, 1.0), (1.0, -1.0), (1.0, 1.0)] + >>> convex_hull_melkman([(0, 3), (2, 2), (1, 1), (2, 1), (3, 0), (0, 0), (3, 3), + ... (2, -1), (2, -4), (1, -3)]) + [(0.0, 0.0), (0.0, 3.0), (1.0, -3.0), (2.0, -4.0), (3.0, 0.0), (3.0, 3.0)] + """ + points = sorted(_validate_input(points)) + n = len(points) + + convex_hull = points[:2] + for i in range(2, n): + det = _det(convex_hull[1], convex_hull[0], points[i]) + if det > 0: + convex_hull.insert(0, points[i]) + break + elif det < 0: + convex_hull.append(points[i]) + break + else: + convex_hull[1] = points[i] + i += 1 + + for i in range(i, n): + if ( + _det(convex_hull[0], convex_hull[-1], points[i]) > 0 + and _det(convex_hull[-1], convex_hull[0], points[1]) < 0 + ): + # The point lies within the convex hull + continue + + convex_hull.insert(0, points[i]) + convex_hull.append(points[i]) + while _det(convex_hull[0], convex_hull[1], convex_hull[2]) >= 0: + del convex_hull[1] + while _det(convex_hull[-1], convex_hull[-2], convex_hull[-3]) <= 0: + del convex_hull[-2] + + # `convex_hull` is contains the convex hull in circular order + return sorted(convex_hull[1:] if len(convex_hull) > 3 else convex_hull) + + def main(): points = [ (0, 3), @@ -426,10 +491,14 @@ def main(): ] # the convex set of points is # [(0, 0), (0, 3), (1, -3), (2, -4), (3, 0), (3, 3)] - results_recursive = convex_hull_recursive(points) results_bf = convex_hull_bf(points) + + results_recursive = convex_hull_recursive(points) assert results_bf == results_recursive + results_melkman = convex_hull_melkman(points) + assert results_bf == results_melkman + print(results_bf) diff --git a/divide_and_conquer/kth_order_statistic.py b/divide_and_conquer/kth_order_statistic.py new file mode 100644 index 000000000000..f6e81a306bff --- /dev/null +++ b/divide_and_conquer/kth_order_statistic.py @@ -0,0 +1,64 @@ +""" +Find the kth smallest element in linear time using divide and conquer. +Recall we can do this trivially in O(nlogn) time. Sort the list and +access kth element in constant time. + +This is a divide and conquer algorithm that can find a solution in O(n) time. + +For more information of this algorithm: +https://web.stanford.edu/class/archive/cs/cs161/cs161.1138/lectures/08/Small08.pdf +""" +from random import choice +from typing import List + + +def random_pivot(lst): + """ + Choose a random pivot for the list. + We can use a more sophisticated algorithm here, such as the median-of-medians + algorithm. + """ + return choice(lst) + + +def kth_number(lst: List[int], k: int) -> int: + """ + Return the kth smallest number in lst. + >>> kth_number([2, 1, 3, 4, 5], 3) + 3 + >>> kth_number([2, 1, 3, 4, 5], 1) + 1 + >>> kth_number([2, 1, 3, 4, 5], 5) + 5 + >>> kth_number([3, 2, 5, 6, 7, 8], 2) + 3 + >>> kth_number([25, 21, 98, 100, 76, 22, 43, 60, 89, 87], 4) + 43 + """ + # pick a pivot and separate into list based on pivot. + pivot = random_pivot(lst) + + # partition based on pivot + # linear time + small = [e for e in lst if e < pivot] + big = [e for e in lst if e > pivot] + + # if we get lucky, pivot might be the element we want. + # we can easily see this: + # small (elements smaller than k) + # + pivot (kth element) + # + big (elements larger than k) + if len(small) == k - 1: + return pivot + # pivot is in elements bigger than k + elif len(small) < k - 1: + return kth_number(big, k - len(small) - 1) + # pivot is in elements smaller than k + else: + return kth_number(small, k) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/divide_and_conquer/max_difference_pair.py b/divide_and_conquer/max_difference_pair.py new file mode 100644 index 000000000000..ffc4b76a7154 --- /dev/null +++ b/divide_and_conquer/max_difference_pair.py @@ -0,0 +1,44 @@ +def max_difference(a: list[int]) -> tuple[int, int]: + """ + We are given an array A[1..n] of integers, n >= 1. We want to + find a pair of indices (i, j) such that + 1 <= i <= j <= n and A[j] - A[i] is as large as possible. + + Explanation: + https://www.geeksforgeeks.org/maximum-difference-between-two-elements/ + + >>> max_difference([5, 11, 2, 1, 7, 9, 0, 7]) + (1, 9) + """ + # base case + if len(a) == 1: + return a[0], a[0] + else: + # split A into half. + first = a[: len(a) // 2] + second = a[len(a) // 2 :] + + # 2 sub problems, 1/2 of original size. + small1, big1 = max_difference(first) + small2, big2 = max_difference(second) + + # get min of first and max of second + # linear time + min_first = min(first) + max_second = max(second) + + # 3 cases, either (small1, big1), + # (min_first, max_second), (small2, big2) + # constant comparisons + if big2 - small2 > max_second - min_first and big2 - small2 > big1 - small1: + return small2, big2 + elif big1 - small1 > max_second - min_first: + return small1, big1 + else: + return min_first, max_second + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/divide_and_conquer/peak.py b/divide_and_conquer/peak.py new file mode 100644 index 000000000000..f94f83ed3fcb --- /dev/null +++ b/divide_and_conquer/peak.py @@ -0,0 +1,53 @@ +""" +Finding the peak of a unimodal list using divide and conquer. +A unimodal array is defined as follows: array is increasing up to index p, +then decreasing afterwards. (for p >= 1) +An obvious solution can be performed in O(n), +to find the maximum of the array. +(From Kleinberg and Tardos. Algorithm Design. +Addison Wesley 2006: Chapter 5 Solved Exercise 1) +""" +from typing import List + + +def peak(lst: List[int]) -> int: + """ + Return the peak value of `lst`. + >>> peak([1, 2, 3, 4, 5, 4, 3, 2, 1]) + 5 + >>> peak([1, 10, 9, 8, 7, 6, 5, 4]) + 10 + >>> peak([1, 9, 8, 7]) + 9 + >>> peak([1, 2, 3, 4, 5, 6, 7, 0]) + 7 + >>> peak([1, 2, 3, 4, 3, 2, 1, 0, -1, -2]) + 4 + """ + # middle index + m = len(lst) // 2 + + # choose the middle 3 elements + three = lst[m - 1 : m + 2] + + # if middle element is peak + if three[1] > three[0] and three[1] > three[2]: + return three[1] + + # if increasing, recurse on right + elif three[0] < three[2]: + if len(lst[:m]) == 2: + m -= 1 + return peak(lst[m:]) + + # decreasing + else: + if len(lst[:m]) == 2: + m += 1 + return peak(lst[:m]) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 29a174daebf9..ca10e04abcbc 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -121,7 +121,7 @@ def strassen(matrix1: list, matrix2: list) -> list: dimension2 = matrix_dimensions(matrix2) if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: - return matrix1, matrix2 + return [matrix1, matrix2] maximum = max(max(dimension1), max(dimension2)) maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py index 79605261f981..048d57aed1be 100644 --- a/dynamic_programming/climbing_stairs.py +++ b/dynamic_programming/climbing_stairs.py @@ -25,8 +25,9 @@ def climb_stairs(n: int) -> int: ... AssertionError: n needs to be positive integer, your input -7 """ - fmt = "n needs to be positive integer, your input {}" - assert isinstance(n, int) and n > 0, fmt.format(n) + assert ( + isinstance(n, int) and n > 0 + ), f"n needs to be positive integer, your input {n}" if n == 1: return 1 dp = [0] * (n + 1) diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 855af61d6707..21c64dba4ecc 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -37,8 +37,9 @@ def list_of_submasks(mask: int) -> list[int]: """ - fmt = "mask needs to be positive integer, your input {}" - assert isinstance(mask, int) and mask > 0, fmt.format(mask) + assert ( + isinstance(mask, int) and mask > 0 + ), f"mask needs to be positive integer, your input {mask}" """ first submask iterated will be mask itself then operation will be performed diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/minimum_coin_change.py similarity index 77% rename from dynamic_programming/coin_change.py rename to dynamic_programming/minimum_coin_change.py index 2d7106f0cc6f..2869b5857be1 100644 --- a/dynamic_programming/coin_change.py +++ b/dynamic_programming/minimum_coin_change.py @@ -7,20 +7,23 @@ """ -def dp_count(S, m, n): +def dp_count(S, n): """ - >>> dp_count([1, 2, 3], 3, 4) + >>> dp_count([1, 2, 3], 4) 4 - >>> dp_count([1, 2, 3], 3, 7) + >>> dp_count([1, 2, 3], 7) 8 - >>> dp_count([2, 5, 3, 6], 4, 10) + >>> dp_count([2, 5, 3, 6], 10) 5 - >>> dp_count([10], 1, 99) + >>> dp_count([10], 99) 0 - >>> dp_count([4, 5, 6], 3, 0) + >>> dp_count([4, 5, 6], 0) 1 + >>> dp_count([1, 2, 3], -5) + 0 """ - + if n < 0: + return 0 # table[i] represents the number of ways to get to amount i table = [0] * (n + 1) diff --git a/dynamic_programming/minimum_steps_to_one.py b/dynamic_programming/minimum_steps_to_one.py new file mode 100644 index 000000000000..f4eb7033dd20 --- /dev/null +++ b/dynamic_programming/minimum_steps_to_one.py @@ -0,0 +1,65 @@ +""" +YouTube Explanation: https://www.youtube.com/watch?v=f2xi3c1S95M + +Given an integer n, return the minimum steps to 1 + +AVAILABLE STEPS: + * Decrement by 1 + * if n is divisible by 2, divide by 2 + * if n is divisible by 3, divide by 3 + + +Example 1: n = 10 +10 -> 9 -> 3 -> 1 +Result: 3 steps + +Example 2: n = 15 +15 -> 5 -> 4 -> 2 -> 1 +Result: 4 steps + +Example 3: n = 6 +6 -> 2 -> 1 +Result: 2 step +""" + +from __future__ import annotations + +__author__ = "Alexander Joslin" + + +def min_steps_to_one(number: int) -> int: + """ + Minimum steps to 1 implemented using tabulation. + >>> min_steps_to_one(10) + 3 + >>> min_steps_to_one(15) + 4 + >>> min_steps_to_one(6) + 2 + + :param number: + :return int: + """ + + if number <= 0: + raise ValueError(f"n must be greater than 0. Got n = {number}") + + table = [number + 1] * (number + 1) + + # starting position + table[1] = 0 + for i in range(1, number): + table[i + 1] = min(table[i + 1], table[i] + 1) + # check if out of bounds + if i * 2 <= number: + table[i * 2] = min(table[i * 2], table[i] + 1) + # check if out of bounds + if i * 3 <= number: + table[i * 3] = min(table[i * 3], table[i] + 1) + return table[number] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/electronics/electric_power.py b/electronics/electric_power.py new file mode 100644 index 000000000000..e4e685bbd0f0 --- /dev/null +++ b/electronics/electric_power.py @@ -0,0 +1,52 @@ +# https://en.m.wikipedia.org/wiki/Electric_power +from collections import namedtuple +from typing import Tuple + + +def electric_power(voltage: float, current: float, power: float) -> Tuple: + """ + This function can calculate any one of the three (voltage, current, power), + fundamental value of electrical system. + examples are below: + >>> electric_power(voltage=0, current=2, power=5) + result(name='voltage', value=2.5) + >>> electric_power(voltage=2, current=2, power=0) + result(name='power', value=4.0) + >>> electric_power(voltage=-2, current=3, power=0) + result(name='power', value=6.0) + >>> electric_power(voltage=2, current=4, power=2) + Traceback (most recent call last): + File "", line 15, in + ValueError: Only one argument must be 0 + >>> electric_power(voltage=0, current=0, power=2) + Traceback (most recent call last): + File "", line 19, in + ValueError: Only one argument must be 0 + >>> electric_power(voltage=0, current=2, power=-4) + Traceback (most recent call last): + File "", line 23, in >> electric_power(voltage=2.2, current=2.2, power=0) + result(name='power', value=4.84) + """ + result = namedtuple("result", "name value") + if (voltage, current, power).count(0) != 1: + raise ValueError("Only one argument must be 0") + elif power < 0: + raise ValueError( + "Power cannot be negative in any electrical/electronics system" + ) + elif voltage == 0: + return result("voltage", power / current) + elif current == 0: + return result("current", power / voltage) + elif power == 0: + return result("power", float(round(abs(voltage * current), 2))) + else: + raise ValueError("Exactly one argument must be 0") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/electronics/ohms_law.py b/electronics/ohms_law.py new file mode 100644 index 000000000000..41bffa9f87c8 --- /dev/null +++ b/electronics/ohms_law.py @@ -0,0 +1,42 @@ +# https://en.wikipedia.org/wiki/Ohm%27s_law +from typing import Dict + + +def ohms_law(voltage: float, current: float, resistance: float) -> Dict[str, float]: + """ + Apply Ohm's Law, on any two given electrical values, which can be voltage, current, + and resistance, and then in a Python dict return name/value pair of the zero value. + + >>> ohms_law(voltage=10, resistance=5, current=0) + {'current': 2.0} + >>> ohms_law(voltage=0, current=0, resistance=10) + Traceback (most recent call last): + ... + ValueError: One and only one argument must be 0 + >>> ohms_law(voltage=0, current=1, resistance=-2) + Traceback (most recent call last): + ... + ValueError: Resistance cannot be negative + >>> ohms_law(resistance=0, voltage=-10, current=1) + {'resistance': -10.0} + >>> ohms_law(voltage=0, current=-1.5, resistance=2) + {'voltage': -3.0} + """ + if (voltage, current, resistance).count(0) != 1: + raise ValueError("One and only one argument must be 0") + if resistance < 0: + raise ValueError("Resistance cannot be negative") + if voltage == 0: + return {"voltage": float(current * resistance)} + elif current == 0: + return {"current": voltage / resistance} + elif resistance == 0: + return {"resistance": voltage / current} + else: + raise ValueError("Exactly one argument must be 0") + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/file_transfer/receive_file.py b/file_transfer/receive_file.py index cfba6ed88484..37a503036dc2 100644 --- a/file_transfer/receive_file.py +++ b/file_transfer/receive_file.py @@ -13,7 +13,7 @@ print("Receiving data...") while True: data = sock.recv(1024) - print(f"data={data}") + print(f"{data = }") if not data: break out_file.write(data) # Write data to a file diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 5b53471dfb50..1c56e48f47a1 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -13,7 +13,7 @@ def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: conn, addr = sock.accept() # Establish connection with client. print(f"Got connection from {addr}") data = conn.recv(1024) - print(f"Server received {data}") + print(f"Server received: {data = }") with open(filename, "rb") as in_file: data = in_file.read(1024) diff --git a/fractals/koch_snowflake.py b/fractals/koch_snowflake.py new file mode 100644 index 000000000000..07c1835b41ed --- /dev/null +++ b/fractals/koch_snowflake.py @@ -0,0 +1,116 @@ +""" +Description + The Koch snowflake is a fractal curve and one of the earliest fractals to + have been described. The Koch snowflake can be built up iteratively, in a + sequence of stages. The first stage is an equilateral triangle, and each + successive stage is formed by adding outward bends to each side of the + previous stage, making smaller equilateral triangles. + This can be achieved through the following steps for each line: + 1. divide the line segment into three segments of equal length. + 2. draw an equilateral triangle that has the middle segment from step 1 + as its base and points outward. + 3. remove the line segment that is the base of the triangle from step 2. + (description adapted from https://en.wikipedia.org/wiki/Koch_snowflake ) + (for a more detailed explanation and an implementation in the + Processing language, see https://natureofcode.com/book/chapter-8-fractals/ + #84-the-koch-curve-and-the-arraylist-technique ) + +Requirements (pip): + - matplotlib + - numpy +""" + + +from __future__ import annotations + +import matplotlib.pyplot as plt # type: ignore +import numpy + +# initial triangle of Koch snowflake +VECTOR_1 = numpy.array([0, 0]) +VECTOR_2 = numpy.array([0.5, 0.8660254]) +VECTOR_3 = numpy.array([1, 0]) +INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1] + +# uncomment for simple Koch curve instead of Koch snowflake +# INITIAL_VECTORS = [VECTOR_1, VECTOR_3] + + +def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndarray]: + """ + Go through the number of iterations determined by the argument "steps". + Be careful with high values (above 5) since the time to calculate increases + exponentially. + >>> iterate([numpy.array([0, 0]), numpy.array([1, 0])], 1) + [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ +0.28867513]), array([0.66666667, 0. ]), array([1, 0])] + """ + vectors = initial_vectors + for i in range(steps): + vectors = iteration_step(vectors) + return vectors + + +def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]: + """ + Loops through each pair of adjacent vectors. Each line between two adjacent + vectors is divided into 4 segments by adding 3 additional vectors in-between + the original two vectors. The vector in the middle is constructed through a + 60 degree rotation so it is bent outwards. + >>> iteration_step([numpy.array([0, 0]), numpy.array([1, 0])]) + [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ +0.28867513]), array([0.66666667, 0. ]), array([1, 0])] + """ + new_vectors = [] + for i, start_vector in enumerate(vectors[:-1]): + end_vector = vectors[i + 1] + new_vectors.append(start_vector) + difference_vector = end_vector - start_vector + new_vectors.append(start_vector + difference_vector / 3) + new_vectors.append( + start_vector + difference_vector / 3 + rotate(difference_vector / 3, 60) + ) + new_vectors.append(start_vector + difference_vector * 2 / 3) + new_vectors.append(vectors[-1]) + return new_vectors + + +def rotate(vector: numpy.ndarray, angle_in_degrees: float) -> numpy.ndarray: + """ + Standard rotation of a 2D vector with a rotation matrix + (see https://en.wikipedia.org/wiki/Rotation_matrix ) + >>> rotate(numpy.array([1, 0]), 60) + array([0.5 , 0.8660254]) + >>> rotate(numpy.array([1, 0]), 90) + array([6.123234e-17, 1.000000e+00]) + """ + theta = numpy.radians(angle_in_degrees) + c, s = numpy.cos(theta), numpy.sin(theta) + rotation_matrix = numpy.array(((c, -s), (s, c))) + return numpy.dot(rotation_matrix, vector) + + +def plot(vectors: list[numpy.ndarray]) -> None: + """ + Utility function to plot the vectors using matplotlib.pyplot + No doctest was implemented since this function does not have a return value + """ + # avoid stretched display of graph + axes = plt.gca() + axes.set_aspect("equal") + + # matplotlib.pyplot.plot takes a list of all x-coordinates and a list of all + # y-coordinates as inputs, which are constructed from the vector-list using + # zip() + x_coordinates, y_coordinates = zip(*vectors) + plt.plot(x_coordinates, y_coordinates) + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + processed_vectors = iterate(INITIAL_VECTORS, 5) + plot(processed_vectors) diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py new file mode 100644 index 000000000000..de795bb3fc6f --- /dev/null +++ b/fractals/mandelbrot.py @@ -0,0 +1,150 @@ +""" +The Mandelbrot set is the set of complex numbers "c" for which the series +"z_(n+1) = z_n * z_n + c" does not diverge, i.e. remains bounded. Thus, a +complex number "c" is a member of the Mandelbrot set if, when starting with +"z_0 = 0" and applying the iteration repeatedly, the absolute value of +"z_n" remains bounded for all "n > 0". Complex numbers can be written as +"a + b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" +is the imaginary component, usually drawn on the y-axis. Most visualizations +of the Mandelbrot set use a color-coding to indicate after how many steps in +the series the numbers outside the set diverge. Images of the Mandelbrot set +exhibit an elaborate and infinitely complicated boundary that reveals +progressively ever-finer recursive detail at increasing magnifications, making +the boundary of the Mandelbrot set a fractal curve. +(description adapted from https://en.wikipedia.org/wiki/Mandelbrot_set ) +(see also https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set ) +""" + + +import colorsys + +from PIL import Image # type: ignore + + +def get_distance(x: float, y: float, max_step: int) -> float: + """ + Return the relative distance (= step/max_step) after which the complex number + constituted by this x-y-pair diverges. Members of the Mandelbrot set do not + diverge so their distance is 1. + + >>> get_distance(0, 0, 50) + 1.0 + >>> get_distance(0.5, 0.5, 50) + 0.061224489795918366 + >>> get_distance(2, 0, 50) + 0.0 + """ + a = x + b = y + for step in range(max_step): + a_new = a * a - b * b + x + b = 2 * a * b + y + a = a_new + + # divergence happens for all complex number with an absolute value + # greater than 4 + if a * a + b * b > 4: + break + return step / (max_step - 1) + + +def get_black_and_white_rgb(distance: float) -> tuple: + """ + Black&white color-coding that ignores the relative distance. The Mandelbrot + set is black, everything else is white. + + >>> get_black_and_white_rgb(0) + (255, 255, 255) + >>> get_black_and_white_rgb(0.5) + (255, 255, 255) + >>> get_black_and_white_rgb(1) + (0, 0, 0) + """ + if distance == 1: + return (0, 0, 0) + else: + return (255, 255, 255) + + +def get_color_coded_rgb(distance: float) -> tuple: + """ + Color-coding taking the relative distance into account. The Mandelbrot set + is black. + + >>> get_color_coded_rgb(0) + (255, 0, 0) + >>> get_color_coded_rgb(0.5) + (0, 255, 255) + >>> get_color_coded_rgb(1) + (0, 0, 0) + """ + if distance == 1: + return (0, 0, 0) + else: + return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(distance, 1, 1)) + + +def get_image( + image_width: int = 800, + image_height: int = 600, + figure_center_x: float = -0.6, + figure_center_y: float = 0, + figure_width: float = 3.2, + max_step: int = 50, + use_distance_color_coding: bool = True, +) -> Image.Image: + """ + Function to generate the image of the Mandelbrot set. Two types of coordinates + are used: image-coordinates that refer to the pixels and figure-coordinates + that refer to the complex numbers inside and outside the Mandelbrot set. The + figure-coordinates in the arguments of this function determine which section + of the Mandelbrot set is viewed. The main area of the Mandelbrot set is + roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. + + >>> get_image().load()[0,0] + (255, 0, 0) + >>> get_image(use_distance_color_coding = False).load()[0,0] + (255, 255, 255) + """ + img = Image.new("RGB", (image_width, image_height)) + pixels = img.load() + + # loop through the image-coordinates + for image_x in range(image_width): + for image_y in range(image_height): + + # determine the figure-coordinates based on the image-coordinates + figure_height = figure_width / image_width * image_height + figure_x = figure_center_x + (image_x / image_width - 0.5) * figure_width + figure_y = figure_center_y + (image_y / image_height - 0.5) * figure_height + + distance = get_distance(figure_x, figure_y, max_step) + + # color the corresponding pixel based on the selected coloring-function + if use_distance_color_coding: + pixels[image_x, image_y] = get_color_coded_rgb(distance) + else: + pixels[image_x, image_y] = get_black_and_white_rgb(distance) + + return img + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # colored version, full figure + img = get_image() + + # uncomment for colored version, different section, zoomed in + # img = get_image(figure_center_x = -0.6, figure_center_y = -0.4, + # figure_width = 0.8) + + # uncomment for black and white version, full figure + # img = get_image(use_distance_color_coding = False) + + # uncomment to save the image + # img.save("mandelbrot.png") + + img.show() diff --git a/other/sierpinski_triangle.py b/fractals/sierpinski_triangle.py similarity index 100% rename from other/sierpinski_triangle.py rename to fractals/sierpinski_triangle.py diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 295ff47e8cdc..2bb764fdc916 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -2,7 +2,7 @@ # https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm from __future__ import annotations -from scipy.special import comb +from scipy.special import comb # type: ignore class BezierCurve: @@ -78,7 +78,7 @@ def plot_curve(self, step_size: float = 0.01): step_size: defines the step(s) at which to evaluate the Bezier curve. The smaller the step size, the finer the curve produced. """ - from matplotlib import pyplot as plt + from matplotlib import pyplot as plt # type: ignore to_plot_x: list[float] = [] # x coordinates of points to plot to_plot_y: list[float] = [] # y coordinates of points to plot diff --git a/graphs/bfs_shortest_path.py b/graphs/bfs_shortest_path.py index 1655ca64208d..754ba403537e 100644 --- a/graphs/bfs_shortest_path.py +++ b/graphs/bfs_shortest_path.py @@ -1,8 +1,6 @@ """Breadth-first search shortest path implementations. - doctest: python -m doctest -v bfs_shortest_path.py - Manual test: python bfs_shortest_path.py """ @@ -19,22 +17,19 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: """Find shortest path between `start` and `goal` nodes. - Args: graph (dict): node/list of neighboring nodes key/value pairs. start: start node. goal: target node. - Returns: Shortest path between `start` and `goal` nodes as a string of nodes. 'Not found' string if no path found. - Example: >>> bfs_shortest_path(graph, "G", "D") ['G', 'C', 'A', 'B', 'D'] """ # keep track of explored nodes - explored = [] + explored = set() # keep track of all the paths to be checked queue = [[start]] @@ -61,7 +56,7 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: return new_path # mark node as explored - explored.append(node) + explored.add(node) # in case there's no path between the 2 nodes return "So sorry, but a connecting path doesn't exist :(" @@ -69,16 +64,13 @@ def bfs_shortest_path(graph: dict, start, goal) -> str: def bfs_shortest_path_distance(graph: dict, start, target) -> int: """Find shortest path distance between `start` and `target` nodes. - Args: graph: node/list of neighboring nodes key/value pairs. start: node to start search from. target: node to search for. - Returns: Number of edges in shortest path between `start` and `target` nodes. -1 if no path exists. - Example: >>> bfs_shortest_path_distance(graph, "G", "D") 4 @@ -92,7 +84,7 @@ def bfs_shortest_path_distance(graph: dict, start, target) -> int: if start == target: return 0 queue = [start] - visited = [start] + visited = set(start) # Keep tab on distances from `start` node. dist = {start: 0, target: -1} while queue: @@ -103,7 +95,7 @@ def bfs_shortest_path_distance(graph: dict, start, target) -> int: ) for adjacent in graph[node]: if adjacent not in visited: - visited.append(adjacent) + visited.add(adjacent) queue.append(adjacent) dist[adjacent] = dist[node] + 1 return dist[target] diff --git a/graphs/bfs_zero_one_shortest_path.py b/graphs/bfs_zero_one_shortest_path.py new file mode 100644 index 000000000000..a725fae7e48f --- /dev/null +++ b/graphs/bfs_zero_one_shortest_path.py @@ -0,0 +1,138 @@ +from collections import deque +from dataclasses import dataclass +from typing import Iterator, List + +""" +Finding the shortest path in 0-1-graph in O(E + V) which is faster than dijkstra. +0-1-graph is the weighted graph with the weights equal to 0 or 1. +Link: https://codeforces.com/blog/entry/22276 +""" + + +@dataclass +class Edge: + """Weighted directed graph edge.""" + + destination_vertex: int + weight: int + + +class AdjacencyList: + """Graph adjacency list.""" + + def __init__(self, size: int): + self._graph: List[List[Edge]] = [[] for _ in range(size)] + self._size = size + + def __getitem__(self, vertex: int) -> Iterator[Edge]: + """Get all the vertices adjacent to the given one.""" + return iter(self._graph[vertex]) + + @property + def size(self): + return self._size + + def add_edge(self, from_vertex: int, to_vertex: int, weight: int): + """ + >>> g = AdjacencyList(2) + >>> g.add_edge(0, 1, 0) + >>> g.add_edge(1, 0, 1) + >>> list(g[0]) + [Edge(destination_vertex=1, weight=0)] + >>> list(g[1]) + [Edge(destination_vertex=0, weight=1)] + >>> g.add_edge(0, 1, 2) + Traceback (most recent call last): + ... + ValueError: Edge weight must be either 0 or 1. + >>> g.add_edge(0, 2, 1) + Traceback (most recent call last): + ... + ValueError: Vertex indexes must be in [0; size). + """ + if weight not in (0, 1): + raise ValueError("Edge weight must be either 0 or 1.") + + if to_vertex < 0 or to_vertex >= self.size: + raise ValueError("Vertex indexes must be in [0; size).") + + self._graph[from_vertex].append(Edge(to_vertex, weight)) + + def get_shortest_path(self, start_vertex: int, finish_vertex: int) -> int: + """ + Return the shortest distance from start_vertex to finish_vertex in 0-1-graph. + 1 1 1 + 0--------->3 6--------7>------->8 + | ^ ^ ^ |1 + | | | |0 v + 0| |0 1| 9-------->10 + | | | ^ 1 + v | | |0 + 1--------->2<-------4------->5 + 0 1 1 + >>> g = AdjacencyList(11) + >>> g.add_edge(0, 1, 0) + >>> g.add_edge(0, 3, 1) + >>> g.add_edge(1, 2, 0) + >>> g.add_edge(2, 3, 0) + >>> g.add_edge(4, 2, 1) + >>> g.add_edge(4, 5, 1) + >>> g.add_edge(4, 6, 1) + >>> g.add_edge(5, 9, 0) + >>> g.add_edge(6, 7, 1) + >>> g.add_edge(7, 8, 1) + >>> g.add_edge(8, 10, 1) + >>> g.add_edge(9, 7, 0) + >>> g.add_edge(9, 10, 1) + >>> g.add_edge(1, 2, 2) + Traceback (most recent call last): + ... + ValueError: Edge weight must be either 0 or 1. + >>> g.get_shortest_path(0, 3) + 0 + >>> g.get_shortest_path(0, 4) + Traceback (most recent call last): + ... + ValueError: No path from start_vertex to finish_vertex. + >>> g.get_shortest_path(4, 10) + 2 + >>> g.get_shortest_path(4, 8) + 2 + >>> g.get_shortest_path(0, 1) + 0 + >>> g.get_shortest_path(1, 0) + Traceback (most recent call last): + ... + ValueError: No path from start_vertex to finish_vertex. + """ + queue = deque([start_vertex]) + distances = [None for i in range(self.size)] + distances[start_vertex] = 0 + + while queue: + current_vertex = queue.popleft() + current_distance = distances[current_vertex] + + for edge in self[current_vertex]: + new_distance = current_distance + edge.weight + if ( + distances[edge.destination_vertex] is not None + and new_distance >= distances[edge.destination_vertex] + ): + continue + distances[edge.destination_vertex] = new_distance + if edge.weight == 0: + queue.appendleft(edge.destination_vertex) + else: + queue.append(edge.destination_vertex) + + if distances[finish_vertex] is None: + raise ValueError("No path from start_vertex to finish_vertex.") + + return distances[finish_vertex] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/graphs/breadth_first_search.py b/graphs/breadth_first_search.py index e40ec9d1d06d..ee9855bd0c2d 100644 --- a/graphs/breadth_first_search.py +++ b/graphs/breadth_first_search.py @@ -2,24 +2,52 @@ """ Author: OMKAR PATHAK """ +from typing import Set + class Graph: - def __init__(self): + def __init__(self) -> None: self.vertices = {} - def printGraph(self): - """prints adjacency list representation of graaph""" - for i in self.vertices.keys(): + def print_graph(self) -> None: + """ + prints adjacency list representation of graaph + >>> g = Graph() + >>> g.print_graph() + >>> g.add_edge(0, 1) + >>> g.print_graph() + 0 : 1 + """ + for i in self.vertices: print(i, " : ", " -> ".join([str(j) for j in self.vertices[i]])) - def addEdge(self, fromVertex, toVertex): - """adding the edge between two vertices""" - if fromVertex in self.vertices.keys(): - self.vertices[fromVertex].append(toVertex) + def add_edge(self, from_vertex: int, to_vertex: int) -> None: + """ + adding the edge between two vertices + >>> g = Graph() + >>> g.print_graph() + >>> g.add_edge(0, 1) + >>> g.print_graph() + 0 : 1 + """ + if from_vertex in self.vertices: + self.vertices[from_vertex].append(to_vertex) else: - self.vertices[fromVertex] = [toVertex] + self.vertices[from_vertex] = [to_vertex] - def BFS(self, startVertex): + def bfs(self, start_vertex: int) -> Set[int]: + """ + >>> g = Graph() + >>> g.add_edge(0, 1) + >>> g.add_edge(0, 1) + >>> g.add_edge(0, 2) + >>> g.add_edge(1, 2) + >>> g.add_edge(2, 0) + >>> g.add_edge(2, 3) + >>> g.add_edge(3, 3) + >>> sorted(g.bfs(2)) + [0, 1, 2, 3] + """ # initialize set for storing already visited vertices visited = set() @@ -27,8 +55,8 @@ def BFS(self, startVertex): queue = [] # mark the source node as visited and enqueue it - visited.add(startVertex) - queue.append(startVertex) + visited.add(start_vertex) + queue.append(start_vertex) while queue: vertex = queue.pop(0) @@ -42,18 +70,22 @@ def BFS(self, startVertex): if __name__ == "__main__": + from doctest import testmod + + testmod(verbose=True) + g = Graph() - g.addEdge(0, 1) - g.addEdge(0, 2) - g.addEdge(1, 2) - g.addEdge(2, 0) - g.addEdge(2, 3) - g.addEdge(3, 3) - - g.printGraph() + g.add_edge(0, 1) + g.add_edge(0, 2) + g.add_edge(1, 2) + g.add_edge(2, 0) + g.add_edge(2, 3) + g.add_edge(3, 3) + + g.print_graph() # 0 : 1 -> 2 # 1 : 2 # 2 : 0 -> 3 # 3 : 3 - assert sorted(g.BFS(2)) == [0, 1, 2, 3] + assert sorted(g.bfs(2)) == [0, 1, 2, 3] diff --git a/graphs/depth_first_search_2.py b/graphs/depth_first_search_2.py index c932e76293ed..3072d527c1c7 100644 --- a/graphs/depth_first_search_2.py +++ b/graphs/depth_first_search_2.py @@ -8,58 +8,58 @@ def __init__(self): self.vertex = {} # for printing the Graph vertices - def printGraph(self): + def print_graph(self) -> None: print(self.vertex) - for i in self.vertex.keys(): + for i in self.vertex: print(i, " -> ", " -> ".join([str(j) for j in self.vertex[i]])) # for adding the edge between two vertices - def addEdge(self, fromVertex, toVertex): + def add_edge(self, from_vertex: int, to_vertex: int) -> None: # check if vertex is already present, - if fromVertex in self.vertex.keys(): - self.vertex[fromVertex].append(toVertex) + if from_vertex in self.vertex: + self.vertex[from_vertex].append(to_vertex) else: # else make a new vertex - self.vertex[fromVertex] = [toVertex] + self.vertex[from_vertex] = [to_vertex] - def DFS(self): + def dfs(self) -> None: # visited array for storing already visited nodes visited = [False] * len(self.vertex) # call the recursive helper function for i in range(len(self.vertex)): - if visited[i] is False: - self.DFSRec(i, visited) + if not visited[i]: + self.dfs_recursive(i, visited) - def DFSRec(self, startVertex, visited): + def dfs_recursive(self, start_vertex: int, visited: list) -> None: # mark start vertex as visited - visited[startVertex] = True + visited[start_vertex] = True - print(startVertex, end=" ") + print(start_vertex, end=" ") # Recur for all the vertices that are adjacent to this node - for i in self.vertex.keys(): - if visited[i] is False: - self.DFSRec(i, visited) + for i in self.vertex: + if not visited[i]: + self.dfs_recursive(i, visited) if __name__ == "__main__": g = Graph() - g.addEdge(0, 1) - g.addEdge(0, 2) - g.addEdge(1, 2) - g.addEdge(2, 0) - g.addEdge(2, 3) - g.addEdge(3, 3) + g.add_edge(0, 1) + g.add_edge(0, 2) + g.add_edge(1, 2) + g.add_edge(2, 0) + g.add_edge(2, 3) + g.add_edge(3, 3) - g.printGraph() + g.print_graph() print("DFS:") - g.DFS() + g.dfs() # OUTPUT: - # 0  ->  1 -> 2 - # 1  ->  2 - # 2  ->  0 -> 3 - # 3  ->  3 + # 0 -> 1 -> 2 + # 1 -> 2 + # 2 -> 0 -> 3 + # 3 -> 3 # DFS: - #  0 1 2 3 + # 0 1 2 3 diff --git a/graphs/graph_list.py b/graphs/graph_list.py index a20940ab1598..bab6d6893a89 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,44 +1,143 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Author: OMKAR PATHAK +# Author: OMKAR PATHAK, Nwachukwu Chidiebere -# We can use Python's dictionary for constructing the graph. +# Use a Python dictionary to construct the graph. +from pprint import pformat -class AdjacencyList: - def __init__(self): - self.List = {} - def addEdge(self, fromVertex, toVertex): - # check if vertex is already present - if fromVertex in self.List.keys(): - self.List[fromVertex].append(toVertex) - else: - self.List[fromVertex] = [toVertex] +class GraphAdjacencyList: + """ + Adjacency List type Graph Data Structure that accounts for directed and undirected + Graphs. Initialize graph object indicating whether it's directed or undirected. - def printList(self): - for i in self.List: - print((i, "->", " -> ".join([str(j) for j in self.List[i]]))) + Directed graph example: + >>> d_graph = GraphAdjacencyList() + >>> d_graph + {} + >>> d_graph.add_edge(0, 1) + {0: [1], 1: []} + >>> d_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} + >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(d_graph) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(repr(d_graph)) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + + Undirected graph example: + >>> u_graph = GraphAdjacencyList(directed=False) + >>> u_graph.add_edge(0, 1) + {0: [1], 1: [0]} + >>> u_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [0, 2, 4, 5], 2: [1], 4: [1], 5: [1]} + >>> u_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1, 2], 1: [0, 2, 4, 5], 2: [1, 0, 6, 7], 4: [1], 5: [1], 6: [2], 7: [2]} + >>> u_graph.add_edge(4, 5) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(u_graph) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(repr(u_graph)) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + """ + + def __init__(self, directed: bool = True): + """ + Parameters: + directed: (bool) Indicates if graph is directed or undirected. Default is True. + """ + + self.adj_list = {} # dictionary of lists + self.directed = directed + + def add_edge(self, source_vertex: int, destination_vertex: int) -> object: + """ + Connects vertices together. Creates and Edge from source vertex to destination + vertex. + Vertices will be created if not found in graph + """ + + if not self.directed: # For undirected graphs + # if both source vertex and destination vertex are both present in the + # adjacency list, add destination vertex to source vertex list of adjacent + # vertices and add source vertex to destination vertex list of adjacent + # vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex].append(source_vertex) + # if only source vertex is present in adjacency list, add destination vertex + # to source vertex list of adjacent vertices, then create a new vertex with + # destination vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [source_vertex] + # if only destination vertex is present in adjacency list, add source vertex + # to destination vertex list of adjacent vertices, then create a new vertex + # with source vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif destination_vertex in self.adj_list: + self.adj_list[destination_vertex].append(source_vertex) + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and assign a list + # containing the destination vertex as it's first adjacent vertex also + # create a new vertex with destination vertex as key and assign a list + # containing the source vertex as it's first adjacent vertex. + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [source_vertex] + else: # For directed graphs + # if both source vertex and destination vertex are present in adjacency + # list, add destination vertex to source vertex list of adjacent vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + # if only source vertex is present in adjacency list, add destination + # vertex to source vertex list of adjacent vertices and create a new vertex + # with destination vertex as key, which has no adjacent vertex + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [] + # if only destination vertex is present in adjacency list, create a new + # vertex with source vertex as key and assign a list containing destination + # vertex as first adjacent vertex + elif destination_vertex in self.adj_list: + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and a list containing + # destination vertex as it's first adjacent vertex. Then create a new vertex + # with destination vertex as key, which has no adjacent vertex + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [] + + return self + + def __repr__(self) -> str: + return pformat(self.adj_list) if __name__ == "__main__": - al = AdjacencyList() - al.addEdge(0, 1) - al.addEdge(0, 4) - al.addEdge(4, 1) - al.addEdge(4, 3) - al.addEdge(1, 0) - al.addEdge(1, 4) - al.addEdge(1, 3) - al.addEdge(1, 2) - al.addEdge(2, 3) - al.addEdge(3, 4) - - al.printList() - - # OUTPUT: - # 0 -> 1 -> 4 - # 1 -> 0 -> 4 -> 3 -> 2 - # 2 -> 3 - # 3 -> 4 - # 4 -> 1 -> 3 + import doctest + + doctest.testmod() diff --git a/other/markov_chain.py b/graphs/markov_chain.py similarity index 100% rename from other/markov_chain.py rename to graphs/markov_chain.py diff --git a/graphs/minimum_spanning_tree_boruvka.py b/graphs/minimum_spanning_tree_boruvka.py index 3b05f94b5140..32548b2ecb6c 100644 --- a/graphs/minimum_spanning_tree_boruvka.py +++ b/graphs/minimum_spanning_tree_boruvka.py @@ -99,7 +99,7 @@ def build(vertices=None, edges=None): g.add_edge(*edge) return g - class UnionFind(object): + class UnionFind: """ Disjoint set Union and Find for Boruvka's algorithm """ diff --git a/hashes/sha1.py b/hashes/sha1.py index 04ecdd788039..cca38b7c3fdc 100644 --- a/hashes/sha1.py +++ b/hashes/sha1.py @@ -8,7 +8,7 @@ Also contains a Test class to verify that the generated Hash is same as that returned by the hashlib library -SHA1 hash or SHA1 sum of a string is a crytpographic function which means it is easy +SHA1 hash or SHA1 sum of a string is a cryptographic function which means it is easy to calculate forwards but extremely difficult to calculate backwards. What this means is, you can easily calculate the hash of a string, but it is extremely difficult to know the original string if you have its hash. This property is useful to communicate diff --git a/images/Travis_CI_fail_1.png b/images/Travis_CI_fail_1.png deleted file mode 100644 index 451e54e4844a..000000000000 Binary files a/images/Travis_CI_fail_1.png and /dev/null differ diff --git a/images/Travis_CI_fail_2.png b/images/Travis_CI_fail_2.png deleted file mode 100644 index caa406099da1..000000000000 Binary files a/images/Travis_CI_fail_2.png and /dev/null differ diff --git a/knapsack/README.md b/knapsack/README.md new file mode 100644 index 000000000000..6041c1e48eb8 --- /dev/null +++ b/knapsack/README.md @@ -0,0 +1,32 @@ +# A naive recursive implementation of 0-1 Knapsack Problem + +This overview is taken from: + + https://en.wikipedia.org/wiki/Knapsack_problem + +--- + +## Overview + +The knapsack problem is a problem in combinatorial optimization: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than or equal to a given limit and the total value is as large as possible. It derives its name from the problem faced by someone who is constrained by a fixed-size knapsack and must fill it with the most valuable items. The problem often arises in resource allocation where the decision makers have to choose from a set of non-divisible projects or tasks under a fixed budget or time constraint, respectively. + +The knapsack problem has been studied for more than a century, with early works dating as far back as 1897 The name "knapsack problem" dates back to the early works of mathematician Tobias Dantzig (1884–1956), and refers to the commonplace problem of packing the most valuable or useful items without overloading the luggage. + +--- + +## Documentation + +This module uses docstrings to enable the use of Python's in-built `help(...)` function. +For instance, try `help(Vector)`, `help(unitBasisVector)`, and `help(CLASSNAME.METHODNAME)`. + +--- + +## Usage + +Import the module `knapsack.py` from the **.** directory into your project. + +--- + +## Tests + +`.` contains Python unit tests which can be run with `python3 -m unittest -v`. diff --git a/greedy_method/__init__.py b/knapsack/__init__.py similarity index 100% rename from greedy_method/__init__.py rename to knapsack/__init__.py diff --git a/greedy_method/greedy_knapsack.py b/knapsack/greedy_knapsack.py similarity index 100% rename from greedy_method/greedy_knapsack.py rename to knapsack/greedy_knapsack.py diff --git a/knapsack/knapsack.py b/knapsack/knapsack.py new file mode 100644 index 000000000000..756443ea6163 --- /dev/null +++ b/knapsack/knapsack.py @@ -0,0 +1,47 @@ +from typing import List + +""" A naive recursive implementation of 0-1 Knapsack Problem + https://en.wikipedia.org/wiki/Knapsack_problem +""" + + +def knapsack(capacity: int, weights: List[int], values: List[int], counter: int) -> int: + """ + Returns the maximum value that can be put in a knapsack of a capacity cap, + whereby each weight w has a specific value val. + + >>> cap = 50 + >>> val = [60, 100, 120] + >>> w = [10, 20, 30] + >>> c = len(val) + >>> knapsack(cap, w, val, c) + 220 + + The result is 220 cause the values of 100 and 120 got the weight of 50 + which is the limit of the capacity. + """ + + # Base Case + if counter == 0 or capacity == 0: + return 0 + + # If weight of the nth item is more than Knapsack of capacity, + # then this item cannot be included in the optimal solution, + # else return the maximum of two cases: + # (1) nth item included + # (2) not included + if weights[counter - 1] > capacity: + return knapsack(capacity, weights, values, counter - 1) + else: + left_capacity = capacity - weights[counter - 1] + new_value_included = values[counter - 1] + knapsack( + left_capacity, weights, values, counter - 1 + ) + without_new_value = knapsack(capacity, weights, values, counter - 1) + return max(new_value_included, without_new_value) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/images/__init__.py b/knapsack/tests/__init__.py similarity index 100% rename from images/__init__.py rename to knapsack/tests/__init__.py diff --git a/greedy_method/test_knapsack.py b/knapsack/tests/test_greedy_knapsack.py similarity index 98% rename from greedy_method/test_knapsack.py rename to knapsack/tests/test_greedy_knapsack.py index 5e277a92114e..b7b62d5d80b4 100644 --- a/greedy_method/test_knapsack.py +++ b/knapsack/tests/test_greedy_knapsack.py @@ -1,6 +1,6 @@ import unittest -from . import greedy_knapsack as kp +from knapsack import greedy_knapsack as kp class TestClass(unittest.TestCase): diff --git a/knapsack/tests/test_knapsack.py b/knapsack/tests/test_knapsack.py new file mode 100644 index 000000000000..248855fbce53 --- /dev/null +++ b/knapsack/tests/test_knapsack.py @@ -0,0 +1,52 @@ +""" +Created on Fri Oct 16 09:31:07 2020 + +@author: Dr. Tobias Schröder +@license: MIT-license + +This file contains the test-suite for the knapsack problem. +""" +import unittest + +from knapsack import knapsack as k + + +class Test(unittest.TestCase): + def test_base_case(self): + """ + test for the base case + """ + cap = 0 + val = [0] + w = [0] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 0) + + val = [60] + w = [10] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 0) + + def test_easy_case(self): + """ + test for the base case + """ + cap = 3 + val = [1, 2, 3] + w = [3, 2, 1] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 5) + + def test_knapsack(self): + """ + test for the knapsack + """ + cap = 50 + val = [60, 100, 120] + w = [10, 20, 30] + c = len(val) + self.assertEqual(k.knapsack(cap, w, val, c), 220) + + +if __name__ == "__main__": + unittest.main() diff --git a/linear_algebra/src/conjugate_gradient.py b/linear_algebra/src/conjugate_gradient.py new file mode 100644 index 000000000000..1a65b8ccf019 --- /dev/null +++ b/linear_algebra/src/conjugate_gradient.py @@ -0,0 +1,173 @@ +""" +Resources: +- https://en.wikipedia.org/wiki/Conjugate_gradient_method +- https://en.wikipedia.org/wiki/Definite_symmetric_matrix +""" +import numpy as np + + +def _is_matrix_spd(matrix: np.array) -> bool: + """ + Returns True if input matrix is symmetric positive definite. + Returns False otherwise. + + For a matrix to be SPD, all eigenvalues must be positive. + + >>> import numpy as np + >>> matrix = np.array([ + ... [4.12401784, -5.01453636, -0.63865857], + ... [-5.01453636, 12.33347422, -3.40493586], + ... [-0.63865857, -3.40493586, 5.78591885]]) + >>> _is_matrix_spd(matrix) + True + >>> matrix = np.array([ + ... [0.34634879, 1.96165514, 2.18277744], + ... [0.74074469, -1.19648894, -1.34223498], + ... [-0.7687067 , 0.06018373, -1.16315631]]) + >>> _is_matrix_spd(matrix) + False + """ + # Ensure matrix is square. + assert np.shape(matrix)[0] == np.shape(matrix)[1] + + # If matrix not symmetric, exit right away. + if np.allclose(matrix, matrix.T) is False: + return False + + # Get eigenvalues and eignevectors for a symmetric matrix. + eigen_values, _ = np.linalg.eigh(matrix) + + # Check sign of all eigenvalues. + return np.all(eigen_values > 0) + + +def _create_spd_matrix(dimension: np.int64) -> np.array: + """ + Returns a symmetric positive definite matrix given a dimension. + + Input: + dimension gives the square matrix dimension. + + Output: + spd_matrix is an diminesion x dimensions symmetric positive definite (SPD) matrix. + + >>> import numpy as np + >>> dimension = 3 + >>> spd_matrix = _create_spd_matrix(dimension) + >>> _is_matrix_spd(spd_matrix) + True + """ + random_matrix = np.random.randn(dimension, dimension) + spd_matrix = np.dot(random_matrix, random_matrix.T) + assert _is_matrix_spd(spd_matrix) + return spd_matrix + + +def conjugate_gradient( + spd_matrix: np.array, + load_vector: np.array, + max_iterations: int = 1000, + tol: float = 1e-8, +) -> np.array: + """ + Returns solution to the linear system np.dot(spd_matrix, x) = b. + + Input: + spd_matrix is an NxN Symmetric Positive Definite (SPD) matrix. + load_vector is an Nx1 vector. + + Output: + x is an Nx1 vector that is the solution vector. + + >>> import numpy as np + >>> spd_matrix = np.array([ + ... [8.73256573, -5.02034289, -2.68709226], + ... [-5.02034289, 3.78188322, 0.91980451], + ... [-2.68709226, 0.91980451, 1.94746467]]) + >>> b = np.array([ + ... [-5.80872761], + ... [ 3.23807431], + ... [ 1.95381422]]) + >>> conjugate_gradient(spd_matrix, b) + array([[-0.63114139], + [-0.01561498], + [ 0.13979294]]) + """ + # Ensure proper dimensionality. + assert np.shape(spd_matrix)[0] == np.shape(spd_matrix)[1] + assert np.shape(load_vector)[0] == np.shape(spd_matrix)[0] + assert _is_matrix_spd(spd_matrix) + + # Initialize solution guess, residual, search direction. + x0 = np.zeros((np.shape(load_vector)[0], 1)) + r0 = np.copy(load_vector) + p0 = np.copy(r0) + + # Set initial errors in solution guess and residual. + error_residual = 1e9 + error_x_solution = 1e9 + error = 1e9 + + # Set iteration counter to threshold number of iterations. + iterations = 0 + + while error > tol: + + # Save this value so we only calculate the matrix-vector product once. + w = np.dot(spd_matrix, p0) + + # The main algorithm. + + # Update search direction magnitude. + alpha = np.dot(r0.T, r0) / np.dot(p0.T, w) + # Update solution guess. + x = x0 + alpha * p0 + # Calculate new residual. + r = r0 - alpha * w + # Calculate new Krylov subspace scale. + beta = np.dot(r.T, r) / np.dot(r0.T, r0) + # Calculate new A conjuage search direction. + p = r + beta * p0 + + # Calculate errors. + error_residual = np.linalg.norm(r - r0) + error_x_solution = np.linalg.norm(x - x0) + error = np.maximum(error_residual, error_x_solution) + + # Update variables. + x0 = np.copy(x) + r0 = np.copy(r) + p0 = np.copy(p) + + # Update number of iterations. + iterations += 1 + + return x + + +def test_conjugate_gradient() -> None: + """ + >>> test_conjugate_gradient() # self running tests + """ + # Create linear system with SPD matrix and known solution x_true. + dimension = 3 + spd_matrix = _create_spd_matrix(dimension) + x_true = np.random.randn(dimension, 1) + b = np.dot(spd_matrix, x_true) + + # Numpy solution. + x_numpy = np.linalg.solve(spd_matrix, b) + + # Our implementation. + x_conjugate_gradient = conjugate_gradient(spd_matrix, b) + + # Ensure both solutions are close to x_true (and therefore one another). + assert np.linalg.norm(x_numpy - x_true) <= 1e-6 + assert np.linalg.norm(x_conjugate_gradient - x_true) <= 1e-6 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + test_conjugate_gradient() diff --git a/machine_learning/astar.py b/machine_learning/astar.py index 2f5c21a2bd5f..ee3fcff0b7bf 100644 --- a/machine_learning/astar.py +++ b/machine_learning/astar.py @@ -13,7 +13,7 @@ import numpy as np -class Cell(object): +class Cell: """ Class cell represents a cell in the world which have the property position : The position of the represented by tupleof x and y @@ -45,7 +45,7 @@ def showcell(self): print(self.position) -class Gridworld(object): +class Gridworld: """ Gridworld class represents the external world here a grid M*M matrix diff --git a/traversals/__init__.py b/machine_learning/forecasting/__init__.py similarity index 100% rename from traversals/__init__.py rename to machine_learning/forecasting/__init__.py diff --git a/machine_learning/forecasting/ex_data.csv b/machine_learning/forecasting/ex_data.csv new file mode 100644 index 000000000000..1c429e649755 --- /dev/null +++ b/machine_learning/forecasting/ex_data.csv @@ -0,0 +1,114 @@ +total_user,total_events,days +18231,0.0,1 +22621,1.0,2 +15675,0.0,3 +23583,1.0,4 +68351,5.0,5 +34338,3.0,6 +19238,0.0,0 +24192,0.0,1 +70349,0.0,2 +103510,0.0,3 +128355,1.0,4 +148484,6.0,5 +153489,3.0,6 +162667,1.0,0 +311430,3.0,1 +435663,7.0,2 +273526,0.0,3 +628588,2.0,4 +454989,13.0,5 +539040,3.0,6 +52974,1.0,0 +103451,2.0,1 +810020,5.0,2 +580982,3.0,3 +216515,0.0,4 +134694,10.0,5 +93563,1.0,6 +55432,1.0,0 +169634,1.0,1 +254908,4.0,2 +315285,3.0,3 +191764,0.0,4 +514284,7.0,5 +181214,4.0,6 +78459,2.0,0 +161620,3.0,1 +245610,4.0,2 +326722,5.0,3 +214578,0.0,4 +312365,5.0,5 +232454,4.0,6 +178368,1.0,0 +97152,1.0,1 +222813,4.0,2 +285852,4.0,3 +192149,1.0,4 +142241,1.0,5 +173011,2.0,6 +56488,3.0,0 +89572,2.0,1 +356082,2.0,2 +172799,0.0,3 +142300,1.0,4 +78432,2.0,5 +539023,9.0,6 +62389,1.0,0 +70247,1.0,1 +89229,0.0,2 +94583,1.0,3 +102455,0.0,4 +129270,0.0,5 +311409,1.0,6 +1837026,0.0,0 +361824,0.0,1 +111379,2.0,2 +76337,2.0,3 +96747,0.0,4 +92058,0.0,5 +81929,2.0,6 +143423,0.0,0 +82939,0.0,1 +74403,1.0,2 +68234,0.0,3 +94556,1.0,4 +80311,0.0,5 +75283,3.0,6 +77724,0.0,0 +49229,2.0,1 +65708,2.0,2 +273864,1.0,3 +1711281,0.0,4 +1900253,5.0,5 +343071,1.0,6 +1551326,0.0,0 +56636,1.0,1 +272782,2.0,2 +1785678,0.0,3 +241866,0.0,4 +461904,0.0,5 +2191901,2.0,6 +102925,0.0,0 +242778,1.0,1 +298608,0.0,2 +322458,10.0,3 +216027,9.0,4 +916052,12.0,5 +193278,12.0,6 +263207,8.0,0 +672948,10.0,1 +281909,1.0,2 +384562,1.0,3 +1027375,2.0,4 +828905,9.0,5 +624188,22.0,6 +392218,8.0,0 +292581,10.0,1 +299869,12.0,2 +769455,20.0,3 +316443,8.0,4 +1212864,24.0,5 +1397338,28.0,6 +223249,8.0,0 +191264,14.0,1 diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py new file mode 100644 index 000000000000..b11a230129eb --- /dev/null +++ b/machine_learning/forecasting/run.py @@ -0,0 +1,156 @@ +""" +this is code for forecasting +but i modified it and used it for safety checker of data +for ex: you have a online shop and for some reason some data are +missing (the amount of data that u expected are not supposed to be) + then we can use it +*ps : 1. ofc we can use normal statistic method but in this case + the data is quite absurd and only a little^^ + 2. ofc u can use this and modified it for forecasting purpose + for the next 3 months sales or something, + u can just adjust it for ur own purpose +""" + +import numpy as np +import pandas as pd +from sklearn.preprocessing import Normalizer +from sklearn.svm import SVR +from statsmodels.tsa.statespace.sarimax import SARIMAX + + +def linear_regression_prediction( + train_dt: list, train_usr: list, train_mtch: list, test_dt: list, test_mtch: list +) -> float: + """ + First method: linear regression + input : training data (date, total_user, total_event) in list of float + output : list of total user prediction in float + >>> n = linear_regression_prediction([2,3,4,5], [5,3,4,6], [3,1,2,4], [2,1], [2,2]) + >>> abs(n - 5.0) < 1e-6 # Checking precision because of floating point errors + True + """ + x = np.array([[1, item, train_mtch[i]] for i, item in enumerate(train_dt)]) + y = np.array(train_usr) + beta = np.dot(np.dot(np.linalg.inv(np.dot(x.transpose(), x)), x.transpose()), y) + return abs(beta[0] + test_dt[0] * beta[1] + test_mtch[0] + beta[2]) + + +def sarimax_predictor(train_user: list, train_match: list, test_match: list) -> float: + """ + second method: Sarimax + sarimax is a statistic method which using previous input + and learn its pattern to predict future data + input : training data (total_user, with exog data = total_event) in list of float + output : list of total user prediction in float + >>> sarimax_predictor([4,2,6,8], [3,1,2,4], [2]) + 6.6666671111109626 + """ + order = (1, 2, 1) + seasonal_order = (1, 1, 0, 7) + model = SARIMAX( + train_user, exog=train_match, order=order, seasonal_order=seasonal_order + ) + model_fit = model.fit(disp=False, maxiter=600, method="nm") + result = model_fit.predict(1, len(test_match), exog=[test_match]) + return result[0] + + +def support_vector_regressor(x_train: list, x_test: list, train_user: list) -> float: + """ + Third method: Support vector regressor + svr is quite the same with svm(support vector machine) + it uses the same principles as the SVM for classification, + with only a few minor differences and the only different is that + it suits better for regression purpose + input : training data (date, total_user, total_event) in list of float + where x = list of set (date and total event) + output : list of total user prediction in float + >>> support_vector_regressor([[5,2],[1,5],[6,2]], [[3,2]], [2,1,4]) + 1.634932078116079 + """ + regressor = SVR(kernel="rbf", C=1, gamma=0.1, epsilon=0.1) + regressor.fit(x_train, train_user) + y_pred = regressor.predict(x_test) + return y_pred[0] + + +def interquartile_range_checker(train_user: list) -> float: + """ + Optional method: interquatile range + input : list of total user in float + output : low limit of input in float + this method can be used to check whether some data is outlier or not + >>> interquartile_range_checker([1,2,3,4,5,6,7,8,9,10]) + 2.8 + """ + train_user.sort() + q1 = np.percentile(train_user, 25) + q3 = np.percentile(train_user, 75) + iqr = q3 - q1 + low_lim = q1 - (iqr * 0.1) + return low_lim + + +def data_safety_checker(list_vote: list, actual_result: float) -> None: + """ + Used to review all the votes (list result prediction) + and compare it to the actual result. + input : list of predictions + output : print whether it's safe or not + >>> data_safety_checker([2,3,4],5.0) + Today's data is not safe. + """ + safe = 0 + not_safe = 0 + for i in list_vote: + if i > actual_result: + safe = not_safe + 1 + else: + if abs(abs(i) - abs(actual_result)) <= 0.1: + safe = safe + 1 + else: + not_safe = not_safe + 1 + print(f"Today's data is {'not ' if safe <= not_safe else ''}safe.") + + +# data_input_df = pd.read_csv("ex_data.csv", header=None) +data_input = [[18231, 0.0, 1], [22621, 1.0, 2], [15675, 0.0, 3], [23583, 1.0, 4]] +data_input_df = pd.DataFrame(data_input, columns=["total_user", "total_even", "days"]) + +""" +data column = total user in a day, how much online event held in one day, +what day is that(sunday-saturday) +""" + +# start normalization +normalize_df = Normalizer().fit_transform(data_input_df.values) +# split data +total_date = normalize_df[:, 2].tolist() +total_user = normalize_df[:, 0].tolist() +total_match = normalize_df[:, 1].tolist() + +# for svr (input variable = total date and total match) +x = normalize_df[:, [1, 2]].tolist() +x_train = x[: len(x) - 1] +x_test = x[len(x) - 1 :] + +# for linear reression & sarimax +trn_date = total_date[: len(total_date) - 1] +trn_user = total_user[: len(total_user) - 1] +trn_match = total_match[: len(total_match) - 1] + +tst_date = total_date[len(total_date) - 1 :] +tst_user = total_user[len(total_user) - 1 :] +tst_match = total_match[len(total_match) - 1 :] + + +# voting system with forecasting +res_vote = [] +res_vote.append( + linear_regression_prediction(trn_date, trn_user, trn_match, tst_date, tst_match) +) +res_vote.append(sarimax_predictor(trn_user, trn_match, tst_match)) +res_vote.append(support_vector_regressor(x_train, x_test, trn_user)) + +# check the safety of todays'data^^ +data_safety_checker(res_vote, tst_user) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index 130e7f1ad669..c45be8a4c064 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -200,7 +200,7 @@ def kmeans( def ReportGenerator( - df: pd.DataFrame, ClusteringVariables: np.array, FillMissingReport=None + df: pd.DataFrame, ClusteringVariables: np.ndarray, FillMissingReport=None ) -> pd.DataFrame: """ Function generates easy-erading clustering report. It takes 2 arguments as an input: @@ -251,7 +251,7 @@ def ReportGenerator( lambda x: np.mean( np.nan_to_num( sorted(x)[ - round((len(x) * 25 / 100)) : round(len(x) * 75 / 100) + round(len(x) * 25 / 100) : round(len(x) * 75 / 100) ] ) ), diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index e90ea09a58c1..2a90cfe5987a 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -32,7 +32,7 @@ def classifier(train_data, train_target, classes, point, k=5): :train_data: Set of points that are classified into two or more classes :train_target: List of classes in the order of train_data points :classes: Labels of the classes - :point: The data point that needs to be classifed + :point: The data point that needs to be classified >>> X_train = [[0, 0], [1, 0], [0, 1], [0.5, 0.5], [3, 3], [2, 3], [3, 2]] >>> y_train = [0, 0, 0, 0, 1, 1, 1] diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py index 9a9114102ff3..4a621a4244b6 100644 --- a/machine_learning/knn_sklearn.py +++ b/machine_learning/knn_sklearn.py @@ -26,6 +26,6 @@ prediction = knn.predict(X_new) print( - "\nNew array: \n {}" - "\n\nTarget Names Prediction: \n {}".format(X_new, iris["target_names"][prediction]) + f"\nNew array: \n {X_new}\n\nTarget Names Prediction: \n" + f" {iris['target_names'][prediction]}" ) diff --git a/machine_learning/linear_discriminant_analysis.py b/machine_learning/linear_discriminant_analysis.py index 22ee63a5a62b..0d19e970e973 100644 --- a/machine_learning/linear_discriminant_analysis.py +++ b/machine_learning/linear_discriminant_analysis.py @@ -2,6 +2,7 @@ Linear Discriminant Analysis + Assumptions About Data : 1. The input variables has a gaussian distribution. 2. The variance calculated for each input variables by class grouping is the @@ -44,6 +45,7 @@ from math import log from os import name, system from random import gauss, seed +from typing import Callable, TypeVar # Make a training dataset drawn from a gaussian distribution @@ -245,6 +247,40 @@ def accuracy(actual_y: list, predicted_y: list) -> float: return (correct / len(actual_y)) * 100 +num = TypeVar("num") + + +def valid_input( + input_type: Callable[[object], num], # Usually float or int + input_msg: str, + err_msg: str, + condition: Callable[[num], bool] = lambda x: True, + default: str = None, +) -> num: + """ + Ask for user value and validate that it fulfill a condition. + + :input_type: user input expected type of value + :input_msg: message to show user in the screen + :err_msg: message to show in the screen in case of error + :condition: function that represents the condition that user input is valid. + :default: Default value in case the user does not type anything + :return: user's input + """ + while True: + try: + user_input = input_type(input(input_msg).strip() or default) + if condition(user_input): + return user_input + else: + print(f"{user_input}: {err_msg}") + continue + except ValueError: + print( + f"{user_input}: Incorrect input type, expected {input_type.__name__!r}" + ) + + # Main Function def main(): """ This function starts execution phase """ @@ -254,48 +290,26 @@ def main(): print("First of all we should specify the number of classes that") print("we want to generate as training dataset") # Trying to get number of classes - n_classes = 0 - while True: - try: - user_input = int( - input("Enter the number of classes (Data Groupings): ").strip() - ) - if user_input > 0: - n_classes = user_input - break - else: - print( - f"Your entered value is {user_input} , Number of classes " - f"should be positive!" - ) - continue - except ValueError: - print("Your entered value is not numerical!") + n_classes = valid_input( + input_type=int, + condition=lambda x: x > 0, + input_msg="Enter the number of classes (Data Groupings): ", + err_msg="Number of classes should be positive!", + ) print("-" * 100) - std_dev = 1.0 # Default value for standard deviation of dataset # Trying to get the value of standard deviation - while True: - try: - user_sd = float( - input( - "Enter the value of standard deviation" - "(Default value is 1.0 for all classes): " - ).strip() - or "1.0" - ) - if user_sd >= 0.0: - std_dev = user_sd - break - else: - print( - f"Your entered value is {user_sd}, Standard deviation should " - f"not be negative!" - ) - continue - except ValueError: - print("Your entered value is not numerical!") + std_dev = valid_input( + input_type=float, + condition=lambda x: x >= 0, + input_msg=( + "Enter the value of standard deviation" + "(Default value is 1.0 for all classes): " + ), + err_msg="Standard deviation should not be negative!", + default="1.0", + ) print("-" * 100) @@ -303,38 +317,24 @@ def main(): # dataset counts = [] # An empty list to store instance counts of classes in dataset for i in range(n_classes): - while True: - try: - user_count = int( - input(f"Enter The number of instances for class_{i+1}: ") - ) - if user_count > 0: - counts.append(user_count) - break - else: - print( - f"Your entered value is {user_count}, Number of " - "instances should be positive!" - ) - continue - except ValueError: - print("Your entered value is not numerical!") + user_count = valid_input( + input_type=int, + condition=lambda x: x > 0, + input_msg=(f"Enter The number of instances for class_{i+1}: "), + err_msg="Number of instances should be positive!", + ) + counts.append(user_count) print("-" * 100) # An empty list to store values of user-entered means of classes user_means = [] for a in range(n_classes): - while True: - try: - user_mean = float( - input(f"Enter the value of mean for class_{a+1}: ") - ) - if isinstance(user_mean, float): - user_means.append(user_mean) - break - print(f"You entered an invalid value: {user_mean}") - except ValueError: - print("Your entered value is not numerical!") + user_mean = valid_input( + input_type=float, + input_msg=(f"Enter the value of mean for class_{a+1}: "), + err_msg="This is an invalid value.", + ) + user_means.append(user_mean) print("-" * 100) print("Standard deviation: ", std_dev) diff --git a/machine_learning/lstm/lstm_prediction.py b/machine_learning/lstm/lstm_prediction.py_tf similarity index 100% rename from machine_learning/lstm/lstm_prediction.py rename to machine_learning/lstm/lstm_prediction.py_tf diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py new file mode 100644 index 000000000000..af845c9109b1 --- /dev/null +++ b/machine_learning/similarity_search.py @@ -0,0 +1,140 @@ +""" +Similarity Search : https://en.wikipedia.org/wiki/Similarity_search +Similarity search is a search algorithm for finding the nearest vector from +vectors, used in natural language processing. +In this algorithm, it calculates distance with euclidean distance and +returns a list containing two data for each vector: + 1. the nearest vector + 2. distance between the vector and the nearest vector (float) +""" +import math +from typing import List, Union + +import numpy as np + + +def euclidean(input_a: np.ndarray, input_b: np.ndarray) -> float: + """ + Calculates euclidean distance between two data. + :param input_a: ndarray of first vector. + :param input_b: ndarray of second vector. + :return: Euclidean distance of input_a and input_b. By using math.sqrt(), + result will be float. + + >>> euclidean(np.array([0]), np.array([1])) + 1.0 + >>> euclidean(np.array([0, 1]), np.array([1, 1])) + 1.0 + >>> euclidean(np.array([0, 0, 0]), np.array([0, 0, 1])) + 1.0 + """ + return math.sqrt(sum(pow(a - b, 2) for a, b in zip(input_a, input_b))) + + +def similarity_search( + dataset: np.ndarray, value_array: np.ndarray +) -> List[List[Union[List[float], float]]]: + """ + :param dataset: Set containing the vectors. Should be ndarray. + :param value_array: vector/vectors we want to know the nearest vector from dataset. + :return: Result will be a list containing + 1. the nearest vector + 2. distance from the vector + + >>> dataset = np.array([[0], [1], [2]]) + >>> value_array = np.array([[0]]) + >>> similarity_search(dataset, value_array) + [[[0], 0.0]] + + >>> dataset = np.array([[0, 0], [1, 1], [2, 2]]) + >>> value_array = np.array([[0, 1]]) + >>> similarity_search(dataset, value_array) + [[[0, 0], 1.0]] + + >>> dataset = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) + >>> value_array = np.array([[0, 0, 1]]) + >>> similarity_search(dataset, value_array) + [[[0, 0, 0], 1.0]] + + >>> dataset = np.array([[0, 0, 0], [1, 1, 1], [2, 2, 2]]) + >>> value_array = np.array([[0, 0, 0], [0, 0, 1]]) + >>> similarity_search(dataset, value_array) + [[[0, 0, 0], 0.0], [[0, 0, 0], 1.0]] + + These are the errors that might occur: + + 1. If dimensions are different. + For example, dataset has 2d array and value_array has 1d array: + >>> dataset = np.array([[1]]) + >>> value_array = np.array([1]) + >>> similarity_search(dataset, value_array) + Traceback (most recent call last): + ... + ValueError: Wrong input data's dimensions... dataset : 2, value_array : 1 + + 2. If data's shapes are different. + For example, dataset has shape of (3, 2) and value_array has (2, 3). + We are expecting same shapes of two arrays, so it is wrong. + >>> dataset = np.array([[0, 0], [1, 1], [2, 2]]) + >>> value_array = np.array([[0, 0, 0], [0, 0, 1]]) + >>> similarity_search(dataset, value_array) + Traceback (most recent call last): + ... + ValueError: Wrong input data's shape... dataset : 2, value_array : 3 + + 3. If data types are different. + When trying to compare, we are expecting same types so they should be same. + If not, it'll come up with errors. + >>> dataset = np.array([[0, 0], [1, 1], [2, 2]], dtype=np.float32) + >>> value_array = np.array([[0, 0], [0, 1]], dtype=np.int32) + >>> similarity_search(dataset, value_array) # doctest: +NORMALIZE_WHITESPACE + Traceback (most recent call last): + ... + TypeError: Input data have different datatype... + dataset : float32, value_array : int32 + """ + + if dataset.ndim != value_array.ndim: + raise ValueError( + f"Wrong input data's dimensions... dataset : {dataset.ndim}, " + f"value_array : {value_array.ndim}" + ) + + try: + if dataset.shape[1] != value_array.shape[1]: + raise ValueError( + f"Wrong input data's shape... dataset : {dataset.shape[1]}, " + f"value_array : {value_array.shape[1]}" + ) + except IndexError: + if dataset.ndim != value_array.ndim: + raise TypeError("Wrong shape") + + if dataset.dtype != value_array.dtype: + raise TypeError( + f"Input data have different datatype... dataset : {dataset.dtype}, " + f"value_array : {value_array.dtype}" + ) + + answer = [] + + for value in value_array: + dist = euclidean(value, dataset[0]) + vector = dataset[0].tolist() + + for dataset_value in dataset[1:]: + temp_dist = euclidean(value, dataset_value) + + if dist > temp_dist: + dist = temp_dist + vector = dataset_value.tolist() + + answer.append([vector, dist]) + + return answer + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index 9cf7b694c6be..3e8faf39cf07 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -61,7 +61,7 @@ def term_frequency(term: str, document: str) -> int: return len([word for word in tokenize_document if word.lower() == term.lower()]) -def document_frequency(term: str, corpus: str) -> int: +def document_frequency(term: str, corpus: str) -> tuple[int, int]: """ Calculate the number of documents in a corpus that contain a given term diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index 28c9fd7b426f..e455a158e619 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -9,7 +9,7 @@ def n31(a: int) -> tuple[list[int], int]: """ if not isinstance(a, int): - raise TypeError("Must be int, not {}".format(type(a).__name__)) + raise TypeError(f"Must be int, not {type(a).__name__}") if a < 1: raise ValueError(f"Given integer must be greater than 1, not {a}") diff --git a/maths/area.py b/maths/area.py index 393d45faa880..8689f323cc9a 100644 --- a/maths/area.py +++ b/maths/area.py @@ -1,7 +1,7 @@ """ Find the area of various geometric shapes """ -from math import pi +from math import pi, sqrt def surface_area_cube(side_length: float) -> float: @@ -26,7 +26,7 @@ def surface_area_sphere(radius: float) -> float: """ Calculate the Surface Area of a Sphere. Wikipedia reference: https://en.wikipedia.org/wiki/Sphere - :return 4 * pi * r^2 + Formula: 4 * pi * r^2 >>> surface_area_sphere(5) 314.1592653589793 @@ -44,7 +44,7 @@ def surface_area_sphere(radius: float) -> float: def area_rectangle(length: float, width: float) -> float: """ - Calculate the area of a rectangle + Calculate the area of a rectangle. >>> area_rectangle(10, 20) 200 @@ -68,7 +68,7 @@ def area_rectangle(length: float, width: float) -> float: def area_square(side_length: float) -> float: """ - Calculate the area of a square + Calculate the area of a square. >>> area_square(10) 100 @@ -84,7 +84,7 @@ def area_square(side_length: float) -> float: def area_triangle(base: float, height: float) -> float: """ - Calculate the area of a triangle + Calculate the area of a triangle given the base and height. >>> area_triangle(10, 10) 50.0 @@ -106,9 +106,42 @@ def area_triangle(base: float, height: float) -> float: return (base * height) / 2 +def area_triangle_three_sides(side1: float, side2: float, side3: float) -> float: + """ + Calculate area of triangle when the length of 3 sides are known. + + This function uses Heron's formula: https://en.wikipedia.org/wiki/Heron%27s_formula + + >>> area_triangle_three_sides(5, 12, 13) + 30.0 + >>> area_triangle_three_sides(10, 11, 12) + 51.521233486786784 + >>> area_triangle_three_sides(-1, -2, -1) + Traceback (most recent call last): + ... + ValueError: area_triangle_three_sides() only accepts non-negative values + >>> area_triangle_three_sides(1, -2, 1) + Traceback (most recent call last): + ... + ValueError: area_triangle_three_sides() only accepts non-negative values + """ + if side1 < 0 or side2 < 0 or side3 < 0: + raise ValueError("area_triangle_three_sides() only accepts non-negative values") + elif side1 + side2 < side3 or side1 + side3 < side2 or side2 + side3 < side1: + raise ValueError("Given three sides do not form a triangle") + semi_perimeter = (side1 + side2 + side3) / 2 + area = sqrt( + semi_perimeter + * (semi_perimeter - side1) + * (semi_perimeter - side2) + * (semi_perimeter - side3) + ) + return area + + def area_parallelogram(base: float, height: float) -> float: """ - Calculate the area of a parallelogram + Calculate the area of a parallelogram. >>> area_parallelogram(10, 20) 200 @@ -132,7 +165,7 @@ def area_parallelogram(base: float, height: float) -> float: def area_trapezium(base1: float, base2: float, height: float) -> float: """ - Calculate the area of a trapezium + Calculate the area of a trapezium. >>> area_trapezium(10, 20, 30) 450.0 @@ -172,7 +205,7 @@ def area_trapezium(base1: float, base2: float, height: float) -> float: def area_circle(radius: float) -> float: """ - Calculate the area of a circle + Calculate the area of a circle. >>> area_circle(20) 1256.6370614359173 @@ -186,9 +219,35 @@ def area_circle(radius: float) -> float: return pi * radius ** 2 +def area_ellipse(radius_x: float, radius_y: float) -> float: + """ + Calculate the area of a ellipse. + + >>> area_ellipse(10, 10) + 314.1592653589793 + >>> area_ellipse(10, 20) + 628.3185307179587 + >>> area_ellipse(-10, 20) + Traceback (most recent call last): + ... + ValueError: area_ellipse() only accepts non-negative values + >>> area_ellipse(10, -20) + Traceback (most recent call last): + ... + ValueError: area_ellipse() only accepts non-negative values + >>> area_ellipse(-10, -20) + Traceback (most recent call last): + ... + ValueError: area_ellipse() only accepts non-negative values + """ + if radius_x < 0 or radius_y < 0: + raise ValueError("area_ellipse() only accepts non-negative values") + return pi * radius_x * radius_y + + def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: """ - Calculate the area of a rhombus + Calculate the area of a rhombus. >>> area_rhombus(10, 20) 100.0 @@ -210,24 +269,20 @@ def area_rhombus(diagonal_1: float, diagonal_2: float) -> float: return 1 / 2 * diagonal_1 * diagonal_2 -def main(): - print("Areas of various geometric shapes: \n") - print(f"Rectangle: {area_rectangle(10, 20)}") - print(f"Square: {area_square(10)}") - print(f"Triangle: {area_triangle(10, 10)}") - print(f"Parallelogram: {area_parallelogram(10, 20)}") - print(f"Trapezium: {area_trapezium(10, 20, 30)}") - print(f"Circle: {area_circle(20)}") - print("\nSurface Areas of various geometric shapes: \n") - print(f"Cube: {surface_area_cube(20)}") - print(f"Sphere: {surface_area_sphere(20)}") - print(f"Rhombus: {area_rhombus(10, 20)}") - - if __name__ == "__main__": - import doctest doctest.testmod(verbose=True) # verbose so we can see methods missing tests - main() + print("[DEMO] Areas of various geometric shapes: \n") + print(f"Rectangle: {area_rectangle(10, 20) = }") + print(f"Square: {area_square(10) = }") + print(f"Triangle: {area_triangle(10, 10) = }") + print(f"Triangle: {area_triangle_three_sides(5, 12, 13) = }") + print(f"Parallelogram: {area_parallelogram(10, 20) = }") + print(f"Trapezium: {area_trapezium(10, 20, 30) = }") + print(f"Circle: {area_circle(20) = }") + print("\nSurface Areas of various geometric shapes: \n") + print(f"Cube: {surface_area_cube(20) = }") + print(f"Sphere: {surface_area_sphere(20) = }") + print(f"Rhombus: {area_rhombus(10, 20) = }") diff --git a/other/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py similarity index 100% rename from other/binary_exponentiation_2.py rename to maths/binary_exponentiation_2.py diff --git a/other/binary_exponentiation.py b/maths/binary_exponentiation_3.py similarity index 100% rename from other/binary_exponentiation.py rename to maths/binary_exponentiation_3.py diff --git a/maths/ceil.py b/maths/ceil.py index ac86798a357f..97578265c1a9 100644 --- a/maths/ceil.py +++ b/maths/ceil.py @@ -1,3 +1,8 @@ +""" +https://en.wikipedia.org/wiki/Floor_and_ceiling_functions +""" + + def ceil(x) -> int: """ Return the ceiling of x as an Integral. @@ -10,9 +15,7 @@ def ceil(x) -> int: ... in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ - return ( - x if isinstance(x, int) or x - int(x) == 0 else int(x + 1) if x > 0 else int(x) - ) + return int(x) if x - int(x) <= 0 else int(x) + 1 if __name__ == "__main__": diff --git a/maths/combinations.py b/maths/combinations.py index fd98992e6c16..40f4f7a9f850 100644 --- a/maths/combinations.py +++ b/maths/combinations.py @@ -1,19 +1,58 @@ +""" +https://en.wikipedia.org/wiki/Combination +""" from math import factorial -def combinations(n, k): +def combinations(n: int, k: int) -> int: """ + Returns the number of different combinations of k length which can + be made from n values, where n >= k. + + Examples: >>> combinations(10,5) 252 + >>> combinations(6,3) 20 + >>> combinations(20,5) 15504 + + >>> combinations(52, 5) + 2598960 + + >>> combinations(0, 0) + 1 + + >>> combinations(-4, -5) + ... + Traceback (most recent call last): + ValueError: Please enter positive integers for n and k where n >= k """ + + # If either of the conditions are true, the function is being asked + # to calculate a factorial of a negative number, which is not possible + if n < k or k < 0: + raise ValueError("Please enter positive integers for n and k where n >= k") return int(factorial(n) / ((factorial(k)) * (factorial(n - k)))) if __name__ == "__main__": - from doctest import testmod - testmod() + print( + "\nThe number of five-card hands possible from a standard", + f"fifty-two card deck is: {combinations(52, 5)}", + ) + + print( + "\nIf a class of 40 students must be arranged into groups of", + f"4 for group projects, there are {combinations(40, 4)} ways", + "to arrange them.\n", + ) + + print( + "If 10 teams are competing in a Formula One race, there", + f"are {combinations(10, 3)} ways that first, second and", + "third place can be awarded.\n", + ) diff --git a/maths/decimal_isolate.py b/maths/decimal_isolate.py new file mode 100644 index 000000000000..0e3967a4671d --- /dev/null +++ b/maths/decimal_isolate.py @@ -0,0 +1,45 @@ +""" +Isolate the Decimal part of a Number +https://stackoverflow.com/questions/3886402/how-to-get-numbers-after-decimal-point +""" + + +def decimal_isolate(number, digitAmount): + + """ + Isolates the decimal part of a number. + If digitAmount > 0 round to that decimal place, else print the entire decimal. + >>> decimal_isolate(1.53, 0) + 0.53 + >>> decimal_isolate(35.345, 1) + 0.3 + >>> decimal_isolate(35.345, 2) + 0.34 + >>> decimal_isolate(35.345, 3) + 0.345 + >>> decimal_isolate(-14.789, 3) + -0.789 + >>> decimal_isolate(0, 2) + 0 + >>> decimal_isolate(-14.123, 1) + -0.1 + >>> decimal_isolate(-14.123, 2) + -0.12 + >>> decimal_isolate(-14.123, 3) + -0.123 + """ + if digitAmount > 0: + return round(number - int(number), digitAmount) + return number - int(number) + + +if __name__ == "__main__": + print(decimal_isolate(1.53, 0)) + print(decimal_isolate(35.345, 1)) + print(decimal_isolate(35.345, 2)) + print(decimal_isolate(35.345, 3)) + print(decimal_isolate(-14.789, 3)) + print(decimal_isolate(0, 2)) + print(decimal_isolate(-14.123, 1)) + print(decimal_isolate(-14.123, 2)) + print(decimal_isolate(-14.123, 3)) diff --git a/maths/entropy.py b/maths/entropy.py index 74980ef9e0c6..43bb3860fc12 100644 --- a/maths/entropy.py +++ b/maths/entropy.py @@ -68,7 +68,7 @@ def calculate_prob(text: str) -> None: my_fir_sum += prob * math.log2(prob) # entropy formula. # print entropy - print("{0:.1f}".format(round(-1 * my_fir_sum))) + print("{:.1f}".format(round(-1 * my_fir_sum))) # two len string all_sum = sum(two_char_strings.values()) @@ -83,10 +83,10 @@ def calculate_prob(text: str) -> None: my_sec_sum += prob * math.log2(prob) # print second entropy - print("{0:.1f}".format(round(-1 * my_sec_sum))) + print("{:.1f}".format(round(-1 * my_sec_sum))) # print the difference between them - print("{0:.1f}".format(round(((-1 * my_sec_sum) - (-1 * my_fir_sum))))) + print("{:.1f}".format(round((-1 * my_sec_sum) - (-1 * my_fir_sum)))) def analyze_text(text: str) -> tuple[dict, dict]: diff --git a/maths/euclidean_distance.py b/maths/euclidean_distance.py new file mode 100644 index 000000000000..6e0da6370219 --- /dev/null +++ b/maths/euclidean_distance.py @@ -0,0 +1,62 @@ +from typing import Iterable, Union + +import numpy as np + +Vector = Union[Iterable[float], Iterable[int], np.ndarray] +VectorOut = Union[np.float64, int, float] + + +def euclidean_distance(vector_1: Vector, vector_2: Vector) -> VectorOut: + """ + Calculate the distance between the two endpoints of two vectors. + A vector is defined as a list, tuple, or numpy 1D array. + >>> euclidean_distance((0, 0), (2, 2)) + 2.8284271247461903 + >>> euclidean_distance(np.array([0, 0, 0]), np.array([2, 2, 2])) + 3.4641016151377544 + >>> euclidean_distance(np.array([1, 2, 3, 4]), np.array([5, 6, 7, 8])) + 8.0 + >>> euclidean_distance([1, 2, 3, 4], [5, 6, 7, 8]) + 8.0 + """ + return np.sqrt(np.sum((np.asarray(vector_1) - np.asarray(vector_2)) ** 2)) + + +def euclidean_distance_no_np(vector_1: Vector, vector_2: Vector) -> VectorOut: + """ + Calculate the distance between the two endpoints of two vectors without numpy. + A vector is defined as a list, tuple, or numpy 1D array. + >>> euclidean_distance_no_np((0, 0), (2, 2)) + 2.8284271247461903 + >>> euclidean_distance_no_np([1, 2, 3, 4], [5, 6, 7, 8]) + 8.0 + """ + return sum((v1 - v2) ** 2 for v1, v2 in zip(vector_1, vector_2)) ** (1 / 2) + + +if __name__ == "__main__": + + def benchmark() -> None: + """ + Benchmarks + """ + from timeit import timeit + + print("Without Numpy") + print( + timeit( + "euclidean_distance_no_np([1, 2, 3], [4, 5, 6])", + number=10000, + globals=globals(), + ) + ) + print("With Numpy") + print( + timeit( + "euclidean_distance([1, 2, 3], [4, 5, 6])", + number=10000, + globals=globals(), + ) + ) + + benchmark() diff --git a/other/euclidean_gcd.py b/maths/euclidean_gcd.py similarity index 100% rename from other/euclidean_gcd.py rename to maths/euclidean_gcd.py diff --git a/maths/explicit_euler.py b/maths/euler_method.py similarity index 100% rename from maths/explicit_euler.py rename to maths/euler_method.py diff --git a/maths/floor.py b/maths/floor.py index 41bd5ecb3cd7..482250f5e59e 100644 --- a/maths/floor.py +++ b/maths/floor.py @@ -1,18 +1,19 @@ +""" +https://en.wikipedia.org/wiki/Floor_and_ceiling_functions +""" + + def floor(x) -> int: """ Return the floor of x as an Integral. - :param x: the number :return: the largest integer <= x. - >>> import math >>> all(floor(n) == math.floor(n) for n ... in (1, -1, 0, -0, 1.1, -1.1, 1.0, -1.0, 1_000_000_000)) True """ - return ( - x if isinstance(x, int) or x - int(x) == 0 else int(x) if x > 0 else int(x - 1) - ) + return int(x) if x - int(x) >= 0 else int(x) - 1 if __name__ == "__main__": diff --git a/maths/greatest_common_divisor.py b/maths/greatest_common_divisor.py index 0926ade5dec2..a2174a8eb74a 100644 --- a/maths/greatest_common_divisor.py +++ b/maths/greatest_common_divisor.py @@ -2,10 +2,12 @@ Greatest Common Divisor. Wikipedia reference: https://en.wikipedia.org/wiki/Greatest_common_divisor + +gcd(a, b) = gcd(a, -b) = gcd(-a, b) = gcd(-a, -b) by definition of divisibility """ -def greatest_common_divisor(a, b): +def greatest_common_divisor(a: int, b: int) -> int: """ Calculate Greatest Common Divisor (GCD). >>> greatest_common_divisor(24, 40) @@ -20,31 +22,44 @@ def greatest_common_divisor(a, b): 1 >>> greatest_common_divisor(16, 4) 4 + >>> greatest_common_divisor(-3, 9) + 3 + >>> greatest_common_divisor(9, -3) + 3 + >>> greatest_common_divisor(3, -9) + 3 + >>> greatest_common_divisor(-3, -9) + 3 """ - return b if a == 0 else greatest_common_divisor(b % a, a) - - -""" -Below method is more memory efficient because it does not use the stack (chunk of -memory). While above method is good, uses more memory for huge numbers because of the -recursive calls required to calculate the greatest common divisor. -""" + return abs(b) if a == 0 else greatest_common_divisor(b % a, a) -def gcd_by_iterative(x, y): +def gcd_by_iterative(x: int, y: int) -> int: """ + Below method is more memory efficient because it does not create additional + stack frames for recursive functions calls (as done in the above method). >>> gcd_by_iterative(24, 40) 8 >>> greatest_common_divisor(24, 40) == gcd_by_iterative(24, 40) True + >>> gcd_by_iterative(-3, -9) + 3 + >>> gcd_by_iterative(3, -9) + 3 + >>> gcd_by_iterative(1, -800) + 1 + >>> gcd_by_iterative(11, 37) + 1 """ while y: # --> when y=0 then loop will terminate and return x as final GCD. x, y = y, x % y - return x + return abs(x) def main(): - """Call Greatest Common Divisor function.""" + """ + Call Greatest Common Divisor function. + """ try: nums = input("Enter two integers separated by comma (,): ").split(",") num_1 = int(nums[0]) diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py new file mode 100644 index 000000000000..5a7d9e8d84ae --- /dev/null +++ b/maths/greedy_coin_change.py @@ -0,0 +1,102 @@ +""" +Test cases: +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make in Indian Currency: 987 +Following is minimal change for 987 : +500 100 100 100 100 50 20 10 5 2 + +Do you want to enter your denominations ? (Y/N) :Y +Enter number of denomination:10 +1 +5 +10 +20 +50 +100 +200 +500 +1000 +2000 +Enter the change you want to make: 18745 +Following is minimal change for 18745 : +2000 2000 2000 2000 2000 2000 2000 2000 2000 500 200 20 20 5 + +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make: 0 +The total value cannot be zero or negative. +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make: -98 +The total value cannot be zero or negative. + +Do you want to enter your denominations ? (Y/N) :Y +Enter number of denomination:5 +1 +5 +100 +500 +1000 +Enter the change you want to make: 456 +Following is minimal change for 456 : +100 100 100 100 5 5 5 5 5 5 5 5 5 5 5 1 +""" + + +def find_minimum_change(denominations: list[int], value: int) -> list[int]: + """ + Find the minimum change from the given denominations and value + >>> find_minimum_change([1, 5, 10, 20, 50, 100, 200, 500, 1000,2000], 18745) + [2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 500, 200, 20, 20, 5] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], 987) + [500, 100, 100, 100, 100, 50, 20, 10, 5, 2] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], 0) + [] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], -98) + [] + >>> find_minimum_change([1, 5, 100, 500, 1000], 456) + [100, 100, 100, 100, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1] + """ + total_value = int(value) + + # Initialize Result + answer = [] + + # Traverse through all denomination + for denomination in reversed(denominations): + + # Find denominations + while int(total_value) >= int(denomination): + total_value -= int(denomination) + answer.append(denomination) # Append the "answers" array + + return answer + + +# Driver Code +if __name__ == "__main__": + + denominations = list() + value = 0 + + if ( + input("Do you want to enter your denominations ? (yY/n): ").strip().lower() + == "y" + ): + n = int(input("Enter the number of denominations you want to add: ").strip()) + + for i in range(0, n): + denominations.append(int(input(f"Denomination {i}: ").strip())) + value = input("Enter the change you want to make in Indian Currency: ").strip() + else: + # All denominations of Indian Currency if user does not enter + denominations = [1, 2, 5, 10, 20, 50, 100, 500, 2000] + value = input("Enter the change you want to make: ").strip() + + if int(value) == 0 or int(value) < 0: + print("The total value cannot be zero or negative.") + + else: + print(f"Following is minimal change for {value}: ") + answer = find_minimum_change(denominations, value) + # Print result + for i in range(len(answer)): + print(answer[i], end=" ") diff --git a/other/integeration_by_simpson_approx.py b/maths/integration_by_simpson_approx.py similarity index 100% rename from other/integeration_by_simpson_approx.py rename to maths/integration_by_simpson_approx.py diff --git a/other/largest_subarray_sum.py b/maths/largest_subarray_sum.py similarity index 100% rename from other/largest_subarray_sum.py rename to maths/largest_subarray_sum.py diff --git a/maths/lucas_series.py b/maths/lucas_series.py index 22ad893a6567..6b32c2022e13 100644 --- a/maths/lucas_series.py +++ b/maths/lucas_series.py @@ -1,22 +1,66 @@ -# Lucas Sequence Using Recursion +""" +https://en.wikipedia.org/wiki/Lucas_number +""" -def recur_luc(n): +def recursive_lucas_number(n_th_number: int) -> int: """ - >>> recur_luc(1) + Returns the nth lucas number + >>> recursive_lucas_number(1) 1 - >>> recur_luc(0) + >>> recursive_lucas_number(20) + 15127 + >>> recursive_lucas_number(0) 2 + >>> recursive_lucas_number(25) + 167761 + >>> recursive_lucas_number(-1.5) + Traceback (most recent call last): + ... + TypeError: recursive_lucas_number accepts only integer arguments. """ - if n == 1: - return n - if n == 0: + if not isinstance(n_th_number, int): + raise TypeError("recursive_lucas_number accepts only integer arguments.") + if n_th_number == 0: return 2 - return recur_luc(n - 1) + recur_luc(n - 2) + if n_th_number == 1: + return 1 + + return recursive_lucas_number(n_th_number - 1) + recursive_lucas_number( + n_th_number - 2 + ) + + +def dynamic_lucas_number(n_th_number: int) -> int: + """ + Returns the nth lucas number + >>> dynamic_lucas_number(1) + 1 + >>> dynamic_lucas_number(20) + 15127 + >>> dynamic_lucas_number(0) + 2 + >>> dynamic_lucas_number(25) + 167761 + >>> dynamic_lucas_number(-1.5) + Traceback (most recent call last): + ... + TypeError: dynamic_lucas_number accepts only integer arguments. + """ + if not isinstance(n_th_number, int): + raise TypeError("dynamic_lucas_number accepts only integer arguments.") + a, b = 2, 1 + for i in range(n_th_number): + a, b = b, a + b + return a if __name__ == "__main__": - limit = int(input("How many terms to include in Lucas series:")) - print("Lucas series:") - for i in range(limit): - print(recur_luc(i)) + from doctest import testmod + + testmod() + n = int(input("Enter the number of terms in lucas series:\n").strip()) + print("Using recursive function to calculate lucas series:") + print(" ".join(str(recursive_lucas_number(i)) for i in range(n))) + print("\nUsing dynamic function to calculate lucas series:") + print(" ".join(str(dynamic_lucas_number(i)) for i in range(n))) diff --git a/other/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py similarity index 96% rename from other/max_sum_sliding_window.py rename to maths/max_sum_sliding_window.py index 4be7d786f215..593cb5c8bd67 100644 --- a/other/max_sum_sliding_window.py +++ b/maths/max_sum_sliding_window.py @@ -1,45 +1,45 @@ -""" -Given an array of integer elements and an integer 'k', we are required to find the -maximum sum of 'k' consecutive elements in the array. - -Instead of using a nested for loop, in a Brute force approach we will use a technique -called 'Window sliding technique' where the nested loops can be converted to a single -loop to reduce time complexity. -""" -from typing import List - - -def max_sum_in_array(array: List[int], k: int) -> int: - """ - Returns the maximum sum of k consecutive elements - >>> arr = [1, 4, 2, 10, 2, 3, 1, 0, 20] - >>> k = 4 - >>> max_sum_in_array(arr, k) - 24 - >>> k = 10 - >>> max_sum_in_array(arr,k) - Traceback (most recent call last): - ... - ValueError: Invalid Input - >>> arr = [1, 4, 2, 10, 2, 13, 1, 0, 2] - >>> k = 4 - >>> max_sum_in_array(arr, k) - 27 - """ - if len(array) < k or k < 0: - raise ValueError("Invalid Input") - max_sum = current_sum = sum(array[:k]) - for i in range(len(array) - k): - current_sum = current_sum - array[i] + array[i + k] - max_sum = max(max_sum, current_sum) - return max_sum - - -if __name__ == "__main__": - from doctest import testmod - from random import randint - - testmod() - array = [randint(-1000, 1000) for i in range(100)] - k = randint(0, 110) - print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}") +""" +Given an array of integer elements and an integer 'k', we are required to find the +maximum sum of 'k' consecutive elements in the array. + +Instead of using a nested for loop, in a Brute force approach we will use a technique +called 'Window sliding technique' where the nested loops can be converted to a single +loop to reduce time complexity. +""" +from typing import List + + +def max_sum_in_array(array: List[int], k: int) -> int: + """ + Returns the maximum sum of k consecutive elements + >>> arr = [1, 4, 2, 10, 2, 3, 1, 0, 20] + >>> k = 4 + >>> max_sum_in_array(arr, k) + 24 + >>> k = 10 + >>> max_sum_in_array(arr,k) + Traceback (most recent call last): + ... + ValueError: Invalid Input + >>> arr = [1, 4, 2, 10, 2, 13, 1, 0, 2] + >>> k = 4 + >>> max_sum_in_array(arr, k) + 27 + """ + if len(array) < k or k < 0: + raise ValueError("Invalid Input") + max_sum = current_sum = sum(array[:k]) + for i in range(len(array) - k): + current_sum = current_sum - array[i] + array[i + k] + max_sum = max(max_sum, current_sum) + return max_sum + + +if __name__ == "__main__": + from doctest import testmod + from random import randint + + testmod() + array = [randint(-1000, 1000) for i in range(100)] + k = randint(0, 110) + print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}") diff --git a/maths/median_of_two_arrays.py b/maths/median_of_two_arrays.py new file mode 100644 index 000000000000..cde12f5d7e3b --- /dev/null +++ b/maths/median_of_two_arrays.py @@ -0,0 +1,33 @@ +from typing import List + + +def median_of_two_arrays(nums1: List[float], nums2: List[float]) -> float: + """ + >>> median_of_two_arrays([1, 2], [3]) + 2 + >>> median_of_two_arrays([0, -1.1], [2.5, 1]) + 0.5 + >>> median_of_two_arrays([], [2.5, 1]) + 1.75 + >>> median_of_two_arrays([], [0]) + 0 + >>> median_of_two_arrays([], []) + Traceback (most recent call last): + ... + IndexError: list index out of range + """ + all_numbers = sorted(nums1 + nums2) + div, mod = divmod(len(all_numbers), 2) + if mod == 1: + return all_numbers[div] + else: + return (all_numbers[div] + all_numbers[div - 1]) / 2 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + array_1 = [float(x) for x in input("Enter the elements of first array: ").split()] + array_2 = [float(x) for x in input("Enter the elements of second array: ").split()] + print(f"The median of two arrays is: {median_of_two_arrays(array_1, array_2)}") diff --git a/maths/numerical_integration.py b/maths/numerical_integration.py index 67fbc0ddbf30..87184a76b740 100644 --- a/maths/numerical_integration.py +++ b/maths/numerical_integration.py @@ -62,5 +62,5 @@ def f(x): i = 10 while i <= 100000: area = trapezoidal_area(f, -5, 5, i) - print("with {} steps: {}".format(i, area)) + print(f"with {i} steps: {area}") i *= 10 diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index e929a2d02972..68ff97ddd25d 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -5,7 +5,7 @@ def evaluate_poly(poly: Sequence[float], x: float) -> float: """Evaluate a polynomial f(x) at specified point x and return the value. Arguments: - poly -- the coeffiecients of a polynomial as an iterable in order of + poly -- the coefficients of a polynomial as an iterable in order of ascending degree x -- the point at which to evaluate the polynomial @@ -26,7 +26,7 @@ def horner(poly: Sequence[float], x: float) -> float: https://en.wikipedia.org/wiki/Horner's_method Arguments: - poly -- the coeffiecients of a polynomial as an iterable in order of + poly -- the coefficients of a polynomial as an iterable in order of ascending degree x -- the point at which to evaluate the polynomial diff --git a/maths/prime_check.py b/maths/prime_check.py index ed8fbbae809a..e2bcb7b8f151 100644 --- a/maths/prime_check.py +++ b/maths/prime_check.py @@ -5,25 +5,20 @@ def prime_check(number: int) -> bool: - """ - Check to See if a Number is Prime. + """Checks to see if a number is a prime. - A number is prime if it has exactly two dividers: 1 and itself. + A number is prime if it has exactly two factors: 1 and itself. """ - if number < 2: - # Negatives, 0 and 1 are not primes - return False - if number < 4: + + if 1 < number < 4: # 2 and 3 are primes return True - if number % 2 == 0: - # Even values are not primes + elif number < 2 or not number % 2: + # Negatives, 0, 1 and all even numbers are not primes return False - # Except 2, all primes are odd. If any odd value divide - # the number, then that number is not prime. - odd_numbers = range(3, int(math.sqrt(number)) + 1, 2) - return not any(number % i == 0 for i in odd_numbers) + odd_numbers = range(3, int(math.sqrt(number) + 1), 2) + return not any(not number % i for i in odd_numbers) class Test(unittest.TestCase): @@ -40,12 +35,17 @@ def test_primes(self): self.assertTrue(prime_check(29)) def test_not_primes(self): - self.assertFalse(prime_check(-19), "Negative numbers are not prime.") self.assertFalse( - prime_check(0), "Zero doesn't have any divider, primes must have two." + prime_check(-19), + "Negative numbers are excluded by definition of prime numbers.", + ) + self.assertFalse( + prime_check(0), + "Zero doesn't have any positive factors, primes must have exactly two.", ) self.assertFalse( - prime_check(1), "One just have 1 divider, primes must have two." + prime_check(1), + "One only has 1 positive factor, primes must have exactly two.", ) self.assertFalse(prime_check(2 * 2)) self.assertFalse(prime_check(2 * 3)) diff --git a/other/primelib.py b/maths/primelib.py similarity index 100% rename from other/primelib.py rename to maths/primelib.py diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index 01a411bc560d..1035171e4ec3 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -30,8 +30,8 @@ def quadratic_roots(a: int, b: int, c: int) -> tuple[complex, complex]: def main(): - solutions = quadratic_roots(a=5, b=6, c=1) - print("The solutions are: {} and {}".format(*solutions)) + solution1, solution2 = quadratic_roots(a=5, b=6, c=1) + print(f"The solutions are: {solution1} and {solution2}") if __name__ == "__main__": diff --git a/maths/series/arithmetic_mean.py b/maths/series/arithmetic_mean.py new file mode 100644 index 000000000000..b5d64b63ac3f --- /dev/null +++ b/maths/series/arithmetic_mean.py @@ -0,0 +1,66 @@ +""" +ARITHMETIC MEAN : https://en.wikipedia.org/wiki/Arithmetic_mean + +""" + + +def is_arithmetic_series(series: list) -> bool: + """ + checking whether the input series is arithmetic series or not + + >>> is_arithmetic_series([2, 4, 6]) + True + >>> is_arithmetic_series([3, 6, 12, 24]) + False + >>> is_arithmetic_series([1, 2, 3]) + True + """ + if len(series) == 1: + return True + common_diff = series[1] - series[0] + for index in range(len(series) - 1): + if series[index + 1] - series[index] != common_diff: + return False + return True + + +def arithmetic_mean(series: list) -> float: + """ + return the arithmetic mean of series + + >>> arithmetic_mean([2, 4, 6]) + 4.0 + >>> arithmetic_mean([3, 6, 9, 12]) + 7.5 + >>> arithmetic_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 6] + >>> arithmetic_mean([4, 8, 1]) + Traceback (most recent call last): + ... + ValueError: Input list is not an arithmetic series + >>> arithmetic_mean([1, 2, 3]) + 2.0 + >>> arithmetic_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 6]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if not is_arithmetic_series(series): + raise ValueError("Input list is not an arithmetic series") + answer = 0 + for val in series: + answer += val + return answer / len(series) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/series/geometric_mean.py b/maths/series/geometric_mean.py new file mode 100644 index 000000000000..50ae54ad6574 --- /dev/null +++ b/maths/series/geometric_mean.py @@ -0,0 +1,75 @@ +""" +GEOMETRIC MEAN : https://en.wikipedia.org/wiki/Geometric_mean +""" + + +def is_geometric_series(series: list) -> bool: + """ + checking whether the input series is geometric series or not + + >>> is_geometric_series([2, 4, 8]) + True + >>> is_geometric_series([3, 6, 12, 24]) + True + >>> is_geometric_series([1, 2, 3]) + False + >>> is_geometric_series([0, 0, 3]) + False + + """ + if len(series) == 1: + return True + try: + common_ratio = series[1] / series[0] + for index in range(len(series) - 1): + if series[index + 1] / series[index] != common_ratio: + return False + except ZeroDivisionError: + return False + return True + + +def geometric_mean(series: list) -> float: + """ + return the geometric mean of series + + >>> geometric_mean([2, 4, 8]) + 3.9999999999999996 + >>> geometric_mean([3, 6, 12, 24]) + 8.48528137423857 + >>> geometric_mean([4, 8, 16]) + 7.999999999999999 + >>> geometric_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 8] + >>> geometric_mean([1, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Input list is not a geometric series + >>> geometric_mean([0, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Input list is not a geometric series + >>> geometric_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 8]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if not is_geometric_series(series): + raise ValueError("Input list is not a geometric series") + answer = 1 + for value in series: + answer *= value + return pow(answer, 1 / len(series)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/sieve_of_eratosthenes.py b/maths/sieve_of_eratosthenes.py index faf6fc0f9a98..47a086546900 100644 --- a/maths/sieve_of_eratosthenes.py +++ b/maths/sieve_of_eratosthenes.py @@ -8,54 +8,58 @@ Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes doctest provider: Bruno Simas Hadlich (https://github.com/brunohadlich) -Also thanks Dmitry (https://github.com/LizardWizzard) for finding the problem +Also thanks to Dmitry (https://github.com/LizardWizzard) for finding the problem """ import math +from typing import List -def sieve(n): +def prime_sieve(num: int) -> List[int]: """ Returns a list with all prime numbers up to n. - >>> sieve(50) + >>> prime_sieve(50) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] - >>> sieve(25) + >>> prime_sieve(25) [2, 3, 5, 7, 11, 13, 17, 19, 23] - >>> sieve(10) + >>> prime_sieve(10) [2, 3, 5, 7] - >>> sieve(9) + >>> prime_sieve(9) [2, 3, 5, 7] - >>> sieve(2) + >>> prime_sieve(2) [2] - >>> sieve(1) + >>> prime_sieve(1) [] """ - l = [True] * (n + 1) # noqa: E741 + if num <= 0: + raise ValueError(f"{num}: Invalid input, please enter a positive integer.") + + sieve = [True] * (num + 1) prime = [] start = 2 - end = int(math.sqrt(n)) + end = int(math.sqrt(num)) while start <= end: # If start is a prime - if l[start] is True: + if sieve[start] is True: prime.append(start) # Set multiples of start be False - for i in range(start * start, n + 1, start): - if l[i] is True: - l[i] = False + for i in range(start * start, num + 1, start): + if sieve[i] is True: + sieve[i] = False start += 1 - for j in range(end + 1, n + 1): - if l[j] is True: + for j in range(end + 1, num + 1): + if sieve[j] is True: prime.append(j) return prime if __name__ == "__main__": - print(sieve(int(input("Enter n: ").strip()))) + print(prime_sieve(int(input("Enter a positive integer: ").strip()))) diff --git a/maths/sigmoid.py b/maths/sigmoid.py new file mode 100644 index 000000000000..147588e8871f --- /dev/null +++ b/maths/sigmoid.py @@ -0,0 +1,39 @@ +""" +This script demonstrates the implementation of the Sigmoid function. + +The function takes a vector of K real numbers as input and then 1 / (1 + exp(-x)). +After through Sigmoid, the element of the vector mostly 0 between 1. or 1 between -1. + +Script inspired from its corresponding Wikipedia article +https://en.wikipedia.org/wiki/Sigmoid_function +""" + +import numpy as np + + +def sigmoid(vector: np.array) -> np.array: + """ + Implements the sigmoid function + + Parameters: + vector (np.array): A numpy array of shape (1,n) + consisting of real values + + Returns: + sigmoid_vec (np.array): The input numpy array, after applying + sigmoid. + + Examples: + >>> sigmoid(np.array([-1.0, 1.0, 2.0])) + array([0.26894142, 0.73105858, 0.88079708]) + + >>> sigmoid(np.array([0.0])) + array([0.5]) + """ + return 1 / (1 + np.exp(-vector)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/other/triplet_sum.py b/maths/triplet_sum.py similarity index 96% rename from other/triplet_sum.py rename to maths/triplet_sum.py index 0e78bb52bb72..22fab17d30c2 100644 --- a/other/triplet_sum.py +++ b/maths/triplet_sum.py @@ -1,89 +1,89 @@ -""" -Given an array of integers and another integer target, -we are required to find a triplet from the array such that it's sum is equal to -the target. -""" -from __future__ import annotations - -from itertools import permutations -from random import randint -from timeit import repeat - - -def make_dataset() -> tuple[list[int], int]: - arr = [randint(-1000, 1000) for i in range(10)] - r = randint(-5000, 5000) - return (arr, r) - - -dataset = make_dataset() - - -def triplet_sum1(arr: list[int], target: int) -> tuple[int, int, int]: - """ - Returns a triplet in the array with sum equal to target, - else (0, 0, 0). - >>> triplet_sum1([13, 29, 7, 23, 5], 35) - (5, 7, 23) - >>> triplet_sum1([37, 9, 19, 50, 44], 65) - (9, 19, 37) - >>> arr = [6, 47, 27, 1, 15] - >>> target = 11 - >>> triplet_sum1(arr, target) - (0, 0, 0) - """ - for triplet in permutations(arr, 3): - if sum(triplet) == target: - return tuple(sorted(triplet)) - return (0, 0, 0) - - -def triplet_sum2(arr: list[int], target: int) -> tuple[int, int, int]: - """ - Returns a triplet in the array with sum equal to target, - else (0, 0, 0). - >>> triplet_sum2([13, 29, 7, 23, 5], 35) - (5, 7, 23) - >>> triplet_sum2([37, 9, 19, 50, 44], 65) - (9, 19, 37) - >>> arr = [6, 47, 27, 1, 15] - >>> target = 11 - >>> triplet_sum2(arr, target) - (0, 0, 0) - """ - arr.sort() - n = len(arr) - for i in range(n - 1): - left, right = i + 1, n - 1 - while left < right: - if arr[i] + arr[left] + arr[right] == target: - return (arr[i], arr[left], arr[right]) - elif arr[i] + arr[left] + arr[right] < target: - left += 1 - elif arr[i] + arr[left] + arr[right] > target: - right -= 1 - return (0, 0, 0) - - -def solution_times() -> tuple[float, float]: - setup_code = """ -from __main__ import dataset, triplet_sum1, triplet_sum2 -""" - test_code1 = """ -triplet_sum1(*dataset) -""" - test_code2 = """ -triplet_sum2(*dataset) -""" - times1 = repeat(setup=setup_code, stmt=test_code1, repeat=5, number=10000) - times2 = repeat(setup=setup_code, stmt=test_code2, repeat=5, number=10000) - return (min(times1), min(times2)) - - -if __name__ == "__main__": - from doctest import testmod - - testmod() - times = solution_times() - print(f"The time for naive implementation is {times[0]}.") - print(f"The time for optimized implementation is {times[1]}.") +""" +Given an array of integers and another integer target, +we are required to find a triplet from the array such that it's sum is equal to +the target. +""" +from __future__ import annotations + +from itertools import permutations +from random import randint +from timeit import repeat + + +def make_dataset() -> tuple[list[int], int]: + arr = [randint(-1000, 1000) for i in range(10)] + r = randint(-5000, 5000) + return (arr, r) + + +dataset = make_dataset() + + +def triplet_sum1(arr: list[int], target: int) -> tuple[int, int, int]: + """ + Returns a triplet in the array with sum equal to target, + else (0, 0, 0). + >>> triplet_sum1([13, 29, 7, 23, 5], 35) + (5, 7, 23) + >>> triplet_sum1([37, 9, 19, 50, 44], 65) + (9, 19, 37) + >>> arr = [6, 47, 27, 1, 15] + >>> target = 11 + >>> triplet_sum1(arr, target) + (0, 0, 0) + """ + for triplet in permutations(arr, 3): + if sum(triplet) == target: + return tuple(sorted(triplet)) + return (0, 0, 0) + + +def triplet_sum2(arr: list[int], target: int) -> tuple[int, int, int]: + """ + Returns a triplet in the array with sum equal to target, + else (0, 0, 0). + >>> triplet_sum2([13, 29, 7, 23, 5], 35) + (5, 7, 23) + >>> triplet_sum2([37, 9, 19, 50, 44], 65) + (9, 19, 37) + >>> arr = [6, 47, 27, 1, 15] + >>> target = 11 + >>> triplet_sum2(arr, target) + (0, 0, 0) + """ + arr.sort() + n = len(arr) + for i in range(n - 1): + left, right = i + 1, n - 1 + while left < right: + if arr[i] + arr[left] + arr[right] == target: + return (arr[i], arr[left], arr[right]) + elif arr[i] + arr[left] + arr[right] < target: + left += 1 + elif arr[i] + arr[left] + arr[right] > target: + right -= 1 + return (0, 0, 0) + + +def solution_times() -> tuple[float, float]: + setup_code = """ +from __main__ import dataset, triplet_sum1, triplet_sum2 +""" + test_code1 = """ +triplet_sum1(*dataset) +""" + test_code2 = """ +triplet_sum2(*dataset) +""" + times1 = repeat(setup=setup_code, stmt=test_code1, repeat=5, number=10000) + times2 = repeat(setup=setup_code, stmt=test_code2, repeat=5, number=10000) + return (min(times1), min(times2)) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + times = solution_times() + print(f"The time for naive implementation is {times[0]}.") + print(f"The time for optimized implementation is {times[1]}.") diff --git a/maths/two_pointer.py b/maths/two_pointer.py new file mode 100644 index 000000000000..ff234cddc9e4 --- /dev/null +++ b/maths/two_pointer.py @@ -0,0 +1,61 @@ +""" +Given a sorted array of integers, return indices of the two numbers such +that they add up to a specific target using the two pointers technique. + +You may assume that each input would have exactly one solution, and you +may not use the same element twice. + +This is an alternative solution of the two-sum problem, which uses a +map to solve the problem. Hence can not solve the issue if there is a +constraint not use the same index twice. [1] + +Example: +Given nums = [2, 7, 11, 15], target = 9, + +Because nums[0] + nums[1] = 2 + 7 = 9, +return [0, 1]. + +[1]: https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py +""" +from __future__ import annotations + + +def two_pointer(nums: list[int], target: int) -> list[int]: + """ + >>> two_pointer([2, 7, 11, 15], 9) + [0, 1] + >>> two_pointer([2, 7, 11, 15], 17) + [0, 3] + >>> two_pointer([2, 7, 11, 15], 18) + [1, 2] + >>> two_pointer([2, 7, 11, 15], 26) + [2, 3] + >>> two_pointer([1, 3, 3], 6) + [1, 2] + >>> two_pointer([2, 7, 11, 15], 8) + [] + >>> two_pointer([3 * i for i in range(10)], 19) + [] + >>> two_pointer([1, 2, 3], 6) + [] + """ + i = 0 + j = len(nums) - 1 + + while i < j: + + if nums[i] + nums[j] == target: + return [i, j] + elif nums[i] + nums[j] < target: + i = i + 1 + else: + j = j - 1 + + return [] + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{two_pointer([2, 7, 11, 15], 9) = }") diff --git a/other/two_sum.py b/maths/two_sum.py similarity index 100% rename from other/two_sum.py rename to maths/two_sum.py diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 296c36e88691..8c39de0f23b6 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -71,13 +71,13 @@ def nth_fibonacci_bruteforce(n): def main(): - fmt = ( - "{} fibonacci number using matrix exponentiation is {} and using bruteforce " - "is {}\n" - ) for ordinal in "0th 1st 2nd 3rd 10th 100th 1000th".split(): n = int("".join(c for c in ordinal if c in "0123456789")) # 1000th --> 1000 - print(fmt.format(ordinal, nth_fibonacci_matrix(n), nth_fibonacci_bruteforce(n))) + print( + f"{ordinal} fibonacci number using matrix exponentiation is " + f"{nth_fibonacci_matrix(n)} and using bruteforce is " + f"{nth_fibonacci_bruteforce(n)}\n" + ) # from timeit import timeit # print(timeit("nth_fibonacci_matrix(1000000)", # "from main import nth_fibonacci_matrix", number=5)) diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index 6daf7e0cf2c5..f638597ae35d 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -5,8 +5,10 @@ https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array """ +from __future__ import annotations -def make_matrix(row_size: int = 4) -> [[int]]: + +def make_matrix(row_size: int = 4) -> list[list]: """ >>> make_matrix() [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] @@ -23,7 +25,7 @@ def make_matrix(row_size: int = 4) -> [[int]]: return [[1 + x + y * row_size for x in range(row_size)] for y in range(row_size)] -def rotate_90(matrix: [[]]) -> [[]]: +def rotate_90(matrix: list[list]) -> list[list]: """ >>> rotate_90(make_matrix()) [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] @@ -35,7 +37,7 @@ def rotate_90(matrix: [[]]) -> [[]]: # OR.. transpose(reverse_column(matrix)) -def rotate_180(matrix: [[]]) -> [[]]: +def rotate_180(matrix: list[list]) -> list[list]: """ >>> rotate_180(make_matrix()) [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] @@ -47,7 +49,7 @@ def rotate_180(matrix: [[]]) -> [[]]: # OR.. reverse_column(reverse_row(matrix)) -def rotate_270(matrix: [[]]) -> [[]]: +def rotate_270(matrix: list[list]) -> list[list]: """ >>> rotate_270(make_matrix()) [[13, 9, 5, 1], [14, 10, 6, 2], [15, 11, 7, 3], [16, 12, 8, 4]] @@ -59,22 +61,22 @@ def rotate_270(matrix: [[]]) -> [[]]: # OR.. transpose(reverse_row(matrix)) -def transpose(matrix: [[]]) -> [[]]: +def transpose(matrix: list[list]) -> list[list]: matrix[:] = [list(x) for x in zip(*matrix)] return matrix -def reverse_row(matrix: [[]]) -> [[]]: +def reverse_row(matrix: list[list]) -> list[list]: matrix[:] = matrix[::-1] return matrix -def reverse_column(matrix: [[]]) -> [[]]: +def reverse_column(matrix: list[list]) -> list[list]: matrix[:] = [x[::-1] for x in matrix] return matrix -def print_matrix(matrix: [[]]) -> [[]]: +def print_matrix(matrix: list[list]) -> None: for i in matrix: print(*i) diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 4920ec6c13db..3466b3d4a01f 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -175,9 +175,7 @@ def __mul__(self, another): result[r, c] += self[r, i] * another[i, c] return result else: - raise TypeError( - "Unsupported type given for another ({})".format(type(another)) - ) + raise TypeError(f"Unsupported type given for another ({type(another)})") def transpose(self): """ @@ -260,7 +258,7 @@ def test1(): print(f"v is {v}") print("uv^T is %s" % (u * v.transpose())) # Sherman Morrison - print("(a + uv^T)^(-1) is {}".format(ainv.ShermanMorrison(u, v))) + print(f"(a + uv^T)^(-1) is {ainv.ShermanMorrison(u, v)}") def test2(): import doctest diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index 3500dfeb0641..65b35fd7e78b 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -12,7 +12,7 @@ import sys import numpy as np -import pytest +import pytest # type: ignore # Custom/local libraries from matrix import matrix_operation as matop diff --git a/neural_network/2_hidden_layers_neural_network.py b/neural_network/2_hidden_layers_neural_network.py new file mode 100644 index 000000000000..1cf78ec4c7c0 --- /dev/null +++ b/neural_network/2_hidden_layers_neural_network.py @@ -0,0 +1,295 @@ +""" +References: + - http://neuralnetworksanddeeplearning.com/chap2.html (Backpropagation) + - https://en.wikipedia.org/wiki/Sigmoid_function (Sigmoid activation function) + - https://en.wikipedia.org/wiki/Feedforward_neural_network (Feedforward) +""" + +import numpy + + +class TwoHiddenLayerNeuralNetwork: + def __init__(self, input_array: numpy.ndarray, output_array: numpy.ndarray) -> None: + """ + This function initializes the TwoHiddenLayerNeuralNetwork class with random + weights for every layer and initializes predicted output with zeroes. + + input_array : input values for training the neural network (i.e training data) . + output_array : expected output values of the given inputs. + """ + + # Input values provided for training the model. + self.input_array = input_array + + # Random initial weights are assigned where first argument is the + # number of nodes in previous layer and second argument is the + # number of nodes in the next layer. + + # Random initial weights are assigned. + # self.input_array.shape[1] is used to represent number of nodes in input layer. + # First hidden layer consists of 4 nodes. + self.input_layer_and_first_hidden_layer_weights = numpy.random.rand( + self.input_array.shape[1], 4 + ) + + # Random initial values for the first hidden layer. + # First hidden layer has 4 nodes. + # Second hidden layer has 3 nodes. + self.first_hidden_layer_and_second_hidden_layer_weights = numpy.random.rand( + 4, 3 + ) + + # Random initial values for the second hidden layer. + # Second hidden layer has 3 nodes. + # Output layer has 1 node. + self.second_hidden_layer_and_output_layer_weights = numpy.random.rand(3, 1) + + # Real output values provided. + self.output_array = output_array + + # Predicted output values by the neural network. + # Predicted_output array initially consists of zeroes. + self.predicted_output = numpy.zeros(output_array.shape) + + def feedforward(self) -> numpy.ndarray: + """ + The information moves in only one direction i.e. forward from the input nodes, + through the two hidden nodes and to the output nodes. + There are no cycles or loops in the network. + + Return layer_between_second_hidden_layer_and_output + (i.e the last layer of the neural network). + + >>> input_val = numpy.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float) + >>> output_val = numpy.array(([0], [0], [0]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> res = nn.feedforward() + >>> array_sum = numpy.sum(res) + >>> numpy.isnan(array_sum) + False + """ + # Layer_between_input_and_first_hidden_layer is the layer connecting the + # input nodes with the first hidden layer nodes. + self.layer_between_input_and_first_hidden_layer = sigmoid( + numpy.dot(self.input_array, self.input_layer_and_first_hidden_layer_weights) + ) + + # layer_between_first_hidden_layer_and_second_hidden_layer is the layer + # connecting the first hidden set of nodes with the second hidden set of nodes. + self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid( + numpy.dot( + self.layer_between_input_and_first_hidden_layer, + self.first_hidden_layer_and_second_hidden_layer_weights, + ) + ) + + # layer_between_second_hidden_layer_and_output is the layer connecting + # second hidden layer with the output node. + self.layer_between_second_hidden_layer_and_output = sigmoid( + numpy.dot( + self.layer_between_first_hidden_layer_and_second_hidden_layer, + self.second_hidden_layer_and_output_layer_weights, + ) + ) + + return self.layer_between_second_hidden_layer_and_output + + def back_propagation(self) -> None: + """ + Function for fine-tuning the weights of the neural net based on the + error rate obtained in the previous epoch (i.e., iteration). + Updation is done using derivative of sogmoid activation function. + + >>> input_val = numpy.array(([0, 0, 0], [0, 0, 0], [0, 0, 0]), dtype=float) + >>> output_val = numpy.array(([0], [0], [0]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> res = nn.feedforward() + >>> nn.back_propagation() + >>> updated_weights = nn.second_hidden_layer_and_output_layer_weights + >>> (res == updated_weights).all() + False + """ + + updated_second_hidden_layer_and_output_layer_weights = numpy.dot( + self.layer_between_first_hidden_layer_and_second_hidden_layer.T, + 2 + * (self.output_array - self.predicted_output) + * sigmoid_derivative(self.predicted_output), + ) + updated_first_hidden_layer_and_second_hidden_layer_weights = numpy.dot( + self.layer_between_input_and_first_hidden_layer.T, + numpy.dot( + 2 + * (self.output_array - self.predicted_output) + * sigmoid_derivative(self.predicted_output), + self.second_hidden_layer_and_output_layer_weights.T, + ) + * sigmoid_derivative( + self.layer_between_first_hidden_layer_and_second_hidden_layer + ), + ) + updated_input_layer_and_first_hidden_layer_weights = numpy.dot( + self.input_array.T, + numpy.dot( + numpy.dot( + 2 + * (self.output_array - self.predicted_output) + * sigmoid_derivative(self.predicted_output), + self.second_hidden_layer_and_output_layer_weights.T, + ) + * sigmoid_derivative( + self.layer_between_first_hidden_layer_and_second_hidden_layer + ), + self.first_hidden_layer_and_second_hidden_layer_weights.T, + ) + * sigmoid_derivative(self.layer_between_input_and_first_hidden_layer), + ) + + self.input_layer_and_first_hidden_layer_weights += ( + updated_input_layer_and_first_hidden_layer_weights + ) + self.first_hidden_layer_and_second_hidden_layer_weights += ( + updated_first_hidden_layer_and_second_hidden_layer_weights + ) + self.second_hidden_layer_and_output_layer_weights += ( + updated_second_hidden_layer_and_output_layer_weights + ) + + def train(self, output: numpy.ndarray, iterations: int, give_loss: bool) -> None: + """ + Performs the feedforwarding and back propagation process for the + given number of iterations. + Every iteration will update the weights of neural network. + + output : real output values,required for calculating loss. + iterations : number of times the weights are to be updated. + give_loss : boolean value, If True then prints loss for each iteration, + If False then nothing is printed + + >>> input_val = numpy.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float) + >>> output_val = numpy.array(([0], [1], [1]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> first_iteration_weights = nn.feedforward() + >>> nn.back_propagation() + >>> updated_weights = nn.second_hidden_layer_and_output_layer_weights + >>> (first_iteration_weights == updated_weights).all() + False + """ + for iteration in range(1, iterations + 1): + self.output = self.feedforward() + self.back_propagation() + if give_loss: + loss = numpy.mean(numpy.square(output - self.feedforward())) + print(f"Iteration {iteration} Loss: {loss}") + + def predict(self, input: numpy.ndarray) -> int: + """ + Predict's the output for the given input values using + the trained neural network. + + The output value given by the model ranges in-between 0 and 1. + The predict function returns 1 if the model value is greater + than the threshold value else returns 0, + as the real output values are in binary. + + >>> input_val = numpy.array(([0, 0, 0], [0, 1, 0], [0, 0, 1]), dtype=float) + >>> output_val = numpy.array(([0], [1], [1]), dtype=float) + >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) + >>> nn.train(output_val, 1000, False) + >>> nn.predict([0,1,0]) in (0, 1) + True + """ + + # Input values for which the predictions are to be made. + self.array = input + + self.layer_between_input_and_first_hidden_layer = sigmoid( + numpy.dot(self.array, self.input_layer_and_first_hidden_layer_weights) + ) + + self.layer_between_first_hidden_layer_and_second_hidden_layer = sigmoid( + numpy.dot( + self.layer_between_input_and_first_hidden_layer, + self.first_hidden_layer_and_second_hidden_layer_weights, + ) + ) + + self.layer_between_second_hidden_layer_and_output = sigmoid( + numpy.dot( + self.layer_between_first_hidden_layer_and_second_hidden_layer, + self.second_hidden_layer_and_output_layer_weights, + ) + ) + + return int(self.layer_between_second_hidden_layer_and_output > 0.6) + + +def sigmoid(value: numpy.ndarray) -> numpy.ndarray: + """ + Applies sigmoid activation function. + + return normalized values + + >>> sigmoid(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64)) + array([[0.73105858, 0.5 , 0.88079708], + [0.73105858, 0.5 , 0.5 ]]) + """ + return 1 / (1 + numpy.exp(-value)) + + +def sigmoid_derivative(value: numpy.ndarray) -> numpy.ndarray: + """ + Provides the derivative value of the sigmoid function. + + returns derivative of the sigmoid value + + >>> sigmoid_derivative(numpy.array(([1, 0, 2], [1, 0, 0]), dtype=numpy.float64)) + array([[ 0., 0., -2.], + [ 0., 0., 0.]]) + """ + return (value) * (1 - (value)) + + +def example() -> int: + """ + Example for "how to use the neural network class and use the + respected methods for the desired output". + Calls the TwoHiddenLayerNeuralNetwork class and + provides the fixed input output values to the model. + Model is trained for a fixed amount of iterations then the predict method is called. + In this example the output is divided into 2 classes i.e. binary classification, + the two classes are represented by '0' and '1'. + + >>> example() in (0, 1) + True + """ + # Input values. + input = numpy.array( + ( + [0, 0, 0], + [0, 0, 1], + [0, 1, 0], + [0, 1, 1], + [1, 0, 0], + [1, 0, 1], + [1, 1, 0], + [1, 1, 1], + ), + dtype=numpy.float64, + ) + + # True output values for the given input values. + output = numpy.array(([0], [1], [1], [0], [1], [0], [0], [1]), dtype=numpy.float64) + + # Calling neural network class. + neural_network = TwoHiddenLayerNeuralNetwork(input_array=input, output_array=output) + + # Calling training function. + # Set give_loss to True if you want to see loss in every iteration. + neural_network.train(output=output, iterations=10, give_loss=False) + + return neural_network.predict(numpy.array(([1, 1, 1]), dtype=numpy.float64)) + + +if __name__ == "__main__": + example() diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 23b409b227c4..063be5ea554c 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -11,7 +11,14 @@ class Perceptron: - def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=-1): + def __init__( + self, + sample: list[list[float]], + target: list[int], + learning_rate: float = 0.01, + epoch_number: int = 1000, + bias: float = -1, + ) -> None: """ Initializes a Perceptron network for oil analysis :param sample: sample dataset of 3 parameters with shape [30,3] @@ -46,7 +53,7 @@ def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=- self.bias = bias self.number_sample = len(sample) self.col_sample = len(sample[0]) # number of columns in dataset - self.weight = [] + self.weight: list = [] def training(self) -> None: """ @@ -94,7 +101,7 @@ def training(self) -> None: # if epoch_count > self.epoch_number or not error: break - def sort(self, sample) -> None: + def sort(self, sample: list[float]) -> None: """ :param sample: example row to classify as P1 or P2 :return: None @@ -221,11 +228,11 @@ def sign(self, u: float) -> int: print("Finished training perceptron") print("Enter values to predict or q to exit") while True: - sample = [] + sample: list = [] for i in range(len(samples[0])): - observation = input("value: ").strip() - if observation == "q": + user_input = input("value: ").strip() + if user_input == "q": break - observation = float(observation) + observation = float(user_input) sample.insert(i, observation) network.sort(sample) diff --git "a/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" "b/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" new file mode 100644 index 000000000000..d16de6dd988b --- /dev/null +++ "b/other/davis\342\200\223putnam\342\200\223logemann\342\200\223loveland.py" @@ -0,0 +1,357 @@ +#!/usr/bin/env python3 + +""" +Davis–Putnam–Logemann–Loveland (DPLL) algorithm is a complete, backtracking-based +search algorithm for deciding the satisfiability of propositional logic formulae in +conjunctive normal form, i.e, for solving the Conjunctive Normal Form SATisfiability +(CNF-SAT) problem. + +For more information about the algorithm: https://en.wikipedia.org/wiki/DPLL_algorithm +""" + +import random +from typing import Dict, List + + +class Clause: + """ + A clause represented in Conjunctive Normal Form. + A clause is a set of literals, either complemented or otherwise. + For example: + {A1, A2, A3'} is the clause (A1 v A2 v A3') + {A5', A2', A1} is the clause (A5' v A2' v A1) + + Create model + >>> clause = Clause(["A1", "A2'", "A3"]) + >>> clause.evaluate({"A1": True}) + True + """ + + def __init__(self, literals: List[int]) -> None: + """ + Represent the literals and an assignment in a clause." + """ + # Assign all literals to None initially + self.literals = {literal: None for literal in literals} + + def __str__(self) -> str: + """ + To print a clause as in Conjunctive Normal Form. + >>> str(Clause(["A1", "A2'", "A3"])) + "{A1 , A2' , A3}" + """ + return "{" + " , ".join(self.literals) + "}" + + def __len__(self) -> int: + """ + To print a clause as in Conjunctive Normal Form. + >>> len(Clause([])) + 0 + >>> len(Clause(["A1", "A2'", "A3"])) + 3 + """ + return len(self.literals) + + def assign(self, model: Dict[str, bool]) -> None: + """ + Assign values to literals of the clause as given by model. + """ + for literal in self.literals: + symbol = literal[:2] + if symbol in model: + value = model[symbol] + else: + continue + if value is not None: + # Complement assignment if literal is in complemented form + if literal.endswith("'"): + value = not value + self.literals[literal] = value + + def evaluate(self, model: Dict[str, bool]) -> bool: + """ + Evaluates the clause with the assignments in model. + This has the following steps: + 1. Return True if both a literal and its complement exist in the clause. + 2. Return True if a single literal has the assignment True. + 3. Return None(unable to complete evaluation) if a literal has no assignment. + 4. Compute disjunction of all values assigned in clause. + """ + for literal in self.literals: + symbol = literal.rstrip("'") if literal.endswith("'") else literal + "'" + if symbol in self.literals: + return True + + self.assign(model) + for value in self.literals.values(): + if value in (True, None): + return value + return any(self.literals.values()) + + +class Formula: + """ + A formula represented in Conjunctive Normal Form. + A formula is a set of clauses. + For example, + {{A1, A2, A3'}, {A5', A2', A1}} is ((A1 v A2 v A3') and (A5' v A2' v A1)) + """ + + def __init__(self, clauses: List[Clause]) -> None: + """ + Represent the number of clauses and the clauses themselves. + """ + self.clauses = list(clauses) + + def __str__(self) -> str: + """ + To print a formula as in Conjunctive Normal Form. + str(Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])])) + "{{A1 , A2' , A3} , {A5' , A2' , A1}}" + """ + return "{" + " , ".join(str(clause) for clause in self.clauses) + "}" + + +def generate_clause() -> Clause: + """ + Randomly generate a clause. + All literals have the name Ax, where x is an integer from 1 to 5. + """ + literals = [] + no_of_literals = random.randint(1, 5) + base_var = "A" + i = 0 + while i < no_of_literals: + var_no = random.randint(1, 5) + var_name = base_var + str(var_no) + var_complement = random.randint(0, 1) + if var_complement == 1: + var_name += "'" + if var_name in literals: + i -= 1 + else: + literals.append(var_name) + i += 1 + return Clause(literals) + + +def generate_formula() -> Formula: + """ + Randomly generate a formula. + """ + clauses = set() + no_of_clauses = random.randint(1, 10) + while len(clauses) < no_of_clauses: + clauses.add(generate_clause()) + return Formula(set(clauses)) + + +def generate_parameters(formula: Formula) -> (List[Clause], List[str]): + """ + Return the clauses and symbols from a formula. + A symbol is the uncomplemented form of a literal. + For example, + Symbol of A3 is A3. + Symbol of A5' is A5. + + >>> formula = Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])]) + >>> clauses, symbols = generate_parameters(formula) + >>> clauses_list = [str(i) for i in clauses] + >>> clauses_list + ["{A1 , A2' , A3}", "{A5' , A2' , A1}"] + >>> symbols + ['A1', 'A2', 'A3', 'A5'] + """ + clauses = formula.clauses + symbols_set = [] + for clause in formula.clauses: + for literal in clause.literals: + symbol = literal[:2] + if symbol not in symbols_set: + symbols_set.append(symbol) + return clauses, symbols_set + + +def find_pure_symbols( + clauses: List[Clause], symbols: List[str], model: Dict[str, bool] +) -> (List[str], Dict[str, bool]): + """ + Return pure symbols and their values to satisfy clause. + Pure symbols are symbols in a formula that exist only + in one form, either complemented or otherwise. + For example, + { { A4 , A3 , A5' , A1 , A3' } , { A4 } , { A3 } } has + pure symbols A4, A5' and A1. + This has the following steps: + 1. Ignore clauses that have already evaluated to be True. + 2. Find symbols that occur only in one form in the rest of the clauses. + 3. Assign value True or False depending on whether the symbols occurs + in normal or complemented form respectively. + + >>> formula = Formula([Clause(["A1", "A2'", "A3"]), Clause(["A5'", "A2'", "A1"])]) + >>> clauses, symbols = generate_parameters(formula) + + >>> pure_symbols, values = find_pure_symbols(clauses, symbols, {}) + >>> pure_symbols + ['A1', 'A2', 'A3', 'A5'] + >>> values + {'A1': True, 'A2': False, 'A3': True, 'A5': False} + """ + pure_symbols = [] + assignment = dict() + literals = [] + + for clause in clauses: + if clause.evaluate(model) is True: + continue + for literal in clause.literals: + literals.append(literal) + + for s in symbols: + sym = s + "'" + if (s in literals and sym not in literals) or ( + s not in literals and sym in literals + ): + pure_symbols.append(s) + for p in pure_symbols: + assignment[p] = None + for s in pure_symbols: + sym = s + "'" + if s in literals: + assignment[s] = True + elif sym in literals: + assignment[s] = False + return pure_symbols, assignment + + +def find_unit_clauses( + clauses: List[Clause], model: Dict[str, bool] +) -> (List[str], Dict[str, bool]): + """ + Returns the unit symbols and their values to satisfy clause. + Unit symbols are symbols in a formula that are: + - Either the only symbol in a clause + - Or all other literals in that clause have been assigned False + This has the following steps: + 1. Find symbols that are the only occurrences in a clause. + 2. Find symbols in a clause where all other literals are assigned False. + 3. Assign True or False depending on whether the symbols occurs in + normal or complemented form respectively. + + >>> clause1 = Clause(["A4", "A3", "A5'", "A1", "A3'"]) + >>> clause2 = Clause(["A4"]) + >>> clause3 = Clause(["A3"]) + >>> clauses, symbols = generate_parameters(Formula([clause1, clause2, clause3])) + + >>> unit_clauses, values = find_unit_clauses(clauses, {}) + >>> unit_clauses + ['A4', 'A3'] + >>> values + {'A4': True, 'A3': True} + """ + unit_symbols = [] + for clause in clauses: + if len(clause) == 1: + unit_symbols.append(list(clause.literals.keys())[0]) + else: + Fcount, Ncount = 0, 0 + for literal, value in clause.literals.items(): + if value is False: + Fcount += 1 + elif value is None: + sym = literal + Ncount += 1 + if Fcount == len(clause) - 1 and Ncount == 1: + unit_symbols.append(sym) + assignment = dict() + for i in unit_symbols: + symbol = i[:2] + assignment[symbol] = len(i) == 2 + unit_symbols = [i[:2] for i in unit_symbols] + + return unit_symbols, assignment + + +def dpll_algorithm( + clauses: List[Clause], symbols: List[str], model: Dict[str, bool] +) -> (bool, Dict[str, bool]): + """ + Returns the model if the formula is satisfiable, else None + This has the following steps: + 1. If every clause in clauses is True, return True. + 2. If some clause in clauses is False, return False. + 3. Find pure symbols. + 4. Find unit symbols. + + >>> formula = Formula([Clause(["A4", "A3", "A5'", "A1", "A3'"]), Clause(["A4"])]) + >>> clauses, symbols = generate_parameters(formula) + + >>> soln, model = dpll_algorithm(clauses, symbols, {}) + >>> soln + True + >>> model + {'A4': True} + """ + check_clause_all_true = True + for clause in clauses: + clause_check = clause.evaluate(model) + if clause_check is False: + return False, None + elif clause_check is None: + check_clause_all_true = False + continue + + if check_clause_all_true: + return True, model + + try: + pure_symbols, assignment = find_pure_symbols(clauses, symbols, model) + except RecursionError: + print("raises a RecursionError and is") + return None, {} + P = None + if len(pure_symbols) > 0: + P, value = pure_symbols[0], assignment[pure_symbols[0]] + + if P: + tmp_model = model + tmp_model[P] = value + tmp_symbols = [i for i in symbols] + if P in tmp_symbols: + tmp_symbols.remove(P) + return dpll_algorithm(clauses, tmp_symbols, tmp_model) + + unit_symbols, assignment = find_unit_clauses(clauses, model) + P = None + if len(unit_symbols) > 0: + P, value = unit_symbols[0], assignment[unit_symbols[0]] + if P: + tmp_model = model + tmp_model[P] = value + tmp_symbols = [i for i in symbols] + if P in tmp_symbols: + tmp_symbols.remove(P) + return dpll_algorithm(clauses, tmp_symbols, tmp_model) + P = symbols[0] + rest = symbols[1:] + tmp1, tmp2 = model, model + tmp1[P], tmp2[P] = True, False + + return dpll_algorithm(clauses, rest, tmp1) or dpll_algorithm(clauses, rest, tmp2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + formula = generate_formula() + print(f"The formula {formula} is", end=" ") + + clauses, symbols = generate_parameters(formula) + solution, model = dpll_algorithm(clauses, symbols, {}) + + if solution: + print(f"satisfiable with the assignment {model}.") + else: + print("not satisfiable.") diff --git a/other/graham_scan.py b/other/graham_scan.py new file mode 100644 index 000000000000..67c5cd8ab9d8 --- /dev/null +++ b/other/graham_scan.py @@ -0,0 +1,171 @@ +""" +This is a pure Python implementation of the merge-insertion sort algorithm +Source: https://en.wikipedia.org/wiki/Graham_scan + +For doctests run following command: +python3 -m doctest -v graham_scan.py +""" + +from __future__ import annotations + +from collections import deque +from enum import Enum +from math import atan2, degrees +from sys import maxsize + + +def graham_scan(points: list[list[int, int]]) -> list[list[int, int]]: + """Pure implementation of graham scan algorithm in Python + + :param points: The unique points on coordinates. + :return: The points on convex hell. + + Examples: + >>> graham_scan([(9, 6), (3, 1), (0, 0), (5, 5), (5, 2), (7, 0), (3, 3), (1, 4)]) + [(0, 0), (7, 0), (9, 6), (5, 5), (1, 4)] + + >>> graham_scan([(0, 0), (1, 0), (1, 1), (0, 1)]) + [(0, 0), (1, 0), (1, 1), (0, 1)] + + >>> graham_scan([(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)]) + [(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)] + + >>> graham_scan([(-100, 20), (99, 3), (1, 10000001), (5133186, -25), (-66, -4)]) + [(5133186, -25), (1, 10000001), (-100, 20), (-66, -4)] + """ + + if len(points) <= 2: + # There is no convex hull + raise ValueError("graham_scan: argument must contain more than 3 points.") + if len(points) == 3: + return points + # find the lowest and the most left point + minidx = 0 + miny, minx = maxsize, maxsize + for i, point in enumerate(points): + x = point[0] + y = point[1] + if y < miny: + miny = y + minx = x + minidx = i + if y == miny: + if x < minx: + minx = x + minidx = i + + # remove the lowest and the most left point from points for preparing for sort + points.pop(minidx) + + def angle_comparer(point: list[int, int], minx: int, miny: int) -> float: + """Return the angle toward to point from (minx, miny) + + :param point: The target point + minx: The starting point's x + miny: The starting point's y + :return: the angle + + Examples: + >>> angle_comparer([1,1], 0, 0) + 45.0 + + >>> angle_comparer([100,1], 10, 10) + -5.710593137499642 + + >>> angle_comparer([5,5], 2, 3) + 33.690067525979785 + """ + # sort the points accorgind to the angle from the lowest and the most left point + x = point[0] + y = point[1] + angle = degrees(atan2(y - miny, x - minx)) + return angle + + sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny)) + # This insert actually costs complexity, + # and you should insteadly add (minx, miny) into stack later. + # I'm using insert just for easy understanding. + sorted_points.insert(0, (minx, miny)) + + # traversal from the lowest and the most left point in anti-clockwise direction + # if direction gets right, the previous point is not the convex hull. + class Direction(Enum): + left = 1 + straight = 2 + right = 3 + + def check_direction( + starting: list[int, int], via: list[int, int], target: list[int, int] + ) -> Direction: + """Return the direction toward to the line from via to target from starting + + :param starting: The starting point + via: The via point + target: The target point + :return: the Direction + + Examples: + >>> check_direction([1,1], [2,2], [3,3]) + Direction.straight + + >>> check_direction([60,1], [-50,199], [30,2]) + Direction.left + + >>> check_direction([0,0], [5,5], [10,0]) + Direction.right + """ + x0, y0 = starting + x1, y1 = via + x2, y2 = target + via_angle = degrees(atan2(y1 - y0, x1 - x0)) + if via_angle < 0: + via_angle += 360 + target_angle = degrees(atan2(y2 - y0, x2 - x0)) + if target_angle < 0: + target_angle += 360 + # t- + # \ \ + # \ v + # \| + # s + # via_angle is always lower than target_angle, if direction is left. + # If they are same, it means they are on a same line of convex hull. + if target_angle > via_angle: + return Direction.left + if target_angle == via_angle: + return Direction.straight + if target_angle < via_angle: + return Direction.right + + stack = deque() + stack.append(sorted_points[0]) + stack.append(sorted_points[1]) + stack.append(sorted_points[2]) + # In any ways, the first 3 points line are towards left. + # Because we sort them the angle from minx, miny. + current_direction = Direction.left + + for i in range(3, len(sorted_points)): + while True: + starting = stack[-2] + via = stack[-1] + target = sorted_points[i] + next_direction = check_direction(starting, via, target) + + if next_direction == Direction.left: + current_direction = Direction.left + break + if next_direction == Direction.straight: + if current_direction == Direction.left: + # We keep current_direction as left. + # Because if the straight line keeps as straight, + # we want to know if this straight line is towards left. + break + elif current_direction == Direction.right: + # If the straight line is towards right, + # every previous points on those straigh line is not convex hull. + stack.pop() + if next_direction == Direction.right: + stack.pop() + stack.append(sorted_points[i]) + return list(stack) diff --git a/project_euler/README.md b/project_euler/README.md index 934e541cc067..1cc6f8150e38 100644 --- a/project_euler/README.md +++ b/project_euler/README.md @@ -1,11 +1,11 @@ # Project Euler -Problems are taken from https://projecteuler.net/. +Problems are taken from https://projecteuler.net/, the Project Euler. [Problems are licensed under CC BY-NC-SA 4.0](https://projecteuler.net/copyright). -Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical +Project Euler is a series of challenging mathematical/computer programming problems that require more than just mathematical insights to solve. Project Euler is ideal for mathematicians who are learning to code. -The solutions will be checked by our [automated testing on Travis CI](https://travis-ci.com/github/TheAlgorithms/Python/pull_requests) with the help of [this script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). The efficiency of your code is also checked. You can view the top 10 slowest solutions on Travis CI logs and open a pull request to improve those solutions. +The solutions will be checked by our [automated testing on Travis CI](https://travis-ci.com/github/TheAlgorithms/Python/pull_requests) with the help of [this script](https://github.com/TheAlgorithms/Python/blob/master/scripts/validate_solutions.py). The efficiency of your code is also checked. You can view the top 10 slowest solutions on Travis CI logs (under `slowest 10 durations`) and open a pull request to improve those solutions. ## Solution Guidelines @@ -17,18 +17,18 @@ Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Befo * Please maintain consistency in project directory and solution file names. Keep the following points in mind: * Create a new directory only for the problems which do not exist yet. * If you create a new directory, please create an empty `__init__.py` file inside it as well. - * Please name the project directory as `problem_` where `problem_number` should be filled with 0s so as to occupy 3 digits. Example: `problem_001`, `problem_002`, `problem_067`, `problem_145`, and so on. + * Please name the project **directory** as `problem_` where `problem_number` should be filled with 0s so as to occupy 3 digits. Example: `problem_001`, `problem_002`, `problem_067`, `problem_145`, and so on. -* Please provide a link to the problem and other references, if used, in the module-level docstring. +* Please provide a link to the problem and other references, if used, in the **module-level docstring**. * All imports should come ***after*** the module-level docstring. * You can have as many helper functions as you want but there should be one main function called `solution` which should satisfy the conditions as stated below: - * It should contain positional argument(s) whose default value is the question input. Example: Please take a look at [problem 1](https://projecteuler.net/problem=1) where the question is to *Find the sum of all the multiples of 3 or 5 below 1000.* In this case the main solution function will be `solution(limit: int = 1000)`. + * It should contain positional argument(s) whose default value is the question input. Example: Please take a look at [Problem 1](https://projecteuler.net/problem=1) where the question is to *Find the sum of all the multiples of 3 or 5 below 1000.* In this case the main solution function will be `solution(limit: int = 1000)`. * When the `solution` function is called without any arguments like so: `solution()`, it should return the answer to the problem. * Every function, which includes all the helper functions, if any, and the main solution function, should have `doctest` in the function docstring along with a brief statement mentioning what the function is about. - * There should not be a `doctest` for testing the answer as that is done by our Travis CI build using this [script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). Keeping in mind the above example of [problem 1](https://projecteuler.net/problem=1): + * There should not be a `doctest` for testing the answer as that is done by our Travis CI build using this [script](https://github.com/TheAlgorithms/Python/blob/master/project_euler/validate_solutions.py). Keeping in mind the above example of [Problem 1](https://projecteuler.net/problem=1): ```python def solution(limit: int = 1000): diff --git a/project_euler/problem_001/sol1.py b/project_euler/problem_001/sol1.py index 385bbbbf43b3..85ad32294c9b 100644 --- a/project_euler/problem_001/sol1.py +++ b/project_euler/problem_001/sol1.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -25,4 +30,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol2.py b/project_euler/problem_001/sol2.py index f08f548cb752..7093d3513378 100644 --- a/project_euler/problem_001/sol2.py +++ b/project_euler/problem_001/sol2.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -30,4 +35,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol3.py b/project_euler/problem_001/sol3.py index 67cb83faf238..8267fec84155 100644 --- a/project_euler/problem_001/sol3.py +++ b/project_euler/problem_001/sol3.py @@ -1,8 +1,12 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ @@ -57,4 +61,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol4.py b/project_euler/problem_001/sol4.py index 77f323695898..a0643c05b34f 100644 --- a/project_euler/problem_001/sol4.py +++ b/project_euler/problem_001/sol4.py @@ -1,47 +1,52 @@ -""" -Problem Statement: -If we list all the natural numbers below 10 that are multiples of 3 or 5, -we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. -""" - - -def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. - - >>> solution(3) - 0 - >>> solution(4) - 3 - >>> solution(10) - 23 - >>> solution(600) - 83700 - """ - - xmulti = [] - zmulti = [] - z = 3 - x = 5 - temp = 1 - while True: - result = z * temp - if result < n: - zmulti.append(result) - temp += 1 - else: - temp = 1 - break - while True: - result = x * temp - if result < n: - xmulti.append(result) - temp += 1 - else: - break - collection = list(set(xmulti + zmulti)) - return sum(collection) - - -if __name__ == "__main__": - print(solution(int(input().strip()))) +""" +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + +If we list all the natural numbers below 10 that are multiples of 3 or 5, +we get 3, 5, 6 and 9. The sum of these multiples is 23. + +Find the sum of all the multiples of 3 or 5 below 1000. +""" + + +def solution(n: int = 1000) -> int: + """ + Returns the sum of all the multiples of 3 or 5 below n. + + >>> solution(3) + 0 + >>> solution(4) + 3 + >>> solution(10) + 23 + >>> solution(600) + 83700 + """ + + xmulti = [] + zmulti = [] + z = 3 + x = 5 + temp = 1 + while True: + result = z * temp + if result < n: + zmulti.append(result) + temp += 1 + else: + temp = 1 + break + while True: + result = x * temp + if result < n: + xmulti.append(result) + temp += 1 + else: + break + collection = list(set(xmulti + zmulti)) + return sum(collection) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol5.py b/project_euler/problem_001/sol5.py index 256516802ca0..7f0b0bd1bc7c 100644 --- a/project_euler/problem_001/sol5.py +++ b/project_euler/problem_001/sol5.py @@ -1,14 +1,19 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. - A straightforward pythonic solution using list comprehension. + """ + Returns the sum of all the multiples of 3 or 5 below n. + A straightforward pythonic solution using list comprehension. >>> solution(3) 0 @@ -24,4 +29,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol6.py b/project_euler/problem_001/sol6.py index 5f60512a73fb..8ddce18ced04 100644 --- a/project_euler/problem_001/sol6.py +++ b/project_euler/problem_001/sol6.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -31,4 +36,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_001/sol7.py b/project_euler/problem_001/sol7.py index 5761c00f2996..8f5d1977fdde 100644 --- a/project_euler/problem_001/sol7.py +++ b/project_euler/problem_001/sol7.py @@ -1,13 +1,18 @@ """ -Problem Statement: +Project Euler Problem 1: https://projecteuler.net/problem=1 + +Multiples of 3 and 5 + If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. -Find the sum of all the multiples of 3 or 5 below N. + +Find the sum of all the multiples of 3 or 5 below 1000. """ def solution(n: int = 1000) -> int: - """Returns the sum of all the multiples of 3 or 5 below n. + """ + Returns the sum of all the multiples of 3 or 5 below n. >>> solution(3) 0 @@ -29,4 +34,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol1.py b/project_euler/problem_002/sol1.py index 2acc93b0affc..539f68fb6bc1 100644 --- a/project_euler/problem_002/sol1.py +++ b/project_euler/problem_002/sol1.py @@ -1,19 +1,25 @@ """ -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: +Project Euler Problem 2: https://projecteuler.net/problem=2 - 1,2,3,5,8,13,21,34,55,89,.. +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -26,6 +32,7 @@ def solution(n: int = 4000000) -> int: >>> solution(34) 44 """ + i = 1 j = 2 total = 0 @@ -38,4 +45,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol2.py b/project_euler/problem_002/sol2.py index 01fc552b9b21..9033d0a69bcf 100644 --- a/project_euler/problem_002/sol2.py +++ b/project_euler/problem_002/sol2.py @@ -1,39 +1,46 @@ -""" -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: - - 1,2,3,5,8,13,21,34,55,89,.. - -By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. -""" - - -def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. - - >>> solution(10) - 10 - >>> solution(15) - 10 - >>> solution(2) - 2 - >>> solution(1) - 0 - >>> solution(34) - 44 - """ - even_fibs = [] - a, b = 0, 1 - while b <= n: - if b % 2 == 0: - even_fibs.append(b) - a, b = b, a + b - return sum(even_fibs) - - -if __name__ == "__main__": - print(solution(int(input().strip()))) +""" +Project Euler Problem 2: https://projecteuler.net/problem=2 + +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... + +By considering the terms in the Fibonacci sequence whose values do not exceed +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number +""" + + +def solution(n: int = 4000000) -> int: + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. + + >>> solution(10) + 10 + >>> solution(15) + 10 + >>> solution(2) + 2 + >>> solution(1) + 0 + >>> solution(34) + 44 + """ + + even_fibs = [] + a, b = 0, 1 + while b <= n: + if b % 2 == 0: + even_fibs.append(b) + a, b = b, a + b + return sum(even_fibs) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol3.py b/project_euler/problem_002/sol3.py index 53d8ca6f1b68..3ae175a99815 100644 --- a/project_euler/problem_002/sol3.py +++ b/project_euler/problem_002/sol3.py @@ -1,19 +1,25 @@ """ -Problem: +Project Euler Problem 2: https://projecteuler.net/problem=2 + +Even Fibonacci Numbers + Each new term in the Fibonacci sequence is generated by adding the previous two terms. By starting with 1 and 2, the first 10 terms will be: - 1,2,3,5,8,13,21,34,55,89,.. +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -26,6 +32,7 @@ def solution(n: int = 4000000) -> int: >>> solution(34) 44 """ + if n <= 1: return 0 a = 0 @@ -38,4 +45,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol4.py b/project_euler/problem_002/sol4.py index a87410b7006d..70b7d6a80a1d 100644 --- a/project_euler/problem_002/sol4.py +++ b/project_euler/problem_002/sol4.py @@ -1,21 +1,27 @@ """ -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: +Project Euler Problem 2: https://projecteuler.net/problem=2 - 1,2,3,5,8,13,21,34,55,89,.. +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ import math from decimal import Decimal, getcontext def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -32,26 +38,27 @@ def solution(n: int = 4000000) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") getcontext().prec = 100 phi = (Decimal(5) ** Decimal(0.5) + 1) / Decimal(2) @@ -62,4 +69,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_002/sol5.py b/project_euler/problem_002/sol5.py index dcf6eae85891..390fd19ef638 100644 --- a/project_euler/problem_002/sol5.py +++ b/project_euler/problem_002/sol5.py @@ -1,19 +1,25 @@ """ -Problem: -Each new term in the Fibonacci sequence is generated by adding the previous two -terms. By starting with 1 and 2, the first 10 terms will be: +Project Euler Problem 2: https://projecteuler.net/problem=2 - 1,2,3,5,8,13,21,34,55,89,.. +Even Fibonacci Numbers + +Each new term in the Fibonacci sequence is generated by adding the previous +two terms. By starting with 1 and 2, the first 10 terms will be: + +1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... By considering the terms in the Fibonacci sequence whose values do not exceed -n, find the sum of the even-valued terms. e.g. for n=10, we have {2,8}, sum is -10. +four million, find the sum of the even-valued terms. + +References: + - https://en.wikipedia.org/wiki/Fibonacci_number """ def solution(n: int = 4000000) -> int: - """Returns the sum of all fibonacci sequence even elements that are lower - or equals to n. + """ + Returns the sum of all even fibonacci sequence elements that are lower + or equal to n. >>> solution(10) 10 @@ -43,4 +49,4 @@ def solution(n: int = 4000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_003/sol1.py b/project_euler/problem_003/sol1.py index 22efeb2c4e90..3441dbf9e0b3 100644 --- a/project_euler/problem_003/sol1.py +++ b/project_euler/problem_003/sol1.py @@ -1,16 +1,22 @@ """ -Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor -of a given number N? +Project Euler Problem 3: https://projecteuler.net/problem=3 -e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. -""" +Largest prime factor + +The prime factors of 13195 are 5, 7, 13 and 29. + +What is the largest prime factor of the number 600851475143? +References: + - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization +""" import math def isprime(num: int) -> bool: - """Returns boolean representing primality of given number num. + """ + Returns boolean representing primality of given number num. + >>> isprime(2) True >>> isprime(3) @@ -22,14 +28,15 @@ def isprime(num: int) -> bool: >>> isprime(0) Traceback (most recent call last): ... - ValueError: Parameter num must be greater or equal to two. + ValueError: Parameter num must be greater than or equal to two. >>> isprime(1) Traceback (most recent call last): ... - ValueError: Parameter num must be greater or equal to two. + ValueError: Parameter num must be greater than or equal to two. """ + if num <= 1: - raise ValueError("Parameter num must be greater or equal to two.") + raise ValueError("Parameter num must be greater than or equal to two.") if num == 2: return True elif num % 2 == 0: @@ -41,7 +48,9 @@ def isprime(num: int) -> bool: def solution(n: int = 600851475143) -> int: - """Returns the largest prime factor of a given number n. + """ + Returns the largest prime factor of a given number n. + >>> solution(13195) 29 >>> solution(10) @@ -53,26 +62,27 @@ def solution(n: int = 600851475143) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") max_number = 0 if isprime(n): return n @@ -91,4 +101,4 @@ def solution(n: int = 600851475143) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_003/sol2.py b/project_euler/problem_003/sol2.py index f28232109a84..0af0daceed06 100644 --- a/project_euler/problem_003/sol2.py +++ b/project_euler/problem_003/sol2.py @@ -1,14 +1,21 @@ """ -Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor -of a given number N? +Project Euler Problem 3: https://projecteuler.net/problem=3 -e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. +Largest prime factor + +The prime factors of 13195 are 5, 7, 13 and 29. + +What is the largest prime factor of the number 600851475143? + +References: + - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization """ def solution(n: int = 600851475143) -> int: - """Returns the largest prime factor of a given number n. + """ + Returns the largest prime factor of a given number n. + >>> solution(13195) 29 >>> solution(10) @@ -20,26 +27,27 @@ def solution(n: int = 600851475143) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") prime = 1 i = 2 while i * i <= n: @@ -53,4 +61,4 @@ def solution(n: int = 600851475143) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_003/sol3.py b/project_euler/problem_003/sol3.py index 676717cceca8..bc6f1d2f61ca 100644 --- a/project_euler/problem_003/sol3.py +++ b/project_euler/problem_003/sol3.py @@ -1,14 +1,21 @@ """ -Problem: -The prime factors of 13195 are 5,7,13 and 29. What is the largest prime factor -of a given number N? +Project Euler Problem 3: https://projecteuler.net/problem=3 -e.g. for 10, largest prime factor = 5. For 17, largest prime factor = 17. +Largest prime factor + +The prime factors of 13195 are 5, 7, 13 and 29. + +What is the largest prime factor of the number 600851475143? + +References: + - https://en.wikipedia.org/wiki/Prime_number#Unique_factorization """ def solution(n: int = 600851475143) -> int: - """Returns the largest prime factor of a given number n. + """ + Returns the largest prime factor of a given number n. + >>> solution(13195) 29 >>> solution(10) @@ -20,26 +27,27 @@ def solution(n: int = 600851475143) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") i = 2 ans = 0 if n == 2: @@ -55,4 +63,4 @@ def solution(n: int = 600851475143) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_004/sol1.py b/project_euler/problem_004/sol1.py index 42f56f3ef389..db6133a1a1d2 100644 --- a/project_euler/problem_004/sol1.py +++ b/project_euler/problem_004/sol1.py @@ -1,15 +1,21 @@ """ -Problem: -A palindromic number reads the same both ways. The largest palindrome made from -the product of two 2-digit numbers is 9009 = 91 x 99. +Project Euler Problem 4: https://projecteuler.net/problem=4 -Find the largest palindrome made from the product of two 3-digit numbers which -is less than N. +Largest palindrome product + +A palindromic number reads the same both ways. The largest palindrome made +from the product of two 2-digit numbers is 9009 = 91 × 99. + +Find the largest palindrome made from the product of two 3-digit numbers. + +References: + - https://en.wikipedia.org/wiki/Palindromic_number """ def solution(n: int = 998001) -> int: - """Returns the largest palindrome made from the product of two 3-digit + """ + Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. >>> solution(20000) @@ -23,10 +29,10 @@ def solution(n: int = 998001) -> int: ... ValueError: That number is larger than our acceptable range. """ + # fetches the next number for number in range(n - 1, 9999, -1): - # converts number into string. str_number = str(number) # checks whether 'str_number' is a palindrome. @@ -44,8 +50,4 @@ def solution(n: int = 998001) -> int: if __name__ == "__main__": - import doctest - - doctest.testmod() - - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_004/sol2.py b/project_euler/problem_004/sol2.py index 8ee082ad2f6a..abc880966d58 100644 --- a/project_euler/problem_004/sol2.py +++ b/project_euler/problem_004/sol2.py @@ -1,15 +1,21 @@ """ -Problem: -A palindromic number reads the same both ways. The largest palindrome made from -the product of two 2-digit numbers is 9009 = 91 x 99. +Project Euler Problem 4: https://projecteuler.net/problem=4 -Find the largest palindrome made from the product of two 3-digit numbers which -is less than N. +Largest palindrome product + +A palindromic number reads the same both ways. The largest palindrome made +from the product of two 2-digit numbers is 9009 = 91 × 99. + +Find the largest palindrome made from the product of two 3-digit numbers. + +References: + - https://en.wikipedia.org/wiki/Palindromic_number """ def solution(n: int = 998001) -> int: - """Returns the largest palindrome made from the product of two 3-digit + """ + Returns the largest palindrome made from the product of two 3-digit numbers which is less than n. >>> solution(20000) @@ -19,6 +25,7 @@ def solution(n: int = 998001) -> int: >>> solution(40000) 39893 """ + answer = 0 for i in range(999, 99, -1): # 3 digit numbers range from 999 down to 100 for j in range(999, 99, -1): @@ -29,4 +36,4 @@ def solution(n: int = 998001) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_005/sol1.py b/project_euler/problem_005/sol1.py index a347d6564fa7..f272c102d2bb 100644 --- a/project_euler/problem_005/sol1.py +++ b/project_euler/problem_005/sol1.py @@ -1,23 +1,28 @@ """ -Problem: -2520 is the smallest number that can be divided by each of the numbers from 1 -to 10 without any remainder. +Project Euler Problem 5: https://projecteuler.net/problem=5 -What is the smallest positive number that is evenly divisible(divisible with no -remainder) by all of the numbers from 1 to N? +Smallest multiple + +2520 is the smallest number that can be divided by each of the numbers +from 1 to 10 without any remainder. + +What is the smallest positive number that is _evenly divisible_ by all +of the numbers from 1 to 20? + +References: + - https://en.wiktionary.org/wiki/evenly_divisible """ def solution(n: int = 20) -> int: - """Returns the smallest positive number that is evenly divisible(divisible + """ + Returns the smallest positive number that is evenly divisible (divisible with no remainder) by all of the numbers from 1 to n. >>> solution(10) 2520 >>> solution(15) 360360 - >>> solution(20) - 232792560 >>> solution(22) 232792560 >>> solution(3.4) @@ -25,26 +30,27 @@ def solution(n: int = 20) -> int: >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter n must be greater or equal to one. + ValueError: Parameter n must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter n must be int or passive of cast to int. + TypeError: Parameter n must be int or castable to int. """ + try: n = int(n) except (TypeError, ValueError): - raise TypeError("Parameter n must be int or passive of cast to int.") + raise TypeError("Parameter n must be int or castable to int.") if n <= 0: - raise ValueError("Parameter n must be greater or equal to one.") + raise ValueError("Parameter n must be greater than or equal to one.") i = 0 while 1: i += n * (n - 1) @@ -60,4 +66,4 @@ def solution(n: int = 20) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_005/sol2.py b/project_euler/problem_005/sol2.py index 57b4cc823d82..c88044487d20 100644 --- a/project_euler/problem_005/sol2.py +++ b/project_euler/problem_005/sol2.py @@ -1,38 +1,70 @@ """ -Problem: -2520 is the smallest number that can be divided by each of the numbers from 1 -to 10 without any remainder. +Project Euler Problem 5: https://projecteuler.net/problem=5 -What is the smallest positive number that is evenly divisible(divisible with no -remainder) by all of the numbers from 1 to N? +Smallest multiple + +2520 is the smallest number that can be divided by each of the numbers +from 1 to 10 without any remainder. + +What is the smallest positive number that is _evenly divisible_ by all +of the numbers from 1 to 20? + +References: + - https://en.wiktionary.org/wiki/evenly_divisible + - https://en.wikipedia.org/wiki/Euclidean_algorithm + - https://en.wikipedia.org/wiki/Least_common_multiple """ -""" Euclidean GCD Algorithm """ def gcd(x: int, y: int) -> int: - return x if y == 0 else gcd(y, x % y) + """ + Euclidean GCD algorithm (Greatest Common Divisor) + >>> gcd(0, 0) + 0 + >>> gcd(23, 42) + 1 + >>> gcd(15, 33) + 3 + >>> gcd(12345, 67890) + 15 + """ -""" Using the property lcm*gcd of two numbers = product of them """ + return x if y == 0 else gcd(y, x % y) def lcm(x: int, y: int) -> int: + """ + Least Common Multiple. + + Using the property that lcm(a, b) * gcd(a, b) = a*b + + >>> lcm(3, 15) + 15 + >>> lcm(1, 27) + 27 + >>> lcm(13, 27) + 351 + >>> lcm(64, 48) + 192 + """ + return (x * y) // gcd(x, y) def solution(n: int = 20) -> int: - """Returns the smallest positive number that is evenly divisible(divisible + """ + Returns the smallest positive number that is evenly divisible (divisible with no remainder) by all of the numbers from 1 to n. >>> solution(10) 2520 >>> solution(15) 360360 - >>> solution(20) - 232792560 >>> solution(22) 232792560 """ + g = 1 for i in range(1, n + 1): g = lcm(g, i) @@ -40,4 +72,4 @@ def solution(n: int = 20) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol1.py b/project_euler/problem_006/sol1.py index 38f995bbf822..61dd7a321011 100644 --- a/project_euler/problem_006/sol1.py +++ b/project_euler/problem_006/sol1.py @@ -1,22 +1,25 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -27,9 +30,8 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_of_squares = 0 sum_of_ints = 0 for i in range(1, n + 1): @@ -39,7 +41,4 @@ def solution(n: int = 100) -> int: if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol2.py b/project_euler/problem_006/sol2.py index f4d74c993f0d..cd1bc5071e0e 100644 --- a/project_euler/problem_006/sol2.py +++ b/project_euler/problem_006/sol2.py @@ -1,22 +1,25 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -27,16 +30,12 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_cubes = (n * (n + 1) // 2) ** 2 sum_squares = n * (n + 1) * (2 * n + 1) // 6 return sum_cubes - sum_squares if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol3.py b/project_euler/problem_006/sol3.py index 8b5c5d3ba4aa..c87931309574 100644 --- a/project_euler/problem_006/sol3.py +++ b/project_euler/problem_006/sol3.py @@ -1,23 +1,26 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ import math def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -28,16 +31,12 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_of_squares = sum([i * i for i in range(1, n + 1)]) square_of_sum = int(math.pow(sum(range(1, n + 1)), 2)) return square_of_sum - sum_of_squares if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_006/sol4.py b/project_euler/problem_006/sol4.py index 5fae84008448..748b141490a0 100644 --- a/project_euler/problem_006/sol4.py +++ b/project_euler/problem_006/sol4.py @@ -1,22 +1,25 @@ """ -Problem 6: https://projecteuler.net/problem=6 +Project Euler Problem 6: https://projecteuler.net/problem=6 + +Sum square difference The sum of the squares of the first ten natural numbers is, - 1^2 + 2^2 + ... + 10^2 = 385 + 1^2 + 2^2 + ... + 10^2 = 385 The square of the sum of the first ten natural numbers is, - (1 + 2 + ... + 10)^2 = 552 = 3025 + (1 + 2 + ... + 10)^2 = 55^2 = 3025 -Hence the difference between the sum of the squares of the first ten natural -numbers and the square of the sum is 3025 − 385 = 2640. +Hence the difference between the sum of the squares of the first ten +natural numbers and the square of the sum is 3025 - 385 = 2640. -Find the difference between the sum of the squares of the first N natural -numbers and the square of the sum. +Find the difference between the sum of the squares of the first one +hundred natural numbers and the square of the sum. """ def solution(n: int = 100) -> int: - """Returns the difference between the sum of the squares of the first n + """ + Returns the difference between the sum of the squares of the first n natural numbers and the square of the sum. >>> solution(10) @@ -27,16 +30,12 @@ def solution(n: int = 100) -> int: 41230 >>> solution(50) 1582700 - >>> solution() - 25164150 """ + sum_of_squares = n * (n + 1) * (2 * n + 1) / 6 square_of_sum = (n * (n + 1) / 2) ** 2 return int(square_of_sum - sum_of_squares) if __name__ == "__main__": - import doctest - - doctest.testmod() - print(solution(int(input("Enter a number: ").strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_007/sol1.py b/project_euler/problem_007/sol1.py index 727d7fb7fac6..78fbcb511611 100644 --- a/project_euler/problem_007/sol1.py +++ b/project_euler/problem_007/sol1.py @@ -1,17 +1,34 @@ """ -Problem 7: https://projecteuler.net/problem=7 +Project Euler Problem 7: https://projecteuler.net/problem=7 -By listing the first six prime numbers: +10001st prime - 2, 3, 5, 7, 11, and 13 +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we +can see that the 6th prime is 13. -We can see that the 6th prime is 13. What is the Nth prime number? +What is the 10001st prime number? + +References: + - https://en.wikipedia.org/wiki/Prime_number """ + from math import sqrt def is_prime(num: int) -> bool: - """Determines whether the given number is prime or not""" + """ + Determines whether the given number is prime or not + + >>> is_prime(2) + True + >>> is_prime(15) + False + >>> is_prime(29) + True + >>> is_prime(0) + False + """ + if num == 2: return True elif num % 2 == 0: @@ -25,7 +42,8 @@ def is_prime(num: int) -> bool: def solution(nth: int = 10001) -> int: - """Returns the n-th prime number. + """ + Returns the n-th prime number. >>> solution(6) 13 @@ -39,9 +57,8 @@ def solution(nth: int = 10001) -> int: 229 >>> solution(100) 541 - >>> solution() - 104743 """ + count = 0 number = 1 while count != nth and number < 3: @@ -56,4 +73,4 @@ def solution(nth: int = 10001) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_007/sol2.py b/project_euler/problem_007/sol2.py index 62806e1e2e5d..b395c631b766 100644 --- a/project_euler/problem_007/sol2.py +++ b/project_euler/problem_007/sol2.py @@ -1,16 +1,30 @@ """ -Problem 7: https://projecteuler.net/problem=7 +Project Euler Problem 7: https://projecteuler.net/problem=7 -By listing the first six prime numbers: +10001st prime - 2, 3, 5, 7, 11, and 13 +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we +can see that the 6th prime is 13. -We can see that the 6th prime is 13. What is the Nth prime number? +What is the 10001st prime number? + +References: + - https://en.wikipedia.org/wiki/Prime_number """ def isprime(number: int) -> bool: - """Determines whether the given number is prime or not""" + """ + Determines whether the given number is prime or not + + >>> isprime(2) + True + >>> isprime(15) + False + >>> isprime(29) + True + """ + for i in range(2, int(number ** 0.5) + 1): if number % i == 0: return False @@ -18,7 +32,8 @@ def isprime(number: int) -> bool: def solution(nth: int = 10001) -> int: - """Returns the n-th prime number. + """ + Returns the n-th prime number. >>> solution(6) 13 @@ -32,35 +47,32 @@ def solution(nth: int = 10001) -> int: 229 >>> solution(100) 541 - >>> solution() - 104743 >>> solution(3.4) 5 >>> solution(0) Traceback (most recent call last): ... - ValueError: Parameter nth must be greater or equal to one. + ValueError: Parameter nth must be greater than or equal to one. >>> solution(-17) Traceback (most recent call last): ... - ValueError: Parameter nth must be greater or equal to one. + ValueError: Parameter nth must be greater than or equal to one. >>> solution([]) Traceback (most recent call last): ... - TypeError: Parameter nth must be int or passive of cast to int. + TypeError: Parameter nth must be int or castable to int. >>> solution("asd") Traceback (most recent call last): ... - TypeError: Parameter nth must be int or passive of cast to int. + TypeError: Parameter nth must be int or castable to int. """ + try: nth = int(nth) except (TypeError, ValueError): - raise TypeError( - "Parameter nth must be int or passive of cast to int." - ) from None + raise TypeError("Parameter nth must be int or castable to int.") from None if nth <= 0: - raise ValueError("Parameter nth must be greater or equal to one.") + raise ValueError("Parameter nth must be greater than or equal to one.") primes = [] num = 2 while len(primes) < nth: @@ -73,4 +85,4 @@ def solution(nth: int = 10001) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_007/sol3.py b/project_euler/problem_007/sol3.py index 1182875c05c9..7911fa3e9d6f 100644 --- a/project_euler/problem_007/sol3.py +++ b/project_euler/problem_007/sol3.py @@ -1,24 +1,42 @@ """ -Project 7: https://projecteuler.net/problem=7 +Project Euler Problem 7: https://projecteuler.net/problem=7 -By listing the first six prime numbers: +10001st prime - 2, 3, 5, 7, 11, and 13 +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we +can see that the 6th prime is 13. -We can see that the 6th prime is 13. What is the Nth prime number? +What is the 10001st prime number? + +References: + - https://en.wikipedia.org/wiki/Prime_number """ import itertools import math def prime_check(number: int) -> bool: - """Determines whether a given number is prime or not""" + """ + Determines whether a given number is prime or not + + >>> prime_check(2) + True + >>> prime_check(15) + False + >>> prime_check(29) + True + """ + if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) def prime_generator(): + """ + Generate a sequence of prime numbers + """ + num = 2 while True: if prime_check(num): @@ -27,7 +45,8 @@ def prime_generator(): def solution(nth: int = 10001) -> int: - """Returns the n-th prime number. + """ + Returns the n-th prime number. >>> solution(6) 13 @@ -41,11 +60,9 @@ def solution(nth: int = 10001) -> int: 229 >>> solution(100) 541 - >>> solution() - 104743 """ return next(itertools.islice(prime_generator(), nth - 1, nth)) if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_008/sol1.py b/project_euler/problem_008/sol1.py index db15907b3fbd..796080127778 100644 --- a/project_euler/problem_008/sol1.py +++ b/project_euler/problem_008/sol1.py @@ -1,33 +1,36 @@ """ -Problem 8: https://projecteuler.net/problem=8 +Project Euler Problem 8: https://projecteuler.net/problem=8 + +Largest product in a series The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. -73167176531330624919225119674426574742355349194934 -96983520312774506326239578318016984801869478851843 -85861560789112949495459501737958331952853208805511 -12540698747158523863050715693290963295227443043557 -66896648950445244523161731856403098711121722383113 -62229893423380308135336276614282806444486645238749 -30358907296290491560440772390713810515859307960866 -70172427121883998797908792274921901699720888093776 -65727333001053367881220235421809751254540594752243 -52584907711670556013604839586446706324415722155397 -53697817977846174064955149290862569321978468622482 -83972241375657056057490261407972968652414535100474 -82166370484403199890008895243450658541227588666881 -16427171479924442928230863465674813919123162824586 -17866458359124566529476545682848912883142607690042 -24219022671055626321111109370544217506941658960408 -07198403850962455444362981230987879927244284909188 -84580156166097919133875499200524063689912560717606 -05886116467109405077541002256983155200055935729725 -71636269561882670428252483600823257530420752963450 + 73167176531330624919225119674426574742355349194934 + 96983520312774506326239578318016984801869478851843 + 85861560789112949495459501737958331952853208805511 + 12540698747158523863050715693290963295227443043557 + 66896648950445244523161731856403098711121722383113 + 62229893423380308135336276614282806444486645238749 + 30358907296290491560440772390713810515859307960866 + 70172427121883998797908792274921901699720888093776 + 65727333001053367881220235421809751254540594752243 + 52584907711670556013604839586446706324415722155397 + 53697817977846174064955149290862569321978468622482 + 83972241375657056057490261407972968652414535100474 + 82166370484403199890008895243450658541227588666881 + 16427171479924442928230863465674813919123162824586 + 17866458359124566529476545682848912883142607690042 + 24219022671055626321111109370544217506941658960408 + 07198403850962455444362981230987879927244284909188 + 84580156166097919133875499200524063689912560717606 + 05886116467109405077541002256983155200055935729725 + 71636269561882670428252483600823257530420752963450 Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product? """ + import sys N = """73167176531330624919225119674426574742355349194934\ @@ -53,12 +56,18 @@ def solution(n: str = N) -> int: - """Find the thirteen adjacent digits in the 1000-digit number n that have + """ + Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - >>> solution(N) - 23514624000 + >>> solution("13978431290823798458352374") + 609638400 + >>> solution("13978431295823798458352374") + 2612736000 + >>> solution("1397843129582379841238352374") + 209018880 """ + largest_product = -sys.maxsize - 1 for i in range(len(n) - 12): product = 1 @@ -70,4 +79,4 @@ def solution(n: str = N) -> int: if __name__ == "__main__": - print(solution(N)) + print(f"{solution() = }") diff --git a/project_euler/problem_008/sol2.py b/project_euler/problem_008/sol2.py index 1b338a9553d7..d2c1b4f7ca48 100644 --- a/project_euler/problem_008/sol2.py +++ b/project_euler/problem_008/sol2.py @@ -1,34 +1,35 @@ """ -Problem 8: https://projecteuler.net/problem=8 +Project Euler Problem 8: https://projecteuler.net/problem=8 + +Largest product in a series The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. -73167176531330624919225119674426574742355349194934 -96983520312774506326239578318016984801869478851843 -85861560789112949495459501737958331952853208805511 -12540698747158523863050715693290963295227443043557 -66896648950445244523161731856403098711121722383113 -62229893423380308135336276614282806444486645238749 -30358907296290491560440772390713810515859307960866 -70172427121883998797908792274921901699720888093776 -65727333001053367881220235421809751254540594752243 -52584907711670556013604839586446706324415722155397 -53697817977846174064955149290862569321978468622482 -83972241375657056057490261407972968652414535100474 -82166370484403199890008895243450658541227588666881 -16427171479924442928230863465674813919123162824586 -17866458359124566529476545682848912883142607690042 -24219022671055626321111109370544217506941658960408 -07198403850962455444362981230987879927244284909188 -84580156166097919133875499200524063689912560717606 -05886116467109405077541002256983155200055935729725 -71636269561882670428252483600823257530420752963450 + 73167176531330624919225119674426574742355349194934 + 96983520312774506326239578318016984801869478851843 + 85861560789112949495459501737958331952853208805511 + 12540698747158523863050715693290963295227443043557 + 66896648950445244523161731856403098711121722383113 + 62229893423380308135336276614282806444486645238749 + 30358907296290491560440772390713810515859307960866 + 70172427121883998797908792274921901699720888093776 + 65727333001053367881220235421809751254540594752243 + 52584907711670556013604839586446706324415722155397 + 53697817977846174064955149290862569321978468622482 + 83972241375657056057490261407972968652414535100474 + 82166370484403199890008895243450658541227588666881 + 16427171479924442928230863465674813919123162824586 + 17866458359124566529476545682848912883142607690042 + 24219022671055626321111109370544217506941658960408 + 07198403850962455444362981230987879927244284909188 + 84580156166097919133875499200524063689912560717606 + 05886116467109405077541002256983155200055935729725 + 71636269561882670428252483600823257530420752963450 Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product? """ - from functools import reduce N = ( @@ -56,12 +57,18 @@ def solution(n: str = N) -> int: - """Find the thirteen adjacent digits in the 1000-digit number n that have + """ + Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - >>> solution(N) - 23514624000 + >>> solution("13978431290823798458352374") + 609638400 + >>> solution("13978431295823798458352374") + 2612736000 + >>> solution("1397843129582379841238352374") + 209018880 """ + return max( [ reduce(lambda x, y: int(x) * int(y), n[i : i + 13]) @@ -71,4 +78,4 @@ def solution(n: str = N) -> int: if __name__ == "__main__": - print(solution(str(N))) + print(f"{solution() = }") diff --git a/project_euler/problem_008/sol3.py b/project_euler/problem_008/sol3.py index 17f68cba57d3..4b99d0ea6e76 100644 --- a/project_euler/problem_008/sol3.py +++ b/project_euler/problem_008/sol3.py @@ -1,29 +1,31 @@ """ -Problem 8: https://projecteuler.net/problem=8 +Project Euler Problem 8: https://projecteuler.net/problem=8 + +Largest product in a series The four adjacent digits in the 1000-digit number that have the greatest product are 9 × 9 × 8 × 9 = 5832. -73167176531330624919225119674426574742355349194934 -96983520312774506326239578318016984801869478851843 -85861560789112949495459501737958331952853208805511 -12540698747158523863050715693290963295227443043557 -66896648950445244523161731856403098711121722383113 -62229893423380308135336276614282806444486645238749 -30358907296290491560440772390713810515859307960866 -70172427121883998797908792274921901699720888093776 -65727333001053367881220235421809751254540594752243 -52584907711670556013604839586446706324415722155397 -53697817977846174064955149290862569321978468622482 -83972241375657056057490261407972968652414535100474 -82166370484403199890008895243450658541227588666881 -16427171479924442928230863465674813919123162824586 -17866458359124566529476545682848912883142607690042 -24219022671055626321111109370544217506941658960408 -07198403850962455444362981230987879927244284909188 -84580156166097919133875499200524063689912560717606 -05886116467109405077541002256983155200055935729725 -71636269561882670428252483600823257530420752963450 + 73167176531330624919225119674426574742355349194934 + 96983520312774506326239578318016984801869478851843 + 85861560789112949495459501737958331952853208805511 + 12540698747158523863050715693290963295227443043557 + 66896648950445244523161731856403098711121722383113 + 62229893423380308135336276614282806444486645238749 + 30358907296290491560440772390713810515859307960866 + 70172427121883998797908792274921901699720888093776 + 65727333001053367881220235421809751254540594752243 + 52584907711670556013604839586446706324415722155397 + 53697817977846174064955149290862569321978468622482 + 83972241375657056057490261407972968652414535100474 + 82166370484403199890008895243450658541227588666881 + 16427171479924442928230863465674813919123162824586 + 17866458359124566529476545682848912883142607690042 + 24219022671055626321111109370544217506941658960408 + 07198403850962455444362981230987879927244284909188 + 84580156166097919133875499200524063689912560717606 + 05886116467109405077541002256983155200055935729725 + 71636269561882670428252483600823257530420752963450 Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product? @@ -53,13 +55,15 @@ def str_eval(s: str) -> int: - """Returns product of digits in given string n + """ + Returns product of digits in given string n >>> str_eval("987654321") 362880 >>> str_eval("22222222") 256 """ + product = 1 for digit in s: product *= int(digit) @@ -67,12 +71,11 @@ def str_eval(s: str) -> int: def solution(n: str = N) -> int: - """Find the thirteen adjacent digits in the 1000-digit number n that have + """ + Find the thirteen adjacent digits in the 1000-digit number n that have the greatest product and returns it. - - >>> solution(N) - 23514624000 """ + largest_product = -sys.maxsize - 1 substr = n[:13] cur_index = 13 @@ -88,4 +91,4 @@ def solution(n: str = N) -> int: if __name__ == "__main__": - print(solution(N)) + print(f"{solution() = }") diff --git a/project_euler/problem_009/sol1.py b/project_euler/problem_009/sol1.py index 1ab3376cae33..a58ea943e48b 100644 --- a/project_euler/problem_009/sol1.py +++ b/project_euler/problem_009/sol1.py @@ -1,26 +1,35 @@ """ -Problem 9: https://projecteuler.net/problem=9 +Project Euler Problem 9: https://projecteuler.net/problem=9 + +Special Pythagorean triplet A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + a^2 + b^2 = c^2 + For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. There exists exactly one Pythagorean triplet for which a + b + c = 1000. -Find the product abc. +Find the product a*b*c. + +References: + - https://en.wikipedia.org/wiki/Pythagorean_triple """ def solution() -> int: """ - Returns the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - 1. a < b < c - 2. a**2 + b**2 = c**2 - 3. a + b + c = 1000 + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 + # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 31875000 """ + for a in range(300): for b in range(400): for c in range(500): @@ -32,16 +41,17 @@ def solution() -> int: def solution_fast() -> int: """ - Returns the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - 1. a < b < c - 2. a**2 + b**2 = c**2 - 3. a + b + c = 1000 + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = 1000 # The code below has been commented due to slow execution affecting Travis. # >>> solution_fast() # 31875000 """ + for a in range(300): for b in range(400): c = 1000 - a - b @@ -66,4 +76,4 @@ def benchmark() -> None: if __name__ == "__main__": - benchmark() + print(f"{solution() = }") diff --git a/project_euler/problem_009/sol2.py b/project_euler/problem_009/sol2.py index e22ed45e8644..722ad522ee45 100644 --- a/project_euler/problem_009/sol2.py +++ b/project_euler/problem_009/sol2.py @@ -1,30 +1,40 @@ """ -Problem 9: https://projecteuler.net/problem=9 +Project Euler Problem 9: https://projecteuler.net/problem=9 + +Special Pythagorean triplet A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, + a^2 + b^2 = c^2 + For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. There exists exactly one Pythagorean triplet for which a + b + c = 1000. -Find the product abc. +Find the product a*b*c. + +References: + - https://en.wikipedia.org/wiki/Pythagorean_triple """ def solution(n: int = 1000) -> int: """ - Return the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - 1. a < b < c - 2. a**2 + b**2 = c**2 - 3. a + b + c = n - - >>> solution(1000) - 31875000 + Return the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a < b < c + 2. a**2 + b**2 = c**2 + 3. a + b + c = n + + >>> solution(36) + 1620 + >>> solution(126) + 66780 """ + product = -1 candidate = 0 for a in range(1, n // 3): - """Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c""" + # Solving the two equations a**2+b**2=c**2 and a+b+c=N eliminating c b = (n * n - 2 * a * n) // (2 * n - 2 * a) c = n - a - b if c * c == (a * a + b * b): @@ -35,4 +45,4 @@ def solution(n: int = 1000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_009/sol3.py b/project_euler/problem_009/sol3.py index 0900a76e6c56..03aed4b70761 100644 --- a/project_euler/problem_009/sol3.py +++ b/project_euler/problem_009/sol3.py @@ -1,5 +1,7 @@ """ -Problem 9: https://projecteuler.net/problem=9 +Project Euler Problem 9: https://projecteuler.net/problem=9 + +Special Pythagorean triplet A Pythagorean triplet is a set of three natural numbers, a < b < c, for which, @@ -8,22 +10,25 @@ For example, 3^2 + 4^2 = 9 + 16 = 25 = 5^2. There exists exactly one Pythagorean triplet for which a + b + c = 1000. -Find the product abc. +Find the product a*b*c. + +References: + - https://en.wikipedia.org/wiki/Pythagorean_triple """ def solution() -> int: """ - Returns the product of a,b,c which are Pythagorean Triplet that satisfies - the following: - - 1. a**2 + b**2 = c**2 - 2. a + b + c = 1000 + Returns the product of a,b,c which are Pythagorean Triplet that satisfies + the following: + 1. a**2 + b**2 = c**2 + 2. a + b + c = 1000 # The code below has been commented due to slow execution affecting Travis. # >>> solution() # 31875000 """ + return [ a * b * (1000 - a - b) for a in range(1, 999) @@ -33,4 +38,4 @@ def solution() -> int: if __name__ == "__main__": - print(solution()) + print(f"{solution() = }") diff --git a/project_euler/problem_010/sol1.py b/project_euler/problem_010/sol1.py index 4f3b3a4a42f5..bd49b3523c97 100644 --- a/project_euler/problem_010/sol1.py +++ b/project_euler/problem_010/sol1.py @@ -1,16 +1,23 @@ """ -https://projecteuler.net/problem=10 +Project Euler Problem 10: https://projecteuler.net/problem=10 + +Summation of primes -Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. + +References: + - https://en.wikipedia.org/wiki/Prime_number """ + from math import sqrt def is_prime(n: int) -> bool: - """Returns boolean representing primality of given number num. + """ + Returns boolean representing primality of given number num. + >>> is_prime(2) True >>> is_prime(3) @@ -20,6 +27,7 @@ def is_prime(n: int) -> bool: >>> is_prime(2999) True """ + for i in range(2, int(sqrt(n)) + 1): if n % i == 0: return False @@ -28,11 +36,9 @@ def is_prime(n: int) -> bool: def solution(n: int = 2000000) -> int: - """Returns the sum of all the primes below n. + """ + Returns the sum of all the primes below n. - # The code below has been commented due to slow execution affecting Travis. - # >>> solution(2000000) - # 142913828922 >>> solution(1000) 76127 >>> solution(5000) @@ -42,6 +48,7 @@ def solution(n: int = 2000000) -> int: >>> solution(7) 10 """ + if n > 2: sum_of_primes = 2 else: @@ -55,4 +62,4 @@ def solution(n: int = 2000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_010/sol2.py b/project_euler/problem_010/sol2.py index 39f5f5604053..3a2f485dde50 100644 --- a/project_euler/problem_010/sol2.py +++ b/project_euler/problem_010/sol2.py @@ -1,10 +1,14 @@ """ -https://projecteuler.net/problem=10 +Project Euler Problem 10: https://projecteuler.net/problem=10 + +Summation of primes -Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. + +References: + - https://en.wikipedia.org/wiki/Prime_number """ import math from itertools import takewhile @@ -12,7 +16,9 @@ def is_prime(number: int) -> bool: - """Returns boolean representing primality of given number num. + """ + Returns boolean representing primality of given number num. + >>> is_prime(2) True >>> is_prime(3) @@ -22,12 +28,17 @@ def is_prime(number: int) -> bool: >>> is_prime(2999) True """ + if number % 2 == 0 and number > 2: return False return all(number % i for i in range(3, int(math.sqrt(number)) + 1, 2)) def prime_generator() -> Iterator[int]: + """ + Generate a list sequence of prime numbers + """ + num = 2 while True: if is_prime(num): @@ -36,11 +47,9 @@ def prime_generator() -> Iterator[int]: def solution(n: int = 2000000) -> int: - """Returns the sum of all the primes below n. + """ + Returns the sum of all the primes below n. - # The code below has been commented due to slow execution affecting Travis. - # >>> solution(2000000) - # 142913828922 >>> solution(1000) 76127 >>> solution(5000) @@ -50,8 +59,9 @@ def solution(n: int = 2000000) -> int: >>> solution(7) 10 """ + return sum(takewhile(lambda x: x < n, prime_generator())) if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_010/sol3.py b/project_euler/problem_010/sol3.py index ef895f546fa5..f49d9393c7af 100644 --- a/project_euler/problem_010/sol3.py +++ b/project_euler/problem_010/sol3.py @@ -1,43 +1,47 @@ """ -https://projecteuler.net/problem=10 +Project Euler Problem 10: https://projecteuler.net/problem=10 + +Summation of primes -Problem Statement: The sum of the primes below 10 is 2 + 3 + 5 + 7 = 17. Find the sum of all the primes below two million. + +References: + - https://en.wikipedia.org/wiki/Prime_number + - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes """ def solution(n: int = 2000000) -> int: - """Returns the sum of all the primes below n using Sieve of Eratosthenes: + """ + Returns the sum of all the primes below n using Sieve of Eratosthenes: - https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes The sieve of Eratosthenes is one of the most efficient ways to find all primes smaller than n when n is smaller than 10 million. Only for positive numbers. - >>> solution(2_000_000) - 142913828922 - >>> solution(1_000) + >>> solution(1000) 76127 - >>> solution(5_000) + >>> solution(5000) 1548136 - >>> solution(10_000) + >>> solution(10000) 5736396 >>> solution(7) 10 - >>> solution(7.1) # doctest: +ELLIPSIS + >>> solution(7.1) # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: 'float' object cannot be interpreted as an integer - >>> solution(-7) # doctest: +ELLIPSIS + >>> solution(-7) # doctest: +ELLIPSIS Traceback (most recent call last): ... IndexError: list assignment index out of range - >>> solution("seven") # doctest: +ELLIPSIS + >>> solution("seven") # doctest: +ELLIPSIS Traceback (most recent call last): ... TypeError: can only concatenate str (not "int") to str """ + primality_list = [0 for i in range(n + 1)] primality_list[0] = 1 primality_list[1] = 1 @@ -54,4 +58,4 @@ def solution(n: int = 2000000) -> int: if __name__ == "__main__": - print(solution(int(input().strip()))) + print(f"{solution() = }") diff --git a/project_euler/problem_013/sol1.py b/project_euler/problem_013/sol1.py index 19b427337c3d..1ea08b12ee93 100644 --- a/project_euler/problem_013/sol1.py +++ b/project_euler/problem_013/sol1.py @@ -17,7 +17,7 @@ def solution(): '5537376230' """ file_path = os.path.join(os.path.dirname(__file__), "num.txt") - with open(file_path, "r") as file_hand: + with open(file_path) as file_hand: return str(sum([int(line) for line in file_hand]))[:10] diff --git a/project_euler/problem_018/solution.py b/project_euler/problem_018/solution.py index 38593813901e..82fc3ce3c9db 100644 --- a/project_euler/problem_018/solution.py +++ b/project_euler/problem_018/solution.py @@ -41,7 +41,7 @@ def solution(): script_dir = os.path.dirname(os.path.realpath(__file__)) triangle = os.path.join(script_dir, "triangle.txt") - with open(triangle, "r") as f: + with open(triangle) as f: triangle = f.readlines() a = [[int(y) for y in x.rstrip("\r\n").split(" ")] for x in triangle] diff --git a/project_euler/problem_038/__init__.py b/project_euler/problem_038/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_038/sol1.py b/project_euler/problem_038/sol1.py new file mode 100644 index 000000000000..6d54f6df7ff8 --- /dev/null +++ b/project_euler/problem_038/sol1.py @@ -0,0 +1,77 @@ +""" +Project Euler Problem 38: https://projecteuler.net/problem=38 + +Take the number 192 and multiply it by each of 1, 2, and 3: + +192 × 1 = 192 +192 × 2 = 384 +192 × 3 = 576 + +By concatenating each product we get the 1 to 9 pandigital, 192384576. We will call +192384576 the concatenated product of 192 and (1,2,3) + +The same can be achieved by starting with 9 and multiplying by 1, 2, 3, 4, and 5, +giving the pandigital, 918273645, which is the concatenated product of 9 and +(1,2,3,4,5). + +What is the largest 1 to 9 pandigital 9-digit number that can be formed as the +concatenated product of an integer with (1,2, ... , n) where n > 1? + +Solution: +Since n>1, the largest candidate for the solution will be a concactenation of +a 4-digit number and its double, a 5-digit number. +Let a be the 4-digit number. +a has 4 digits => 1000 <= a < 10000 +2a has 5 digits => 10000 <= 2a < 100000 +=> 5000 <= a < 10000 + +The concatenation of a with 2a = a * 10^5 + 2a +so our candidate for a given a is 100002 * a. +We iterate through the search space 5000 <= a < 10000 in reverse order, +calculating the candidates for each a and checking if they are 1-9 pandigital. + +In case there are no 4-digit numbers that satisfy this property, we check +the 3-digit numbers with a similar formula (the example a=192 gives a lower +bound on the length of a): +a has 3 digits, etc... +=> 100 <= a < 334, candidate = a * 10^6 + 2a * 10^3 + 3a + = 1002003 * a +""" + +from typing import Union + + +def is_9_pandigital(n: int) -> bool: + """ + Checks whether n is a 9-digit 1 to 9 pandigital number. + >>> is_9_pandigital(12345) + False + >>> is_9_pandigital(156284973) + True + >>> is_9_pandigital(1562849733) + False + """ + s = str(n) + return len(s) == 9 and set(s) == set("123456789") + + +def solution() -> Union[int, None]: + """ + Return the largest 1 to 9 pandigital 9-digital number that can be formed as the + concatenated product of an integer with (1,2,...,n) where n > 1. + """ + for base_num in range(9999, 4999, -1): + candidate = 100002 * base_num + if is_9_pandigital(candidate): + return candidate + + for base_num in range(333, 99, -1): + candidate = 1002003 * base_num + if is_9_pandigital(candidate): + return candidate + + return None + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_042/solution42.py b/project_euler/problem_042/solution42.py index 1e9bb49c7a06..b3aecf4cf144 100644 --- a/project_euler/problem_042/solution42.py +++ b/project_euler/problem_042/solution42.py @@ -30,7 +30,7 @@ def solution(): wordsFilePath = os.path.join(script_dir, "words.txt") words = "" - with open(wordsFilePath, "r") as f: + with open(wordsFilePath) as f: words = f.readline() words = list(map(lambda word: word.strip('"'), words.strip("\r\n").split(","))) diff --git a/project_euler/problem_049/sol1.py b/project_euler/problem_049/sol1.py index 6c3d69ad0d11..c0d0715be91c 100644 --- a/project_euler/problem_049/sol1.py +++ b/project_euler/problem_049/sol1.py @@ -114,7 +114,7 @@ def solution(): if ( abs(candidate[i] - candidate[j]) == abs(candidate[j] - candidate[k]) - and len(set([candidate[i], candidate[j], candidate[k]])) == 3 + and len({candidate[i], candidate[j], candidate[k]}) == 3 ): passed.append( sorted([candidate[i], candidate[j], candidate[k]]) diff --git a/project_euler/problem_050/__init__.py b/project_euler/problem_050/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_050/sol1.py b/project_euler/problem_050/sol1.py new file mode 100644 index 000000000000..7d142e5ffc91 --- /dev/null +++ b/project_euler/problem_050/sol1.py @@ -0,0 +1,85 @@ +""" +Project Euler Problem 50: https://projecteuler.net/problem=50 + +Consecutive prime sum + +The prime 41, can be written as the sum of six consecutive primes: +41 = 2 + 3 + 5 + 7 + 11 + 13 + +This is the longest sum of consecutive primes that adds to a prime below +one-hundred. + +The longest sum of consecutive primes below one-thousand that adds to a prime, +contains 21 terms, and is equal to 953. + +Which prime, below one-million, can be written as the sum of the most +consecutive primes? +""" +from typing import List + + +def prime_sieve(limit: int) -> List[int]: + """ + Sieve of Erotosthenes + Function to return all the prime numbers up to a number 'limit' + https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes + + >>> prime_sieve(3) + [2] + + >>> prime_sieve(50) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] + """ + is_prime = [True] * limit + is_prime[0] = False + is_prime[1] = False + is_prime[2] = True + + for i in range(3, int(limit ** 0.5 + 1), 2): + index = i * 2 + while index < limit: + is_prime[index] = False + index = index + i + + primes = [2] + + for i in range(3, limit, 2): + if is_prime[i]: + primes.append(i) + + return primes + + +def solution(ceiling: int = 1_000_000) -> int: + """ + Returns the biggest prime, below the celing, that can be written as the sum + of consecutive the most consecutive primes. + + >>> solution(500) + 499 + + >>> solution(1_000) + 953 + + >>> solution(10_000) + 9521 + """ + primes = prime_sieve(ceiling) + length = 0 + largest = 0 + + for i in range(len(primes)): + for j in range(i + length, len(primes)): + sol = sum(primes[i:j]) + if sol >= ceiling: + break + + if sol in primes: + length = j - i + largest = sol + + return largest + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_054/sol1.py b/project_euler/problem_054/sol1.py index 4d75271784de..d2fd810d1b69 100644 --- a/project_euler/problem_054/sol1.py +++ b/project_euler/problem_054/sol1.py @@ -45,7 +45,7 @@ import os -class PokerHand(object): +class PokerHand: """Create an object representing a Poker Hand based on an input of a string which represents the best 5 card combination from the player's hand and board cards. @@ -366,7 +366,7 @@ def solution() -> int: answer = 0 script_dir = os.path.abspath(os.path.dirname(__file__)) poker_hands = os.path.join(script_dir, "poker_hands.txt") - with open(poker_hands, "r") as file_hand: + with open(poker_hands) as file_hand: for line in file_hand: player_hand = line[:14].strip() opponent_hand = line[15:].strip() diff --git a/project_euler/problem_054/test_poker_hand.py b/project_euler/problem_054/test_poker_hand.py index f60c3aba6616..96317fc7df33 100644 --- a/project_euler/problem_054/test_poker_hand.py +++ b/project_euler/problem_054/test_poker_hand.py @@ -217,7 +217,7 @@ def test_euler_project(): answer = 0 script_dir = os.path.abspath(os.path.dirname(__file__)) poker_hands = os.path.join(script_dir, "poker_hands.txt") - with open(poker_hands, "r") as file_hand: + with open(poker_hands) as file_hand: for line in file_hand: player_hand = line[:14].strip() opponent_hand = line[15:].strip() diff --git a/project_euler/problem_057/__init__.py b/project_euler/problem_057/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_057/sol1.py b/project_euler/problem_057/sol1.py new file mode 100644 index 000000000000..04b6199f4717 --- /dev/null +++ b/project_euler/problem_057/sol1.py @@ -0,0 +1,48 @@ +""" +Project Euler Problem 57: https://projecteuler.net/problem=57 +It is possible to show that the square root of two can be expressed as an infinite +continued fraction. + +sqrt(2) = 1 + 1 / (2 + 1 / (2 + 1 / (2 + ...))) + +By expanding this for the first four iterations, we get: +1 + 1 / 2 = 3 / 2 = 1.5 +1 + 1 / (2 + 1 / 2} = 7 / 5 = 1.4 +1 + 1 / (2 + 1 / (2 + 1 / 2)) = 17 / 12 = 1.41666... +1 + 1 / (2 + 1 / (2 + 1 / (2 + 1 / 2))) = 41/ 29 = 1.41379... + +The next three expansions are 99/70, 239/169, and 577/408, but the eighth expansion, +1393/985, is the first example where the number of digits in the numerator exceeds +the number of digits in the denominator. + +In the first one-thousand expansions, how many fractions contain a numerator with +more digits than the denominator? +""" + + +def solution(n: int = 1000) -> int: + """ + returns number of fractions containing a numerator with more digits than + the denominator in the first n expansions. + >>> solution(14) + 2 + >>> solution(100) + 15 + >>> solution(10000) + 1508 + """ + prev_numerator, prev_denominator = 1, 1 + result = [] + for i in range(1, n + 1): + numerator = prev_numerator + 2 * prev_denominator + denominator = prev_numerator + prev_denominator + if len(str(numerator)) > len(str(denominator)): + result.append(i) + prev_numerator = numerator + prev_denominator = denominator + + return len(result) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_058/__init__.py b/project_euler/problem_058/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_058/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_058/sol1.py b/project_euler/problem_058/sol1.py new file mode 100644 index 000000000000..d3b15157fbbd --- /dev/null +++ b/project_euler/problem_058/sol1.py @@ -0,0 +1,86 @@ +""" +Project Euler Problem 58:https://projecteuler.net/problem=58 + + +Starting with 1 and spiralling anticlockwise in the following way, +a square spiral with side length 7 is formed. + +37 36 35 34 33 32 31 +38 17 16 15 14 13 30 +39 18 5 4 3 12 29 +40 19 6 1 2 11 28 +41 20 7 8 9 10 27 +42 21 22 23 24 25 26 +43 44 45 46 47 48 49 + +It is interesting to note that the odd squares lie along the bottom right +diagonal ,but what is more interesting is that 8 out of the 13 numbers +lying along both diagonals are prime; that is, a ratio of 8/13 ≈ 62%. + +If one complete new layer is wrapped around the spiral above, +a square spiral with side length 9 will be formed. +If this process is continued, +what is the side length of the square spiral for which +the ratio of primes along both diagonals first falls below 10%? + +Solution: We have to find an odd length side for which square falls below +10%. With every layer we add 4 elements are being added to the diagonals +,lets say we have a square spiral of odd length with side length j, +then if we move from j to j+2, we are adding j*j+j+1,j*j+2*(j+1),j*j+3*(j+1) +j*j+4*(j+1). Out of these 4 only the first three can become prime +because last one reduces to (j+2)*(j+2). +So we check individually each one of these before incrementing our +count of current primes. + +""" + + +def isprime(d: int) -> int: + """ + returns whether the given digit is prime or not + >>> isprime(1) + 0 + >>> isprime(17) + 1 + >>> isprime(10000) + 0 + """ + if d == 1: + return 0 + + i = 2 + while i * i <= d: + if d % i == 0: + return 0 + i = i + 1 + return 1 + + +def solution(ratio: float = 0.1) -> int: + """ + returns the side length of the square spiral of odd length greater + than 1 for which the ratio of primes along both diagonals + first falls below the given ratio. + >>> solution(.5) + 11 + >>> solution(.2) + 309 + >>> solution(.111) + 11317 + """ + + j = 3 + primes = 3 + + while primes / (2 * j - 1) >= ratio: + for i in range(j * j + j + 1, (j + 2) * (j + 2), j + 1): + primes = primes + isprime(i) + + j = j + 2 + return j + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/project_euler/problem_059/__init__.py b/project_euler/problem_059/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_059/p059_cipher.txt b/project_euler/problem_059/p059_cipher.txt new file mode 100644 index 000000000000..b3b3247298d1 --- /dev/null +++ b/project_euler/problem_059/p059_cipher.txt @@ -0,0 +1 @@ +36,22,80,0,0,4,23,25,19,17,88,4,4,19,21,11,88,22,23,23,29,69,12,24,0,88,25,11,12,2,10,28,5,6,12,25,10,22,80,10,30,80,10,22,21,69,23,22,69,61,5,9,29,2,66,11,80,8,23,3,17,88,19,0,20,21,7,10,17,17,29,20,69,8,17,21,29,2,22,84,80,71,60,21,69,11,5,8,21,25,22,88,3,0,10,25,0,10,5,8,88,2,0,27,25,21,10,31,6,25,2,16,21,82,69,35,63,11,88,4,13,29,80,22,13,29,22,88,31,3,88,3,0,10,25,0,11,80,10,30,80,23,29,19,12,8,2,10,27,17,9,11,45,95,88,57,69,16,17,19,29,80,23,29,19,0,22,4,9,1,80,3,23,5,11,28,92,69,9,5,12,12,21,69,13,30,0,0,0,0,27,4,0,28,28,28,84,80,4,22,80,0,20,21,2,25,30,17,88,21,29,8,2,0,11,3,12,23,30,69,30,31,23,88,4,13,29,80,0,22,4,12,10,21,69,11,5,8,88,31,3,88,4,13,17,3,69,11,21,23,17,21,22,88,65,69,83,80,84,87,68,69,83,80,84,87,73,69,83,80,84,87,65,83,88,91,69,29,4,6,86,92,69,15,24,12,27,24,69,28,21,21,29,30,1,11,80,10,22,80,17,16,21,69,9,5,4,28,2,4,12,5,23,29,80,10,30,80,17,16,21,69,27,25,23,27,28,0,84,80,22,23,80,17,16,17,17,88,25,3,88,4,13,29,80,17,10,5,0,88,3,16,21,80,10,30,80,17,16,25,22,88,3,0,10,25,0,11,80,12,11,80,10,26,4,4,17,30,0,28,92,69,30,2,10,21,80,12,12,80,4,12,80,10,22,19,0,88,4,13,29,80,20,13,17,1,10,17,17,13,2,0,88,31,3,88,4,13,29,80,6,17,2,6,20,21,69,30,31,9,20,31,18,11,94,69,54,17,8,29,28,28,84,80,44,88,24,4,14,21,69,30,31,16,22,20,69,12,24,4,12,80,17,16,21,69,11,5,8,88,31,3,88,4,13,17,3,69,11,21,23,17,21,22,88,25,22,88,17,69,11,25,29,12,24,69,8,17,23,12,80,10,30,80,17,16,21,69,11,1,16,25,2,0,88,31,3,88,4,13,29,80,21,29,2,12,21,21,17,29,2,69,23,22,69,12,24,0,88,19,12,10,19,9,29,80,18,16,31,22,29,80,1,17,17,8,29,4,0,10,80,12,11,80,84,67,80,10,10,80,7,1,80,21,13,4,17,17,30,2,88,4,13,29,80,22,13,29,69,23,22,69,12,24,12,11,80,22,29,2,12,29,3,69,29,1,16,25,28,69,12,31,69,11,92,69,17,4,69,16,17,22,88,4,13,29,80,23,25,4,12,23,80,22,9,2,17,80,70,76,88,29,16,20,4,12,8,28,12,29,20,69,26,9,69,11,80,17,23,80,84,88,31,3,88,4,13,29,80,21,29,2,12,21,21,17,29,2,69,12,31,69,12,24,0,88,20,12,25,29,0,12,21,23,86,80,44,88,7,12,20,28,69,11,31,10,22,80,22,16,31,18,88,4,13,25,4,69,12,24,0,88,3,16,21,80,10,30,80,17,16,25,22,88,3,0,10,25,0,11,80,17,23,80,7,29,80,4,8,0,23,23,8,12,21,17,17,29,28,28,88,65,75,78,68,81,65,67,81,72,70,83,64,68,87,74,70,81,75,70,81,67,80,4,22,20,69,30,2,10,21,80,8,13,28,17,17,0,9,1,25,11,31,80,17,16,25,22,88,30,16,21,18,0,10,80,7,1,80,22,17,8,73,88,17,11,28,80,17,16,21,11,88,4,4,19,25,11,31,80,17,16,21,69,11,1,16,25,2,0,88,2,10,23,4,73,88,4,13,29,80,11,13,29,7,29,2,69,75,94,84,76,65,80,65,66,83,77,67,80,64,73,82,65,67,87,75,72,69,17,3,69,17,30,1,29,21,1,88,0,23,23,20,16,27,21,1,84,80,18,16,25,6,16,80,0,0,0,23,29,3,22,29,3,69,12,24,0,88,0,0,10,25,8,29,4,0,10,80,10,30,80,4,88,19,12,10,19,9,29,80,18,16,31,22,29,80,1,17,17,8,29,4,0,10,80,12,11,80,84,86,80,35,23,28,9,23,7,12,22,23,69,25,23,4,17,30,69,12,24,0,88,3,4,21,21,69,11,4,0,8,3,69,26,9,69,15,24,12,27,24,69,49,80,13,25,20,69,25,2,23,17,6,0,28,80,4,12,80,17,16,25,22,88,3,16,21,92,69,49,80,13,25,6,0,88,20,12,11,19,10,14,21,23,29,20,69,12,24,4,12,80,17,16,21,69,11,5,8,88,31,3,88,4,13,29,80,22,29,2,12,29,3,69,73,80,78,88,65,74,73,70,69,83,80,84,87,72,84,88,91,69,73,95,87,77,70,69,83,80,84,87,70,87,77,80,78,88,21,17,27,94,69,25,28,22,23,80,1,29,0,0,22,20,22,88,31,11,88,4,13,29,80,20,13,17,1,10,17,17,13,2,0,88,31,3,88,4,13,29,80,6,17,2,6,20,21,75,88,62,4,21,21,9,1,92,69,12,24,0,88,3,16,21,80,10,30,80,17,16,25,22,88,29,16,20,4,12,8,28,12,29,20,69,26,9,69,65,64,69,31,25,19,29,3,69,12,24,0,88,18,12,9,5,4,28,2,4,12,21,69,80,22,10,13,2,17,16,80,21,23,7,0,10,89,69,23,22,69,12,24,0,88,19,12,10,19,16,21,22,0,10,21,11,27,21,69,23,22,69,12,24,0,88,0,0,10,25,8,29,4,0,10,80,10,30,80,4,88,19,12,10,19,9,29,80,18,16,31,22,29,80,1,17,17,8,29,4,0,10,80,12,11,80,84,86,80,36,22,20,69,26,9,69,11,25,8,17,28,4,10,80,23,29,17,22,23,30,12,22,23,69,49,80,13,25,6,0,88,28,12,19,21,18,17,3,0,88,18,0,29,30,69,25,18,9,29,80,17,23,80,1,29,4,0,10,29,12,22,21,69,12,24,0,88,3,16,21,3,69,23,22,69,12,24,0,88,3,16,26,3,0,9,5,0,22,4,69,11,21,23,17,21,22,88,25,11,88,7,13,17,19,13,88,4,13,29,80,0,0,0,10,22,21,11,12,3,69,25,2,0,88,21,19,29,30,69,22,5,8,26,21,23,11,94 \ No newline at end of file diff --git a/project_euler/problem_059/sol1.py b/project_euler/problem_059/sol1.py new file mode 100644 index 000000000000..1f55029b2613 --- /dev/null +++ b/project_euler/problem_059/sol1.py @@ -0,0 +1,128 @@ +""" +Each character on a computer is assigned a unique code and the preferred standard is +ASCII (American Standard Code for Information Interchange). +For example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107. + +A modern encryption method is to take a text file, convert the bytes to ASCII, then +XOR each byte with a given value, taken from a secret key. The advantage with the +XOR function is that using the same encryption key on the cipher text, restores +the plain text; for example, 65 XOR 42 = 107, then 107 XOR 42 = 65. + +For unbreakable encryption, the key is the same length as the plain text message, and +the key is made up of random bytes. The user would keep the encrypted message and the +encryption key in different locations, and without both "halves", it is impossible to +decrypt the message. + +Unfortunately, this method is impractical for most users, so the modified method is +to use a password as a key. If the password is shorter than the message, which is +likely, the key is repeated cyclically throughout the message. The balance for this +method is using a sufficiently long password key for security, but short enough to +be memorable. + +Your task has been made easy, as the encryption key consists of three lower case +characters. Using p059_cipher.txt (right click and 'Save Link/Target As...'), a +file containing the encrypted ASCII codes, and the knowledge that the plain text +must contain common English words, decrypt the message and find the sum of the ASCII +values in the original text. +""" + + +import string +from itertools import cycle, product +from pathlib import Path +from typing import List, Optional, Set, Tuple + +VALID_CHARS: str = ( + string.ascii_letters + string.digits + string.punctuation + string.whitespace +) +LOWERCASE_INTS: List[int] = [ord(letter) for letter in string.ascii_lowercase] +VALID_INTS: Set[int] = {ord(char) for char in VALID_CHARS} + +COMMON_WORDS: List[str] = ["the", "be", "to", "of", "and", "in", "that", "have"] + + +def try_key(ciphertext: List[int], key: Tuple[int, ...]) -> Optional[str]: + """ + Given an encrypted message and a possible 3-character key, decrypt the message. + If the decrypted message contains a invalid character, i.e. not an ASCII letter, + a digit, punctuation or whitespace, then we know the key is incorrect, so return + None. + >>> try_key([0, 17, 20, 4, 27], (104, 116, 120)) + 'hello' + >>> try_key([68, 10, 300, 4, 27], (104, 116, 120)) is None + True + """ + decoded: str = "" + keychar: int + cipherchar: int + decodedchar: int + + for keychar, cipherchar in zip(cycle(key), ciphertext): + decodedchar = cipherchar ^ keychar + if decodedchar not in VALID_INTS: + return None + decoded += chr(decodedchar) + + return decoded + + +def filter_valid_chars(ciphertext: List[int]) -> List[str]: + """ + Given an encrypted message, test all 3-character strings to try and find the + key. Return a list of the possible decrypted messages. + >>> from itertools import cycle + >>> text = "The enemy's gate is down" + >>> key = "end" + >>> encoded = [ord(k) ^ ord(c) for k,c in zip(cycle(key), text)] + >>> text in filter_valid_chars(encoded) + True + """ + possibles: List[str] = [] + for key in product(LOWERCASE_INTS, repeat=3): + encoded = try_key(ciphertext, key) + if encoded is not None: + possibles.append(encoded) + return possibles + + +def filter_common_word(possibles: List[str], common_word: str) -> List[str]: + """ + Given a list of possible decoded messages, narrow down the possibilities + for checking for the presence of a specified common word. Only decoded messages + containing common_word will be returned. + >>> filter_common_word(['asfla adf', 'I am here', ' !?! #a'], 'am') + ['I am here'] + >>> filter_common_word(['athla amf', 'I am here', ' !?! #a'], 'am') + ['athla amf', 'I am here'] + """ + return [possible for possible in possibles if common_word in possible.lower()] + + +def solution(filename: str = "p059_cipher.txt") -> int: + """ + Test the ciphertext against all possible 3-character keys, then narrow down the + possibilities by filtering using common words until there's only one possible + decoded message. + >>> solution("test_cipher.txt") + 3000 + """ + ciphertext: List[int] + possibles: List[str] + common_word: str + decoded_text: str + data: str = Path(__file__).parent.joinpath(filename).read_text(encoding="utf-8") + + ciphertext = [int(number) for number in data.strip().split(",")] + + possibles = filter_valid_chars(ciphertext) + for common_word in COMMON_WORDS: + possibles = filter_common_word(possibles, common_word) + if len(possibles) == 1: + break + + decoded_text = possibles[0] + return sum([ord(char) for char in decoded_text]) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_059/test_cipher.txt b/project_euler/problem_059/test_cipher.txt new file mode 100644 index 000000000000..27c53740cc1a --- /dev/null +++ b/project_euler/problem_059/test_cipher.txt @@ -0,0 +1 @@ +63,13,28,75,0,23,14,8,0,76,22,89,12,4,13,14,69,16,24,69,29,4,18,23,69,69,59,14,69,11,14,4,29,18 diff --git a/project_euler/problem_063/sol1.py b/project_euler/problem_063/sol1.py index f6a8d3240ffd..29efddba4216 100644 --- a/project_euler/problem_063/sol1.py +++ b/project_euler/problem_063/sol1.py @@ -26,7 +26,7 @@ def solution(max_base: int = 10, max_power: int = 22) -> int: bases = range(1, max_base) powers = range(1, max_power) return sum( - 1 for power in powers for base in bases if len(str((base ** power))) == power + 1 for power in powers for base in bases if len(str(base ** power)) == power ) diff --git a/project_euler/problem_064/__init__.py b/project_euler/problem_064/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_064/sol1.py b/project_euler/problem_064/sol1.py new file mode 100644 index 000000000000..69e3f6d97580 --- /dev/null +++ b/project_euler/problem_064/sol1.py @@ -0,0 +1,77 @@ +""" +Project Euler Problem 64: https://projecteuler.net/problem=64 + +All square roots are periodic when written as continued fractions. +For example, let us consider sqrt(23). +It can be seen that the sequence is repeating. +For conciseness, we use the notation sqrt(23)=[4;(1,3,1,8)], +to indicate that the block (1,3,1,8) repeats indefinitely. +Exactly four continued fractions, for N<=13, have an odd period. +How many continued fractions for N<=10000 have an odd period? + +References: +- https://en.wikipedia.org/wiki/Continued_fraction +""" + +from math import floor, sqrt + + +def continuous_fraction_period(n: int) -> int: + """ + Returns the continued fraction period of a number n. + + >>> continuous_fraction_period(2) + 1 + >>> continuous_fraction_period(5) + 1 + >>> continuous_fraction_period(7) + 4 + >>> continuous_fraction_period(11) + 2 + >>> continuous_fraction_period(13) + 5 + """ + numerator = 0.0 + denominator = 1.0 + ROOT = int(sqrt(n)) + integer_part = ROOT + period = 0 + while integer_part != 2 * ROOT: + numerator = denominator * integer_part - numerator + denominator = (n - numerator ** 2) / denominator + integer_part = int((ROOT + numerator) / denominator) + period += 1 + return period + + +def solution(n: int = 10000) -> int: + """ + Returns the count of numbers <= 10000 with odd periods. + This function calls continuous_fraction_period for numbers which are + not perfect squares. + This is checked in if sr - floor(sr) != 0 statement. + If an odd period is returned by continuous_fraction_period, + count_odd_periods is increased by 1. + + >>> solution(2) + 1 + >>> solution(5) + 2 + >>> solution(7) + 2 + >>> solution(11) + 3 + >>> solution(13) + 4 + """ + count_odd_periods = 0 + for i in range(2, n + 1): + sr = sqrt(i) + if sr - floor(sr) != 0: + if continuous_fraction_period(i) % 2 == 1: + count_odd_periods += 1 + return count_odd_periods + + +if __name__ == "__main__": + print(f"{solution(int(input().strip()))}") diff --git a/project_euler/problem_065/__init__.py b/project_euler/problem_065/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_065/sol1.py b/project_euler/problem_065/sol1.py new file mode 100644 index 000000000000..229769a77d07 --- /dev/null +++ b/project_euler/problem_065/sol1.py @@ -0,0 +1,99 @@ +""" +Project Euler Problem 65: https://projecteuler.net/problem=65 + +The square root of 2 can be written as an infinite continued fraction. + +sqrt(2) = 1 + 1 / (2 + 1 / (2 + 1 / (2 + 1 / (2 + ...)))) + +The infinite continued fraction can be written, sqrt(2) = [1;(2)], (2) +indicates that 2 repeats ad infinitum. In a similar way, sqrt(23) = +[4;(1,3,1,8)]. + +It turns out that the sequence of partial values of continued +fractions for square roots provide the best rational approximations. +Let us consider the convergents for sqrt(2). + +1 + 1 / 2 = 3/2 +1 + 1 / (2 + 1 / 2) = 7/5 +1 + 1 / (2 + 1 / (2 + 1 / 2)) = 17/12 +1 + 1 / (2 + 1 / (2 + 1 / (2 + 1 / 2))) = 41/29 + +Hence the sequence of the first ten convergents for sqrt(2) are: +1, 3/2, 7/5, 17/12, 41/29, 99/70, 239/169, 577/408, 1393/985, 3363/2378, ... + +What is most surprising is that the important mathematical constant, +e = [2;1,2,1,1,4,1,1,6,1,...,1,2k,1,...]. + +The first ten terms in the sequence of convergents for e are: +2, 3, 8/3, 11/4, 19/7, 87/32, 106/39, 193/71, 1264/465, 1457/536, ... + +The sum of digits in the numerator of the 10th convergent is +1 + 4 + 5 + 7 = 17. + +Find the sum of the digits in the numerator of the 100th convergent +of the continued fraction for e. + +----- + +The solution mostly comes down to finding an equation that will generate +the numerator of the continued fraction. For the i-th numerator, the +pattern is: + +n_i = m_i * n_(i-1) + n_(i-2) + +for m_i = the i-th index of the continued fraction representation of e, +n_0 = 1, and n_1 = 2 as the first 2 numbers of the representation. + +For example: +n_9 = 6 * 193 + 106 = 1264 +1 + 2 + 6 + 4 = 13 + +n_10 = 1 * 193 + 1264 = 1457 +1 + 4 + 5 + 7 = 17 +""" + + +def sum_digits(num: int) -> int: + """ + Returns the sum of every digit in num. + + >>> sum_digits(1) + 1 + >>> sum_digits(12345) + 15 + >>> sum_digits(999001) + 28 + """ + digit_sum = 0 + while num > 0: + digit_sum += num % 10 + num //= 10 + return digit_sum + + +def solution(max: int = 100) -> int: + """ + Returns the sum of the digits in the numerator of the max-th convergent of + the continued fraction for e. + + >>> solution(9) + 13 + >>> solution(10) + 17 + >>> solution(50) + 91 + """ + pre_numerator = 1 + cur_numerator = 2 + + for i in range(2, max + 1): + temp = pre_numerator + e_cont = 2 * i // 3 if i % 3 == 0 else 1 + pre_numerator = cur_numerator + cur_numerator = e_cont * pre_numerator + temp + + return sum_digits(cur_numerator) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_067/sol1.py b/project_euler/problem_067/sol1.py index 9494ff7bbabd..ebfa865a9479 100644 --- a/project_euler/problem_067/sol1.py +++ b/project_euler/problem_067/sol1.py @@ -25,7 +25,7 @@ def solution(): script_dir = os.path.dirname(os.path.realpath(__file__)) triangle = os.path.join(script_dir, "triangle.txt") - with open(triangle, "r") as f: + with open(triangle) as f: triangle = f.readlines() a = map(lambda x: x.rstrip("\r\n").split(" "), triangle) diff --git a/project_euler/problem_070/__init__.py b/project_euler/problem_070/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_070/sol1.py b/project_euler/problem_070/sol1.py new file mode 100644 index 000000000000..9d27119ba95c --- /dev/null +++ b/project_euler/problem_070/sol1.py @@ -0,0 +1,119 @@ +""" +Project Euler Problem 70: https://projecteuler.net/problem=70 + +Euler's Totient function, φ(n) [sometimes called the phi function], is used to +determine the number of positive numbers less than or equal to n which are +relatively prime to n. For example, as 1, 2, 4, 5, 7, and 8, are all less than +nine and relatively prime to nine, φ(9)=6. + +The number 1 is considered to be relatively prime to every positive number, so +φ(1)=1. + +Interestingly, φ(87109)=79180, and it can be seen that 87109 is a permutation +of 79180. + +Find the value of n, 1 < n < 10^7, for which φ(n) is a permutation of n and +the ratio n/φ(n) produces a minimum. + +----- + +This is essentially brute force. Calculate all totients up to 10^7 and +find the minimum ratio of n/φ(n) that way. To minimize the ratio, we want +to minimize n and maximize φ(n) as much as possible, so we can store the +minimum fraction's numerator and denominator and calculate new fractions +with each totient to compare against. To avoid dividing by zero, I opt to +use cross multiplication. + +References: +Finding totients +https://en.wikipedia.org/wiki/Euler's_totient_function#Euler's_product_formula +""" +from typing import List + + +def get_totients(max_one: int) -> List[int]: + """ + Calculates a list of totients from 0 to max_one exclusive, using the + definition of Euler's product formula. + + >>> get_totients(5) + [0, 1, 1, 2, 2] + + >>> get_totients(10) + [0, 1, 1, 2, 2, 4, 2, 6, 4, 6] + """ + totients = [0] * max_one + + for i in range(0, max_one): + totients[i] = i + + for i in range(2, max_one): + if totients[i] == i: + for j in range(i, max_one, i): + totients[j] -= totients[j] // i + + return totients + + +def has_same_digits(num1: int, num2: int) -> bool: + """ + Return True if num1 and num2 have the same frequency of every digit, False + otherwise. + + digits[] is a frequency table where the index represents the digit from + 0-9, and the element stores the number of appearances. Increment the + respective index every time you see the digit in num1, and decrement if in + num2. At the end, if the numbers have the same digits, every index must + contain 0. + + >>> has_same_digits(123456789, 987654321) + True + + >>> has_same_digits(123, 12) + False + + >>> has_same_digits(1234566, 123456) + False + """ + digits = [0] * 10 + + while num1 > 0 and num2 > 0: + digits[num1 % 10] += 1 + digits[num2 % 10] -= 1 + num1 //= 10 + num2 //= 10 + + for digit in digits: + if digit != 0: + return False + + return True + + +def solution(max: int = 10000000) -> int: + """ + Finds the value of n from 1 to max such that n/φ(n) produces a minimum. + + >>> solution(100) + 21 + + >>> solution(10000) + 4435 + """ + + min_numerator = 1 # i + min_denominator = 0 # φ(i) + totients = get_totients(max + 1) + + for i in range(2, max + 1): + t = totients[i] + + if i * min_denominator < min_numerator * t and has_same_digits(i, t): + min_numerator = i + min_denominator = t + + return min_numerator + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_074/sol2.py b/project_euler/problem_074/sol2.py new file mode 100644 index 000000000000..689593277a81 --- /dev/null +++ b/project_euler/problem_074/sol2.py @@ -0,0 +1,126 @@ +""" + Project Euler Problem 074: https://projecteuler.net/problem=74 + + Starting from any positive integer number + it is possible to attain another one summing the factorial of its digits. + + Repeating this step, we can build chains of numbers. + It is not difficult to prove that EVERY starting number + will eventually get stuck in a loop. + + The request is to find how many numbers less than one million + produce a chain with exactly 60 non repeating items. + + Solution approach: + This solution simply consists in a loop that generates + the chains of non repeating items. + The generation of the chain stops before a repeating item + or if the size of the chain is greater then the desired one. + After generating each chain, the length is checked and the + counter increases. +""" + +factorial_cache = {} +factorial_sum_cache = {} + + +def factorial(a: int) -> int: + """Returns the factorial of the input a + >>> factorial(5) + 120 + + >>> factorial(6) + 720 + + >>> factorial(0) + 1 + """ + + # The factorial function is not defined for negative numbers + if a < 0: + raise ValueError("Invalid negative input!", a) + + if a in factorial_cache: + return factorial_cache[a] + + # The case of 0! is handled separately + if a == 0: + factorial_cache[a] = 1 + else: + # use a temporary support variable to store the computation + temporary_number = a + temporary_computation = 1 + + while temporary_number > 0: + temporary_computation *= temporary_number + temporary_number -= 1 + + factorial_cache[a] = temporary_computation + return factorial_cache[a] + + +def factorial_sum(a: int) -> int: + """Function to perform the sum of the factorial + of all the digits in a + + >>> factorial_sum(69) + 363600 + """ + if a in factorial_sum_cache: + return factorial_sum_cache[a] + # Prepare a variable to hold the computation + fact_sum = 0 + + """ Convert a in string to iterate on its digits + convert the digit back into an int + and add its factorial to fact_sum. + """ + for i in str(a): + fact_sum += factorial(int(i)) + factorial_sum_cache[a] = fact_sum + return fact_sum + + +def solution(chain_length: int = 60, number_limit: int = 1000000) -> int: + """Returns the number of numbers that produce + chains with exactly 60 non repeating elements. + >>> solution(10, 1000) + 26 + """ + + # the counter for the chains with the exact desired length + chain_counter = 0 + + for i in range(1, number_limit + 1): + + # The temporary list will contain the elements of the chain + chain_set = {i} + len_chain_set = 1 + last_chain_element = i + + # The new element of the chain + new_chain_element = factorial_sum(last_chain_element) + + # Stop computing the chain when you find a repeating item + # or the length it greater then the desired one. + + while new_chain_element not in chain_set and len_chain_set <= chain_length: + chain_set.add(new_chain_element) + + len_chain_set += 1 + last_chain_element = new_chain_element + new_chain_element = factorial_sum(last_chain_element) + + # If the while exited because the chain list contains the exact amount + # of elements increase the counter + if len_chain_set == chain_length: + chain_counter += 1 + + return chain_counter + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + print(f"{solution()}") diff --git a/project_euler/problem_077/__init__.py b/project_euler/problem_077/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_077/sol1.py b/project_euler/problem_077/sol1.py new file mode 100644 index 000000000000..e92992a90ab3 --- /dev/null +++ b/project_euler/problem_077/sol1.py @@ -0,0 +1,81 @@ +""" +Project Euler Problem 77: https://projecteuler.net/problem=77 + +It is possible to write ten as the sum of primes in exactly five different ways: + +7 + 3 +5 + 5 +5 + 3 + 2 +3 + 3 + 2 + 2 +2 + 2 + 2 + 2 + 2 + +What is the first value which can be written as the sum of primes in over +five thousand different ways? +""" + +from functools import lru_cache +from math import ceil +from typing import Optional, Set + +NUM_PRIMES = 100 + +primes = set(range(3, NUM_PRIMES, 2)) +primes.add(2) +prime: int + +for prime in range(3, ceil(NUM_PRIMES ** 0.5), 2): + if prime not in primes: + continue + primes.difference_update(set(range(prime * prime, NUM_PRIMES, prime))) + + +@lru_cache(maxsize=100) +def partition(number_to_partition: int) -> Set[int]: + """ + Return a set of integers corresponding to unique prime partitions of n. + The unique prime partitions can be represented as unique prime decompositions, + e.g. (7+3) <-> 7*3 = 12, (3+3+2+2) = 3*3*2*2 = 36 + >>> partition(10) + {32, 36, 21, 25, 30} + >>> partition(15) + {192, 160, 105, 44, 112, 243, 180, 150, 216, 26, 125, 126} + >>> len(partition(20)) + 26 + """ + if number_to_partition < 0: + return set() + elif number_to_partition == 0: + return {1} + + ret: Set[int] = set() + prime: int + sub: int + + for prime in primes: + if prime > number_to_partition: + continue + for sub in partition(number_to_partition - prime): + ret.add(sub * prime) + + return ret + + +def solution(number_unique_partitions: int = 5000) -> Optional[int]: + """ + Return the smallest integer that can be written as the sum of primes in over + m unique ways. + >>> solution(4) + 10 + >>> solution(500) + 45 + >>> solution(1000) + 53 + """ + for number_to_partition in range(1, NUM_PRIMES): + if len(partition(number_to_partition)) > number_unique_partitions: + return number_to_partition + return None + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_081/__init__.py b/project_euler/problem_081/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_081/matrix.txt b/project_euler/problem_081/matrix.txt new file mode 100644 index 000000000000..f65322a7e541 --- /dev/null +++ b/project_euler/problem_081/matrix.txt @@ -0,0 +1,80 @@ +4445,2697,5115,718,2209,2212,654,4348,3079,6821,7668,3276,8874,4190,3785,2752,9473,7817,9137,496,7338,3434,7152,4355,4552,7917,7827,2460,2350,691,3514,5880,3145,7633,7199,3783,5066,7487,3285,1084,8985,760,872,8609,8051,1134,9536,5750,9716,9371,7619,5617,275,9721,2997,2698,1887,8825,6372,3014,2113,7122,7050,6775,5948,2758,1219,3539,348,7989,2735,9862,1263,8089,6401,9462,3168,2758,3748,5870 +1096,20,1318,7586,5167,2642,1443,5741,7621,7030,5526,4244,2348,4641,9827,2448,6918,5883,3737,300,7116,6531,567,5997,3971,6623,820,6148,3287,1874,7981,8424,7672,7575,6797,6717,1078,5008,4051,8795,5820,346,1851,6463,2117,6058,3407,8211,117,4822,1317,4377,4434,5925,8341,4800,1175,4173,690,8978,7470,1295,3799,8724,3509,9849,618,3320,7068,9633,2384,7175,544,6583,1908,9983,481,4187,9353,9377 +9607,7385,521,6084,1364,8983,7623,1585,6935,8551,2574,8267,4781,3834,2764,2084,2669,4656,9343,7709,2203,9328,8004,6192,5856,3555,2260,5118,6504,1839,9227,1259,9451,1388,7909,5733,6968,8519,9973,1663,5315,7571,3035,4325,4283,2304,6438,3815,9213,9806,9536,196,5542,6907,2475,1159,5820,9075,9470,2179,9248,1828,4592,9167,3713,4640,47,3637,309,7344,6955,346,378,9044,8635,7466,5036,9515,6385,9230 +7206,3114,7760,1094,6150,5182,7358,7387,4497,955,101,1478,7777,6966,7010,8417,6453,4955,3496,107,449,8271,131,2948,6185,784,5937,8001,6104,8282,4165,3642,710,2390,575,715,3089,6964,4217,192,5949,7006,715,3328,1152,66,8044,4319,1735,146,4818,5456,6451,4113,1063,4781,6799,602,1504,6245,6550,1417,1343,2363,3785,5448,4545,9371,5420,5068,4613,4882,4241,5043,7873,8042,8434,3939,9256,2187 +3620,8024,577,9997,7377,7682,1314,1158,6282,6310,1896,2509,5436,1732,9480,706,496,101,6232,7375,2207,2306,110,6772,3433,2878,8140,5933,8688,1399,2210,7332,6172,6403,7333,4044,2291,1790,2446,7390,8698,5723,3678,7104,1825,2040,140,3982,4905,4160,2200,5041,2512,1488,2268,1175,7588,8321,8078,7312,977,5257,8465,5068,3453,3096,1651,7906,253,9250,6021,8791,8109,6651,3412,345,4778,5152,4883,7505 +1074,5438,9008,2679,5397,5429,2652,3403,770,9188,4248,2493,4361,8327,9587,707,9525,5913,93,1899,328,2876,3604,673,8576,6908,7659,2544,3359,3883,5273,6587,3065,1749,3223,604,9925,6941,2823,8767,7039,3290,3214,1787,7904,3421,7137,9560,8451,2669,9219,6332,1576,5477,6755,8348,4164,4307,2984,4012,6629,1044,2874,6541,4942,903,1404,9125,5160,8836,4345,2581,460,8438,1538,5507,668,3352,2678,6942 +4295,1176,5596,1521,3061,9868,7037,7129,8933,6659,5947,5063,3653,9447,9245,2679,767,714,116,8558,163,3927,8779,158,5093,2447,5782,3967,1716,931,7772,8164,1117,9244,5783,7776,3846,8862,6014,2330,6947,1777,3112,6008,3491,1906,5952,314,4602,8994,5919,9214,3995,5026,7688,6809,5003,3128,2509,7477,110,8971,3982,8539,2980,4689,6343,5411,2992,5270,5247,9260,2269,7474,1042,7162,5206,1232,4556,4757 +510,3556,5377,1406,5721,4946,2635,7847,4251,8293,8281,6351,4912,287,2870,3380,3948,5322,3840,4738,9563,1906,6298,3234,8959,1562,6297,8835,7861,239,6618,1322,2553,2213,5053,5446,4402,6500,5182,8585,6900,5756,9661,903,5186,7687,5998,7997,8081,8955,4835,6069,2621,1581,732,9564,1082,1853,5442,1342,520,1737,3703,5321,4793,2776,1508,1647,9101,2499,6891,4336,7012,3329,3212,1442,9993,3988,4930,7706 +9444,3401,5891,9716,1228,7107,109,3563,2700,6161,5039,4992,2242,8541,7372,2067,1294,3058,1306,320,8881,5756,9326,411,8650,8824,5495,8282,8397,2000,1228,7817,2099,6473,3571,5994,4447,1299,5991,543,7874,2297,1651,101,2093,3463,9189,6872,6118,872,1008,1779,2805,9084,4048,2123,5877,55,3075,1737,9459,4535,6453,3644,108,5982,4437,5213,1340,6967,9943,5815,669,8074,1838,6979,9132,9315,715,5048 +3327,4030,7177,6336,9933,5296,2621,4785,2755,4832,2512,2118,2244,4407,2170,499,7532,9742,5051,7687,970,6924,3527,4694,5145,1306,2165,5940,2425,8910,3513,1909,6983,346,6377,4304,9330,7203,6605,3709,3346,970,369,9737,5811,4427,9939,3693,8436,5566,1977,3728,2399,3985,8303,2492,5366,9802,9193,7296,1033,5060,9144,2766,1151,7629,5169,5995,58,7619,7565,4208,1713,6279,3209,4908,9224,7409,1325,8540 +6882,1265,1775,3648,4690,959,5837,4520,5394,1378,9485,1360,4018,578,9174,2932,9890,3696,116,1723,1178,9355,7063,1594,1918,8574,7594,7942,1547,6166,7888,354,6932,4651,1010,7759,6905,661,7689,6092,9292,3845,9605,8443,443,8275,5163,7720,7265,6356,7779,1798,1754,5225,6661,1180,8024,5666,88,9153,1840,3508,1193,4445,2648,3538,6243,6375,8107,5902,5423,2520,1122,5015,6113,8859,9370,966,8673,2442 +7338,3423,4723,6533,848,8041,7921,8277,4094,5368,7252,8852,9166,2250,2801,6125,8093,5738,4038,9808,7359,9494,601,9116,4946,2702,5573,2921,9862,1462,1269,2410,4171,2709,7508,6241,7522,615,2407,8200,4189,5492,5649,7353,2590,5203,4274,710,7329,9063,956,8371,3722,4253,4785,1194,4828,4717,4548,940,983,2575,4511,2938,1827,2027,2700,1236,841,5760,1680,6260,2373,3851,1841,4968,1172,5179,7175,3509 +4420,1327,3560,2376,6260,2988,9537,4064,4829,8872,9598,3228,1792,7118,9962,9336,4368,9189,6857,1829,9863,6287,7303,7769,2707,8257,2391,2009,3975,4993,3068,9835,3427,341,8412,2134,4034,8511,6421,3041,9012,2983,7289,100,1355,7904,9186,6920,5856,2008,6545,8331,3655,5011,839,8041,9255,6524,3862,8788,62,7455,3513,5003,8413,3918,2076,7960,6108,3638,6999,3436,1441,4858,4181,1866,8731,7745,3744,1000 +356,8296,8325,1058,1277,4743,3850,2388,6079,6462,2815,5620,8495,5378,75,4324,3441,9870,1113,165,1544,1179,2834,562,6176,2313,6836,8839,2986,9454,5199,6888,1927,5866,8760,320,1792,8296,7898,6121,7241,5886,5814,2815,8336,1576,4314,3109,2572,6011,2086,9061,9403,3947,5487,9731,7281,3159,1819,1334,3181,5844,5114,9898,4634,2531,4412,6430,4262,8482,4546,4555,6804,2607,9421,686,8649,8860,7794,6672 +9870,152,1558,4963,8750,4754,6521,6256,8818,5208,5691,9659,8377,9725,5050,5343,2539,6101,1844,9700,7750,8114,5357,3001,8830,4438,199,9545,8496,43,2078,327,9397,106,6090,8181,8646,6414,7499,5450,4850,6273,5014,4131,7639,3913,6571,8534,9703,4391,7618,445,1320,5,1894,6771,7383,9191,4708,9706,6939,7937,8726,9382,5216,3685,2247,9029,8154,1738,9984,2626,9438,4167,6351,5060,29,1218,1239,4785 +192,5213,8297,8974,4032,6966,5717,1179,6523,4679,9513,1481,3041,5355,9303,9154,1389,8702,6589,7818,6336,3539,5538,3094,6646,6702,6266,2759,4608,4452,617,9406,8064,6379,444,5602,4950,1810,8391,1536,316,8714,1178,5182,5863,5110,5372,4954,1978,2971,5680,4863,2255,4630,5723,2168,538,1692,1319,7540,440,6430,6266,7712,7385,5702,620,641,3136,7350,1478,3155,2820,9109,6261,1122,4470,14,8493,2095 +1046,4301,6082,474,4974,7822,2102,5161,5172,6946,8074,9716,6586,9962,9749,5015,2217,995,5388,4402,7652,6399,6539,1349,8101,3677,1328,9612,7922,2879,231,5887,2655,508,4357,4964,3554,5930,6236,7384,4614,280,3093,9600,2110,7863,2631,6626,6620,68,1311,7198,7561,1768,5139,1431,221,230,2940,968,5283,6517,2146,1646,869,9402,7068,8645,7058,1765,9690,4152,2926,9504,2939,7504,6074,2944,6470,7859 +4659,736,4951,9344,1927,6271,8837,8711,3241,6579,7660,5499,5616,3743,5801,4682,9748,8796,779,1833,4549,8138,4026,775,4170,2432,4174,3741,7540,8017,2833,4027,396,811,2871,1150,9809,2719,9199,8504,1224,540,2051,3519,7982,7367,2761,308,3358,6505,2050,4836,5090,7864,805,2566,2409,6876,3361,8622,5572,5895,3280,441,7893,8105,1634,2929,274,3926,7786,6123,8233,9921,2674,5340,1445,203,4585,3837 +5759,338,7444,7968,7742,3755,1591,4839,1705,650,7061,2461,9230,9391,9373,2413,1213,431,7801,4994,2380,2703,6161,6878,8331,2538,6093,1275,5065,5062,2839,582,1014,8109,3525,1544,1569,8622,7944,2905,6120,1564,1839,5570,7579,1318,2677,5257,4418,5601,7935,7656,5192,1864,5886,6083,5580,6202,8869,1636,7907,4759,9082,5854,3185,7631,6854,5872,5632,5280,1431,2077,9717,7431,4256,8261,9680,4487,4752,4286 +1571,1428,8599,1230,7772,4221,8523,9049,4042,8726,7567,6736,9033,2104,4879,4967,6334,6716,3994,1269,8995,6539,3610,7667,6560,6065,874,848,4597,1711,7161,4811,6734,5723,6356,6026,9183,2586,5636,1092,7779,7923,8747,6887,7505,9909,1792,3233,4526,3176,1508,8043,720,5212,6046,4988,709,5277,8256,3642,1391,5803,1468,2145,3970,6301,7767,2359,8487,9771,8785,7520,856,1605,8972,2402,2386,991,1383,5963 +1822,4824,5957,6511,9868,4113,301,9353,6228,2881,2966,6956,9124,9574,9233,1601,7340,973,9396,540,4747,8590,9535,3650,7333,7583,4806,3593,2738,8157,5215,8472,2284,9473,3906,6982,5505,6053,7936,6074,7179,6688,1564,1103,6860,5839,2022,8490,910,7551,7805,881,7024,1855,9448,4790,1274,3672,2810,774,7623,4223,4850,6071,9975,4935,1915,9771,6690,3846,517,463,7624,4511,614,6394,3661,7409,1395,8127 +8738,3850,9555,3695,4383,2378,87,6256,6740,7682,9546,4255,6105,2000,1851,4073,8957,9022,6547,5189,2487,303,9602,7833,1628,4163,6678,3144,8589,7096,8913,5823,4890,7679,1212,9294,5884,2972,3012,3359,7794,7428,1579,4350,7246,4301,7779,7790,3294,9547,4367,3549,1958,8237,6758,3497,3250,3456,6318,1663,708,7714,6143,6890,3428,6853,9334,7992,591,6449,9786,1412,8500,722,5468,1371,108,3939,4199,2535 +7047,4323,1934,5163,4166,461,3544,2767,6554,203,6098,2265,9078,2075,4644,6641,8412,9183,487,101,7566,5622,1975,5726,2920,5374,7779,5631,3753,3725,2672,3621,4280,1162,5812,345,8173,9785,1525,955,5603,2215,2580,5261,2765,2990,5979,389,3907,2484,1232,5933,5871,3304,1138,1616,5114,9199,5072,7442,7245,6472,4760,6359,9053,7876,2564,9404,3043,9026,2261,3374,4460,7306,2326,966,828,3274,1712,3446 +3975,4565,8131,5800,4570,2306,8838,4392,9147,11,3911,7118,9645,4994,2028,6062,5431,2279,8752,2658,7836,994,7316,5336,7185,3289,1898,9689,2331,5737,3403,1124,2679,3241,7748,16,2724,5441,6640,9368,9081,5618,858,4969,17,2103,6035,8043,7475,2181,939,415,1617,8500,8253,2155,7843,7974,7859,1746,6336,3193,2617,8736,4079,6324,6645,8891,9396,5522,6103,1857,8979,3835,2475,1310,7422,610,8345,7615 +9248,5397,5686,2988,3446,4359,6634,9141,497,9176,6773,7448,1907,8454,916,1596,2241,1626,1384,2741,3649,5362,8791,7170,2903,2475,5325,6451,924,3328,522,90,4813,9737,9557,691,2388,1383,4021,1609,9206,4707,5200,7107,8104,4333,9860,5013,1224,6959,8527,1877,4545,7772,6268,621,4915,9349,5970,706,9583,3071,4127,780,8231,3017,9114,3836,7503,2383,1977,4870,8035,2379,9704,1037,3992,3642,1016,4303 +5093,138,4639,6609,1146,5565,95,7521,9077,2272,974,4388,2465,2650,722,4998,3567,3047,921,2736,7855,173,2065,4238,1048,5,6847,9548,8632,9194,5942,4777,7910,8971,6279,7253,2516,1555,1833,3184,9453,9053,6897,7808,8629,4877,1871,8055,4881,7639,1537,7701,2508,7564,5845,5023,2304,5396,3193,2955,1088,3801,6203,1748,3737,1276,13,4120,7715,8552,3047,2921,106,7508,304,1280,7140,2567,9135,5266 +6237,4607,7527,9047,522,7371,4883,2540,5867,6366,5301,1570,421,276,3361,527,6637,4861,2401,7522,5808,9371,5298,2045,5096,5447,7755,5115,7060,8529,4078,1943,1697,1764,5453,7085,960,2405,739,2100,5800,728,9737,5704,5693,1431,8979,6428,673,7540,6,7773,5857,6823,150,5869,8486,684,5816,9626,7451,5579,8260,3397,5322,6920,1879,2127,2884,5478,4977,9016,6165,6292,3062,5671,5968,78,4619,4763 +9905,7127,9390,5185,6923,3721,9164,9705,4341,1031,1046,5127,7376,6528,3248,4941,1178,7889,3364,4486,5358,9402,9158,8600,1025,874,1839,1783,309,9030,1843,845,8398,1433,7118,70,8071,2877,3904,8866,6722,4299,10,1929,5897,4188,600,1889,3325,2485,6473,4474,7444,6992,4846,6166,4441,2283,2629,4352,7775,1101,2214,9985,215,8270,9750,2740,8361,7103,5930,8664,9690,8302,9267,344,2077,1372,1880,9550 +5825,8517,7769,2405,8204,1060,3603,7025,478,8334,1997,3692,7433,9101,7294,7498,9415,5452,3850,3508,6857,9213,6807,4412,7310,854,5384,686,4978,892,8651,3241,2743,3801,3813,8588,6701,4416,6990,6490,3197,6838,6503,114,8343,5844,8646,8694,65,791,5979,2687,2621,2019,8097,1423,3644,9764,4921,3266,3662,5561,2476,8271,8138,6147,1168,3340,1998,9874,6572,9873,6659,5609,2711,3931,9567,4143,7833,8887 +6223,2099,2700,589,4716,8333,1362,5007,2753,2848,4441,8397,7192,8191,4916,9955,6076,3370,6396,6971,3156,248,3911,2488,4930,2458,7183,5455,170,6809,6417,3390,1956,7188,577,7526,2203,968,8164,479,8699,7915,507,6393,4632,1597,7534,3604,618,3280,6061,9793,9238,8347,568,9645,2070,5198,6482,5000,9212,6655,5961,7513,1323,3872,6170,3812,4146,2736,67,3151,5548,2781,9679,7564,5043,8587,1893,4531 +5826,3690,6724,2121,9308,6986,8106,6659,2142,1642,7170,2877,5757,6494,8026,6571,8387,9961,6043,9758,9607,6450,8631,8334,7359,5256,8523,2225,7487,1977,9555,8048,5763,2414,4948,4265,2427,8978,8088,8841,9208,9601,5810,9398,8866,9138,4176,5875,7212,3272,6759,5678,7649,4922,5422,1343,8197,3154,3600,687,1028,4579,2084,9467,4492,7262,7296,6538,7657,7134,2077,1505,7332,6890,8964,4879,7603,7400,5973,739 +1861,1613,4879,1884,7334,966,2000,7489,2123,4287,1472,3263,4726,9203,1040,4103,6075,6049,330,9253,4062,4268,1635,9960,577,1320,3195,9628,1030,4092,4979,6474,6393,2799,6967,8687,7724,7392,9927,2085,3200,6466,8702,265,7646,8665,7986,7266,4574,6587,612,2724,704,3191,8323,9523,3002,704,5064,3960,8209,2027,2758,8393,4875,4641,9584,6401,7883,7014,768,443,5490,7506,1852,2005,8850,5776,4487,4269 +4052,6687,4705,7260,6645,6715,3706,5504,8672,2853,1136,8187,8203,4016,871,1809,1366,4952,9294,5339,6872,2645,6083,7874,3056,5218,7485,8796,7401,3348,2103,426,8572,4163,9171,3176,948,7654,9344,3217,1650,5580,7971,2622,76,2874,880,2034,9929,1546,2659,5811,3754,7096,7436,9694,9960,7415,2164,953,2360,4194,2397,1047,2196,6827,575,784,2675,8821,6802,7972,5996,6699,2134,7577,2887,1412,4349,4380 +4629,2234,6240,8132,7592,3181,6389,1214,266,1910,2451,8784,2790,1127,6932,1447,8986,2492,5476,397,889,3027,7641,5083,5776,4022,185,3364,5701,2442,2840,4160,9525,4828,6602,2614,7447,3711,4505,7745,8034,6514,4907,2605,7753,6958,7270,6936,3006,8968,439,2326,4652,3085,3425,9863,5049,5361,8688,297,7580,8777,7916,6687,8683,7141,306,9569,2384,1500,3346,4601,7329,9040,6097,2727,6314,4501,4974,2829 +8316,4072,2025,6884,3027,1808,5714,7624,7880,8528,4205,8686,7587,3230,1139,7273,6163,6986,3914,9309,1464,9359,4474,7095,2212,7302,2583,9462,7532,6567,1606,4436,8981,5612,6796,4385,5076,2007,6072,3678,8331,1338,3299,8845,4783,8613,4071,1232,6028,2176,3990,2148,3748,103,9453,538,6745,9110,926,3125,473,5970,8728,7072,9062,1404,1317,5139,9862,6496,6062,3338,464,1600,2532,1088,8232,7739,8274,3873 +2341,523,7096,8397,8301,6541,9844,244,4993,2280,7689,4025,4196,5522,7904,6048,2623,9258,2149,9461,6448,8087,7245,1917,8340,7127,8466,5725,6996,3421,5313,512,9164,9837,9794,8369,4185,1488,7210,1524,1016,4620,9435,2478,7765,8035,697,6677,3724,6988,5853,7662,3895,9593,1185,4727,6025,5734,7665,3070,138,8469,6748,6459,561,7935,8646,2378,462,7755,3115,9690,8877,3946,2728,8793,244,6323,8666,4271 +6430,2406,8994,56,1267,3826,9443,7079,7579,5232,6691,3435,6718,5698,4144,7028,592,2627,217,734,6194,8156,9118,58,2640,8069,4127,3285,694,3197,3377,4143,4802,3324,8134,6953,7625,3598,3584,4289,7065,3434,2106,7132,5802,7920,9060,7531,3321,1725,1067,3751,444,5503,6785,7937,6365,4803,198,6266,8177,1470,6390,1606,2904,7555,9834,8667,2033,1723,5167,1666,8546,8152,473,4475,6451,7947,3062,3281 +2810,3042,7759,1741,2275,2609,7676,8640,4117,1958,7500,8048,1757,3954,9270,1971,4796,2912,660,5511,3553,1012,5757,4525,6084,7198,8352,5775,7726,8591,7710,9589,3122,4392,6856,5016,749,2285,3356,7482,9956,7348,2599,8944,495,3462,3578,551,4543,7207,7169,7796,1247,4278,6916,8176,3742,8385,2310,1345,8692,2667,4568,1770,8319,3585,4920,3890,4928,7343,5385,9772,7947,8786,2056,9266,3454,2807,877,2660 +6206,8252,5928,5837,4177,4333,207,7934,5581,9526,8906,1498,8411,2984,5198,5134,2464,8435,8514,8674,3876,599,5327,826,2152,4084,2433,9327,9697,4800,2728,3608,3849,3861,3498,9943,1407,3991,7191,9110,5666,8434,4704,6545,5944,2357,1163,4995,9619,6754,4200,9682,6654,4862,4744,5953,6632,1054,293,9439,8286,2255,696,8709,1533,1844,6441,430,1999,6063,9431,7018,8057,2920,6266,6799,356,3597,4024,6665 +3847,6356,8541,7225,2325,2946,5199,469,5450,7508,2197,9915,8284,7983,6341,3276,3321,16,1321,7608,5015,3362,8491,6968,6818,797,156,2575,706,9516,5344,5457,9210,5051,8099,1617,9951,7663,8253,9683,2670,1261,4710,1068,8753,4799,1228,2621,3275,6188,4699,1791,9518,8701,5932,4275,6011,9877,2933,4182,6059,2930,6687,6682,9771,654,9437,3169,8596,1827,5471,8909,2352,123,4394,3208,8756,5513,6917,2056 +5458,8173,3138,3290,4570,4892,3317,4251,9699,7973,1163,1935,5477,6648,9614,5655,9592,975,9118,2194,7322,8248,8413,3462,8560,1907,7810,6650,7355,2939,4973,6894,3933,3784,3200,2419,9234,4747,2208,2207,1945,2899,1407,6145,8023,3484,5688,7686,2737,3828,3704,9004,5190,9740,8643,8650,5358,4426,1522,1707,3613,9887,6956,2447,2762,833,1449,9489,2573,1080,4167,3456,6809,2466,227,7125,2759,6250,6472,8089 +3266,7025,9756,3914,1265,9116,7723,9788,6805,5493,2092,8688,6592,9173,4431,4028,6007,7131,4446,4815,3648,6701,759,3312,8355,4485,4187,5188,8746,7759,3528,2177,5243,8379,3838,7233,4607,9187,7216,2190,6967,2920,6082,7910,5354,3609,8958,6949,7731,494,8753,8707,1523,4426,3543,7085,647,6771,9847,646,5049,824,8417,5260,2730,5702,2513,9275,4279,2767,8684,1165,9903,4518,55,9682,8963,6005,2102,6523 +1998,8731,936,1479,5259,7064,4085,91,7745,7136,3773,3810,730,8255,2705,2653,9790,6807,2342,355,9344,2668,3690,2028,9679,8102,574,4318,6481,9175,5423,8062,2867,9657,7553,3442,3920,7430,3945,7639,3714,3392,2525,4995,4850,2867,7951,9667,486,9506,9888,781,8866,1702,3795,90,356,1483,4200,2131,6969,5931,486,6880,4404,1084,5169,4910,6567,8335,4686,5043,2614,3352,2667,4513,6472,7471,5720,1616 +8878,1613,1716,868,1906,2681,564,665,5995,2474,7496,3432,9491,9087,8850,8287,669,823,347,6194,2264,2592,7871,7616,8508,4827,760,2676,4660,4881,7572,3811,9032,939,4384,929,7525,8419,5556,9063,662,8887,7026,8534,3111,1454,2082,7598,5726,6687,9647,7608,73,3014,5063,670,5461,5631,3367,9796,8475,7908,5073,1565,5008,5295,4457,1274,4788,1728,338,600,8415,8535,9351,7750,6887,5845,1741,125 +3637,6489,9634,9464,9055,2413,7824,9517,7532,3577,7050,6186,6980,9365,9782,191,870,2497,8498,2218,2757,5420,6468,586,3320,9230,1034,1393,9886,5072,9391,1178,8464,8042,6869,2075,8275,3601,7715,9470,8786,6475,8373,2159,9237,2066,3264,5000,679,355,3069,4073,494,2308,5512,4334,9438,8786,8637,9774,1169,1949,6594,6072,4270,9158,7916,5752,6794,9391,6301,5842,3285,2141,3898,8027,4310,8821,7079,1307 +8497,6681,4732,7151,7060,5204,9030,7157,833,5014,8723,3207,9796,9286,4913,119,5118,7650,9335,809,3675,2597,5144,3945,5090,8384,187,4102,1260,2445,2792,4422,8389,9290,50,1765,1521,6921,8586,4368,1565,5727,7855,2003,4834,9897,5911,8630,5070,1330,7692,7557,7980,6028,5805,9090,8265,3019,3802,698,9149,5748,1965,9658,4417,5994,5584,8226,2937,272,5743,1278,5698,8736,2595,6475,5342,6596,1149,6920 +8188,8009,9546,6310,8772,2500,9846,6592,6872,3857,1307,8125,7042,1544,6159,2330,643,4604,7899,6848,371,8067,2062,3200,7295,1857,9505,6936,384,2193,2190,301,8535,5503,1462,7380,5114,4824,8833,1763,4974,8711,9262,6698,3999,2645,6937,7747,1128,2933,3556,7943,2885,3122,9105,5447,418,2899,5148,3699,9021,9501,597,4084,175,1621,1,1079,6067,5812,4326,9914,6633,5394,4233,6728,9084,1864,5863,1225 +9935,8793,9117,1825,9542,8246,8437,3331,9128,9675,6086,7075,319,1334,7932,3583,7167,4178,1726,7720,695,8277,7887,6359,5912,1719,2780,8529,1359,2013,4498,8072,1129,9998,1147,8804,9405,6255,1619,2165,7491,1,8882,7378,3337,503,5758,4109,3577,985,3200,7615,8058,5032,1080,6410,6873,5496,1466,2412,9885,5904,4406,3605,8770,4361,6205,9193,1537,9959,214,7260,9566,1685,100,4920,7138,9819,5637,976 +3466,9854,985,1078,7222,8888,5466,5379,3578,4540,6853,8690,3728,6351,7147,3134,6921,9692,857,3307,4998,2172,5783,3931,9417,2541,6299,13,787,2099,9131,9494,896,8600,1643,8419,7248,2660,2609,8579,91,6663,5506,7675,1947,6165,4286,1972,9645,3805,1663,1456,8853,5705,9889,7489,1107,383,4044,2969,3343,152,7805,4980,9929,5033,1737,9953,7197,9158,4071,1324,473,9676,3984,9680,3606,8160,7384,5432 +1005,4512,5186,3953,2164,3372,4097,3247,8697,3022,9896,4101,3871,6791,3219,2742,4630,6967,7829,5991,6134,1197,1414,8923,8787,1394,8852,5019,7768,5147,8004,8825,5062,9625,7988,1110,3992,7984,9966,6516,6251,8270,421,3723,1432,4830,6935,8095,9059,2214,6483,6846,3120,1587,6201,6691,9096,9627,6671,4002,3495,9939,7708,7465,5879,6959,6634,3241,3401,2355,9061,2611,7830,3941,2177,2146,5089,7079,519,6351 +7280,8586,4261,2831,7217,3141,9994,9940,5462,2189,4005,6942,9848,5350,8060,6665,7519,4324,7684,657,9453,9296,2944,6843,7499,7847,1728,9681,3906,6353,5529,2822,3355,3897,7724,4257,7489,8672,4356,3983,1948,6892,7415,4153,5893,4190,621,1736,4045,9532,7701,3671,1211,1622,3176,4524,9317,7800,5638,6644,6943,5463,3531,2821,1347,5958,3436,1438,2999,994,850,4131,2616,1549,3465,5946,690,9273,6954,7991 +9517,399,3249,2596,7736,2142,1322,968,7350,1614,468,3346,3265,7222,6086,1661,5317,2582,7959,4685,2807,2917,1037,5698,1529,3972,8716,2634,3301,3412,8621,743,8001,4734,888,7744,8092,3671,8941,1487,5658,7099,2781,99,1932,4443,4756,4652,9328,1581,7855,4312,5976,7255,6480,3996,2748,1973,9731,4530,2790,9417,7186,5303,3557,351,7182,9428,1342,9020,7599,1392,8304,2070,9138,7215,2008,9937,1106,7110 +7444,769,9688,632,1571,6820,8743,4338,337,3366,3073,1946,8219,104,4210,6986,249,5061,8693,7960,6546,1004,8857,5997,9352,4338,6105,5008,2556,6518,6694,4345,3727,7956,20,3954,8652,4424,9387,2035,8358,5962,5304,5194,8650,8282,1256,1103,2138,6679,1985,3653,2770,2433,4278,615,2863,1715,242,3790,2636,6998,3088,1671,2239,957,5411,4595,6282,2881,9974,2401,875,7574,2987,4587,3147,6766,9885,2965 +3287,3016,3619,6818,9073,6120,5423,557,2900,2015,8111,3873,1314,4189,1846,4399,7041,7583,2427,2864,3525,5002,2069,748,1948,6015,2684,438,770,8367,1663,7887,7759,1885,157,7770,4520,4878,3857,1137,3525,3050,6276,5569,7649,904,4533,7843,2199,5648,7628,9075,9441,3600,7231,2388,5640,9096,958,3058,584,5899,8150,1181,9616,1098,8162,6819,8171,1519,1140,7665,8801,2632,1299,9192,707,9955,2710,7314 +1772,2963,7578,3541,3095,1488,7026,2634,6015,4633,4370,2762,1650,2174,909,8158,2922,8467,4198,4280,9092,8856,8835,5457,2790,8574,9742,5054,9547,4156,7940,8126,9824,7340,8840,6574,3547,1477,3014,6798,7134,435,9484,9859,3031,4,1502,4133,1738,1807,4825,463,6343,9701,8506,9822,9555,8688,8168,3467,3234,6318,1787,5591,419,6593,7974,8486,9861,6381,6758,194,3061,4315,2863,4665,3789,2201,1492,4416 +126,8927,6608,5682,8986,6867,1715,6076,3159,788,3140,4744,830,9253,5812,5021,7616,8534,1546,9590,1101,9012,9821,8132,7857,4086,1069,7491,2988,1579,2442,4321,2149,7642,6108,250,6086,3167,24,9528,7663,2685,1220,9196,1397,5776,1577,1730,5481,977,6115,199,6326,2183,3767,5928,5586,7561,663,8649,9688,949,5913,9160,1870,5764,9887,4477,6703,1413,4995,5494,7131,2192,8969,7138,3997,8697,646,1028 +8074,1731,8245,624,4601,8706,155,8891,309,2552,8208,8452,2954,3124,3469,4246,3352,1105,4509,8677,9901,4416,8191,9283,5625,7120,2952,8881,7693,830,4580,8228,9459,8611,4499,1179,4988,1394,550,2336,6089,6872,269,7213,1848,917,6672,4890,656,1478,6536,3165,4743,4990,1176,6211,7207,5284,9730,4738,1549,4986,4942,8645,3698,9429,1439,2175,6549,3058,6513,1574,6988,8333,3406,5245,5431,7140,7085,6407 +7845,4694,2530,8249,290,5948,5509,1588,5940,4495,5866,5021,4626,3979,3296,7589,4854,1998,5627,3926,8346,6512,9608,1918,7070,4747,4182,2858,2766,4606,6269,4107,8982,8568,9053,4244,5604,102,2756,727,5887,2566,7922,44,5986,621,1202,374,6988,4130,3627,6744,9443,4568,1398,8679,397,3928,9159,367,2917,6127,5788,3304,8129,911,2669,1463,9749,264,4478,8940,1109,7309,2462,117,4692,7724,225,2312 +4164,3637,2000,941,8903,39,3443,7172,1031,3687,4901,8082,4945,4515,7204,9310,9349,9535,9940,218,1788,9245,2237,1541,5670,6538,6047,5553,9807,8101,1925,8714,445,8332,7309,6830,5786,5736,7306,2710,3034,1838,7969,6318,7912,2584,2080,7437,6705,2254,7428,820,782,9861,7596,3842,3631,8063,5240,6666,394,4565,7865,4895,9890,6028,6117,4724,9156,4473,4552,602,470,6191,4927,5387,884,3146,1978,3000 +4258,6880,1696,3582,5793,4923,2119,1155,9056,9698,6603,3768,5514,9927,9609,6166,6566,4536,4985,4934,8076,9062,6741,6163,7399,4562,2337,5600,2919,9012,8459,1308,6072,1225,9306,8818,5886,7243,7365,8792,6007,9256,6699,7171,4230,7002,8720,7839,4533,1671,478,7774,1607,2317,5437,4705,7886,4760,6760,7271,3081,2997,3088,7675,6208,3101,6821,6840,122,9633,4900,2067,8546,4549,2091,7188,5605,8599,6758,5229 +7854,5243,9155,3556,8812,7047,2202,1541,5993,4600,4760,713,434,7911,7426,7414,8729,322,803,7960,7563,4908,6285,6291,736,3389,9339,4132,8701,7534,5287,3646,592,3065,7582,2592,8755,6068,8597,1982,5782,1894,2900,6236,4039,6569,3037,5837,7698,700,7815,2491,7272,5878,3083,6778,6639,3589,5010,8313,2581,6617,5869,8402,6808,2951,2321,5195,497,2190,6187,1342,1316,4453,7740,4154,2959,1781,1482,8256 +7178,2046,4419,744,8312,5356,6855,8839,319,2962,5662,47,6307,8662,68,4813,567,2712,9931,1678,3101,8227,6533,4933,6656,92,5846,4780,6256,6361,4323,9985,1231,2175,7178,3034,9744,6155,9165,7787,5836,9318,7860,9644,8941,6480,9443,8188,5928,161,6979,2352,5628,6991,1198,8067,5867,6620,3778,8426,2994,3122,3124,6335,3918,8897,2655,9670,634,1088,1576,8935,7255,474,8166,7417,9547,2886,5560,3842 +6957,3111,26,7530,7143,1295,1744,6057,3009,1854,8098,5405,2234,4874,9447,2620,9303,27,7410,969,40,2966,5648,7596,8637,4238,3143,3679,7187,690,9980,7085,7714,9373,5632,7526,6707,3951,9734,4216,2146,3602,5371,6029,3039,4433,4855,4151,1449,3376,8009,7240,7027,4602,2947,9081,4045,8424,9352,8742,923,2705,4266,3232,2264,6761,363,2651,3383,7770,6730,7856,7340,9679,2158,610,4471,4608,910,6241 +4417,6756,1013,8797,658,8809,5032,8703,7541,846,3357,2920,9817,1745,9980,7593,4667,3087,779,3218,6233,5568,4296,2289,2654,7898,5021,9461,5593,8214,9173,4203,2271,7980,2983,5952,9992,8399,3468,1776,3188,9314,1720,6523,2933,621,8685,5483,8986,6163,3444,9539,4320,155,3992,2828,2150,6071,524,2895,5468,8063,1210,3348,9071,4862,483,9017,4097,6186,9815,3610,5048,1644,1003,9865,9332,2145,1944,2213 +9284,3803,4920,1927,6706,4344,7383,4786,9890,2010,5228,1224,3158,6967,8580,8990,8883,5213,76,8306,2031,4980,5639,9519,7184,5645,7769,3259,8077,9130,1317,3096,9624,3818,1770,695,2454,947,6029,3474,9938,3527,5696,4760,7724,7738,2848,6442,5767,6845,8323,4131,2859,7595,2500,4815,3660,9130,8580,7016,8231,4391,8369,3444,4069,4021,556,6154,627,2778,1496,4206,6356,8434,8491,3816,8231,3190,5575,1015 +3787,7572,1788,6803,5641,6844,1961,4811,8535,9914,9999,1450,8857,738,4662,8569,6679,2225,7839,8618,286,2648,5342,2294,3205,4546,176,8705,3741,6134,8324,8021,7004,5205,7032,6637,9442,5539,5584,4819,5874,5807,8589,6871,9016,983,1758,3786,1519,6241,185,8398,495,3370,9133,3051,4549,9674,7311,9738,3316,9383,2658,2776,9481,7558,619,3943,3324,6491,4933,153,9738,4623,912,3595,7771,7939,1219,4405 +2650,3883,4154,5809,315,7756,4430,1788,4451,1631,6461,7230,6017,5751,138,588,5282,2442,9110,9035,6349,2515,1570,6122,4192,4174,3530,1933,4186,4420,4609,5739,4135,2963,6308,1161,8809,8619,2796,3819,6971,8228,4188,1492,909,8048,2328,6772,8467,7671,9068,2226,7579,6422,7056,8042,3296,2272,3006,2196,7320,3238,3490,3102,37,1293,3212,4767,5041,8773,5794,4456,6174,7279,7054,2835,7053,9088,790,6640 +3101,1057,7057,3826,6077,1025,2955,1224,1114,6729,5902,4698,6239,7203,9423,1804,4417,6686,1426,6941,8071,1029,4985,9010,6122,6597,1622,1574,3513,1684,7086,5505,3244,411,9638,4150,907,9135,829,981,1707,5359,8781,9751,5,9131,3973,7159,1340,6955,7514,7993,6964,8198,1933,2797,877,3993,4453,8020,9349,8646,2779,8679,2961,3547,3374,3510,1129,3568,2241,2625,9138,5974,8206,7669,7678,1833,8700,4480 +4865,9912,8038,8238,782,3095,8199,1127,4501,7280,2112,2487,3626,2790,9432,1475,6312,8277,4827,2218,5806,7132,8752,1468,7471,6386,739,8762,8323,8120,5169,9078,9058,3370,9560,7987,8585,8531,5347,9312,1058,4271,1159,5286,5404,6925,8606,9204,7361,2415,560,586,4002,2644,1927,2824,768,4409,2942,3345,1002,808,4941,6267,7979,5140,8643,7553,9438,7320,4938,2666,4609,2778,8158,6730,3748,3867,1866,7181 +171,3771,7134,8927,4778,2913,3326,2004,3089,7853,1378,1729,4777,2706,9578,1360,5693,3036,1851,7248,2403,2273,8536,6501,9216,613,9671,7131,7719,6425,773,717,8803,160,1114,7554,7197,753,4513,4322,8499,4533,2609,4226,8710,6627,644,9666,6260,4870,5744,7385,6542,6203,7703,6130,8944,5589,2262,6803,6381,7414,6888,5123,7320,9392,9061,6780,322,8975,7050,5089,1061,2260,3199,1150,1865,5386,9699,6501 +3744,8454,6885,8277,919,1923,4001,6864,7854,5519,2491,6057,8794,9645,1776,5714,9786,9281,7538,6916,3215,395,2501,9618,4835,8846,9708,2813,3303,1794,8309,7176,2206,1602,1838,236,4593,2245,8993,4017,10,8215,6921,5206,4023,5932,6997,7801,262,7640,3107,8275,4938,7822,2425,3223,3886,2105,8700,9526,2088,8662,8034,7004,5710,2124,7164,3574,6630,9980,4242,2901,9471,1491,2117,4562,1130,9086,4117,6698 +2810,2280,2331,1170,4554,4071,8387,1215,2274,9848,6738,1604,7281,8805,439,1298,8318,7834,9426,8603,6092,7944,1309,8828,303,3157,4638,4439,9175,1921,4695,7716,1494,1015,1772,5913,1127,1952,1950,8905,4064,9890,385,9357,7945,5035,7082,5369,4093,6546,5187,5637,2041,8946,1758,7111,6566,1027,1049,5148,7224,7248,296,6169,375,1656,7993,2816,3717,4279,4675,1609,3317,42,6201,3100,3144,163,9530,4531 +7096,6070,1009,4988,3538,5801,7149,3063,2324,2912,7911,7002,4338,7880,2481,7368,3516,2016,7556,2193,1388,3865,8125,4637,4096,8114,750,3144,1938,7002,9343,4095,1392,4220,3455,6969,9647,1321,9048,1996,1640,6626,1788,314,9578,6630,2813,6626,4981,9908,7024,4355,3201,3521,3864,3303,464,1923,595,9801,3391,8366,8084,9374,1041,8807,9085,1892,9431,8317,9016,9221,8574,9981,9240,5395,2009,6310,2854,9255 +8830,3145,2960,9615,8220,6061,3452,2918,6481,9278,2297,3385,6565,7066,7316,5682,107,7646,4466,68,1952,9603,8615,54,7191,791,6833,2560,693,9733,4168,570,9127,9537,1925,8287,5508,4297,8452,8795,6213,7994,2420,4208,524,5915,8602,8330,2651,8547,6156,1812,6271,7991,9407,9804,1553,6866,1128,2119,4691,9711,8315,5879,9935,6900,482,682,4126,1041,428,6247,3720,5882,7526,2582,4327,7725,3503,2631 +2738,9323,721,7434,1453,6294,2957,3786,5722,6019,8685,4386,3066,9057,6860,499,5315,3045,5194,7111,3137,9104,941,586,3066,755,4177,8819,7040,5309,3583,3897,4428,7788,4721,7249,6559,7324,825,7311,3760,6064,6070,9672,4882,584,1365,9739,9331,5783,2624,7889,1604,1303,1555,7125,8312,425,8936,3233,7724,1480,403,7440,1784,1754,4721,1569,652,3893,4574,5692,9730,4813,9844,8291,9199,7101,3391,8914 +6044,2928,9332,3328,8588,447,3830,1176,3523,2705,8365,6136,5442,9049,5526,8575,8869,9031,7280,706,2794,8814,5767,4241,7696,78,6570,556,5083,1426,4502,3336,9518,2292,1885,3740,3153,9348,9331,8051,2759,5407,9028,7840,9255,831,515,2612,9747,7435,8964,4971,2048,4900,5967,8271,1719,9670,2810,6777,1594,6367,6259,8316,3815,1689,6840,9437,4361,822,9619,3065,83,6344,7486,8657,8228,9635,6932,4864 +8478,4777,6334,4678,7476,4963,6735,3096,5860,1405,5127,7269,7793,4738,227,9168,2996,8928,765,733,1276,7677,6258,1528,9558,3329,302,8901,1422,8277,6340,645,9125,8869,5952,141,8141,1816,9635,4025,4184,3093,83,2344,2747,9352,7966,1206,1126,1826,218,7939,2957,2729,810,8752,5247,4174,4038,8884,7899,9567,301,5265,5752,7524,4381,1669,3106,8270,6228,6373,754,2547,4240,2313,5514,3022,1040,9738 +2265,8192,1763,1369,8469,8789,4836,52,1212,6690,5257,8918,6723,6319,378,4039,2421,8555,8184,9577,1432,7139,8078,5452,9628,7579,4161,7490,5159,8559,1011,81,478,5840,1964,1334,6875,8670,9900,739,1514,8692,522,9316,6955,1345,8132,2277,3193,9773,3923,4177,2183,1236,6747,6575,4874,6003,6409,8187,745,8776,9440,7543,9825,2582,7381,8147,7236,5185,7564,6125,218,7991,6394,391,7659,7456,5128,5294 +2132,8992,8160,5782,4420,3371,3798,5054,552,5631,7546,4716,1332,6486,7892,7441,4370,6231,4579,2121,8615,1145,9391,1524,1385,2400,9437,2454,7896,7467,2928,8400,3299,4025,7458,4703,7206,6358,792,6200,725,4275,4136,7390,5984,4502,7929,5085,8176,4600,119,3568,76,9363,6943,2248,9077,9731,6213,5817,6729,4190,3092,6910,759,2682,8380,1254,9604,3011,9291,5329,9453,9746,2739,6522,3765,5634,1113,5789 +5304,5499,564,2801,679,2653,1783,3608,7359,7797,3284,796,3222,437,7185,6135,8571,2778,7488,5746,678,6140,861,7750,803,9859,9918,2425,3734,2698,9005,4864,9818,6743,2475,132,9486,3825,5472,919,292,4411,7213,7699,6435,9019,6769,1388,802,2124,1345,8493,9487,8558,7061,8777,8833,2427,2238,5409,4957,8503,3171,7622,5779,6145,2417,5873,5563,5693,9574,9491,1937,7384,4563,6842,5432,2751,3406,7981 diff --git a/project_euler/problem_081/sol1.py b/project_euler/problem_081/sol1.py new file mode 100644 index 000000000000..afa143f23b33 --- /dev/null +++ b/project_euler/problem_081/sol1.py @@ -0,0 +1,47 @@ +""" +Problem 81: https://projecteuler.net/problem=81 +In the 5 by 5 matrix below, the minimal path sum from the top left to the bottom right, +by only moving to the right and down, is indicated in bold red and is equal to 2427. + + [131] 673 234 103 18 + [201] [96] [342] 965 150 + 630 803 [746] [422] 111 + 537 699 497 [121] 956 + 805 732 524 [37] [331] + +Find the minimal path sum from the top left to the bottom right by only moving right +and down in matrix.txt (https://projecteuler.net/project/resources/p081_matrix.txt), +a 31K text file containing an 80 by 80 matrix. +""" +import os + + +def solution(filename: str = "matrix.txt") -> int: + """ + Returns the minimal path sum from the top left to the bottom right of the matrix. + >>> solution() + 427337 + """ + with open(os.path.join(os.path.dirname(__file__), filename), "r") as in_file: + data = in_file.read() + + grid = [[int(cell) for cell in row.split(",")] for row in data.strip().splitlines()] + dp = [[0 for cell in row] for row in grid] + n = len(grid[0]) + + dp = [[0 for i in range(n)] for j in range(n)] + dp[0][0] = grid[0][0] + for i in range(1, n): + dp[0][i] = grid[0][i] + dp[0][i - 1] + for i in range(1, n): + dp[i][0] = grid[i][0] + dp[i - 1][0] + + for i in range(1, n): + for j in range(1, n): + dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]) + + return dp[-1][-1] + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_085/__init__.py b/project_euler/problem_085/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_085/sol1.py b/project_euler/problem_085/sol1.py new file mode 100644 index 000000000000..74e36b1301a4 --- /dev/null +++ b/project_euler/problem_085/sol1.py @@ -0,0 +1,108 @@ +""" +Project Euler Problem 85: https://projecteuler.net/problem=85 + +By counting carefully it can be seen that a rectangular grid measuring 3 by 2 +contains eighteen rectangles. + +Although there exists no rectangular grid that contains exactly two million +rectangles, find the area of the grid with the nearest solution. + +Solution: + + For a grid with side-lengths a and b, the number of rectangles contained in the grid + is [a*(a+1)/2] * [b*(b+1)/2)], which happens to be the product of the a-th and b-th + triangle numbers. So to find the solution grid (a,b), we need to find the two + triangle numbers whose product is closest to two million. + + Denote these two triangle numbers Ta and Tb. We want their product Ta*Tb to be + as close as possible to 2m. Assuming that the best solution is fairly close to 2m, + We can assume that both Ta and Tb are roughly bounded by 2m. Since Ta = a(a+1)/2, + we can assume that a (and similarly b) are roughly bounded by sqrt(2 * 2m) = 2000. + Since this is a rough bound, to be on the safe side we add 10%. Therefore we start + by generating all the triangle numbers Ta for 1 <= a <= 2200. This can be done + iteratively since the ith triangle number is the sum of 1,2, ... ,i, and so + T(i) = T(i-1) + i. + + We then search this list of triangle numbers for the two that give a product + closest to our target of two million. Rather than testing every combination of 2 + elements of the list, which would find the result in quadratic time, we can find + the best pair in linear time. + + We iterate through the list of triangle numbers using enumerate() so we have a + and Ta. Since we want Ta * Tb to be as close as possible to 2m, we know that Tb + needs to be roughly 2m / Ta. Using the formula Tb = b*(b+1)/2 as well as the + quadratic formula, we can solve for b: + b is roughly (-1 + sqrt(1 + 8 * 2m / Ta)) / 2. + + Since the closest integers to this estimate will give product closest to 2m, + we only need to consider the integers above and below. It's then a simple matter + to get the triangle numbers corresponding to those integers, calculate the product + Ta * Tb, compare that product to our target 2m, and keep track of the (a,b) pair + that comes the closest. + + +Reference: https://en.wikipedia.org/wiki/Triangular_number + https://en.wikipedia.org/wiki/Quadratic_formula +""" + + +from math import ceil, floor, sqrt +from typing import List + + +def solution(target: int = 2000000) -> int: + """ + Find the area of the grid which contains as close to two million rectangles + as possible. + >>> solution(20) + 6 + >>> solution(2000) + 72 + >>> solution(2000000000) + 86595 + """ + triangle_numbers: List[int] = [0] + idx: int + + for idx in range(1, ceil(sqrt(target * 2) * 1.1)): + triangle_numbers.append(triangle_numbers[-1] + idx) + + # we want this to be as close as possible to target + best_product: int = 0 + # the area corresponding to the grid that gives the product closest to target + area: int = 0 + # an estimate of b, using the quadratic formula + b_estimate: float + # the largest integer less than b_estimate + b_floor: int + # the largest integer less than b_estimate + b_ceil: int + # the triangle number corresponding to b_floor + triangle_b_first_guess: int + # the triangle number corresponding to b_ceil + triangle_b_second_guess: int + + for idx_a, triangle_a in enumerate(triangle_numbers[1:], 1): + b_estimate = (-1 + sqrt(1 + 8 * target / triangle_a)) / 2 + b_floor = floor(b_estimate) + b_ceil = ceil(b_estimate) + triangle_b_first_guess = triangle_numbers[b_floor] + triangle_b_second_guess = triangle_numbers[b_ceil] + + if abs(target - triangle_b_first_guess * triangle_a) < abs( + target - best_product + ): + best_product = triangle_b_first_guess * triangle_a + area = idx_a * b_floor + + if abs(target - triangle_b_second_guess * triangle_a) < abs( + target - best_product + ): + best_product = triangle_b_second_guess * triangle_a + area = idx_a * b_ceil + + return area + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_086/__init__.py b/project_euler/problem_086/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_086/sol1.py b/project_euler/problem_086/sol1.py new file mode 100644 index 000000000000..0bf66e6b5a31 --- /dev/null +++ b/project_euler/problem_086/sol1.py @@ -0,0 +1,105 @@ +""" +Project Euler Problem 86: https://projecteuler.net/problem=86 + +A spider, S, sits in one corner of a cuboid room, measuring 6 by 5 by 3, and a fly, F, +sits in the opposite corner. By travelling on the surfaces of the room the shortest +"straight line" distance from S to F is 10 and the path is shown on the diagram. + +However, there are up to three "shortest" path candidates for any given cuboid and the +shortest route doesn't always have integer length. + +It can be shown that there are exactly 2060 distinct cuboids, ignoring rotations, with +integer dimensions, up to a maximum size of M by M by M, for which the shortest route +has integer length when M = 100. This is the least value of M for which the number of +solutions first exceeds two thousand; the number of solutions when M = 99 is 1975. + +Find the least value of M such that the number of solutions first exceeds one million. + +Solution: + Label the 3 side-lengths of the cuboid a,b,c such that 1 <= a <= b <= c <= M. + By conceptually "opening up" the cuboid and laying out its faces on a plane, + it can be seen that the shortest distance between 2 opposite corners is + sqrt((a+b)^2 + c^2). This distance is an integer if and only if (a+b),c make up + the first 2 sides of a pythagorean triplet. + + The second useful insight is rather than calculate the number of cuboids + with integral shortest distance for each maximum cuboid side-length M, + we can calculate this number iteratively each time we increase M, as follows. + The set of cuboids satisfying this property with maximum side-length M-1 is a + subset of the cuboids satisfying the property with maximum side-length M + (since any cuboids with side lengths <= M-1 are also <= M). To calculate the + number of cuboids in the larger set (corresponding to M) we need only consider + the cuboids which have at least one side of length M. Since we have ordered the + side lengths a <= b <= c, we can assume that c = M. Then we just need to count + the number of pairs a,b satisfying the conditions: + sqrt((a+b)^2 + M^2) is integer + 1 <= a <= b <= M + + To count the number of pairs (a,b) satisfying these conditions, write d = a+b. + Now we have: + 1 <= a <= b <= M => 2 <= d <= 2*M + we can actually make the second equality strict, + since d = 2*M => d^2 + M^2 = 5M^2 + => shortest distance = M * sqrt(5) + => not integral. + a + b = d => b = d - a + and a <= b + => a <= d/2 + also a <= M + => a <= min(M, d//2) + + a + b = d => a = d - b + and b <= M + => a >= d - M + also a >= 1 + => a >= max(1, d - M) + + So a is in range(max(1, d - M), min(M, d // 2) + 1) + + For a given d, the number of cuboids satisfying the required property with c = M + and a + b = d is the length of this range, which is + min(M, d // 2) + 1 - max(1, d - M). + + In the code below, d is sum_shortest_sides + and M is max_cuboid_size. + + +""" + + +from math import sqrt + + +def solution(limit: int = 1000000) -> int: + """ + Return the least value of M such that there are more than one million cuboids + of side lengths 1 <= a,b,c <= M such that the shortest distance between two + opposite vertices of the cuboid is integral. + >>> solution(100) + 24 + >>> solution(1000) + 72 + >>> solution(2000) + 100 + >>> solution(20000) + 288 + """ + num_cuboids: int = 0 + max_cuboid_size: int = 0 + sum_shortest_sides: int + + while num_cuboids <= limit: + max_cuboid_size += 1 + for sum_shortest_sides in range(2, 2 * max_cuboid_size + 1): + if sqrt(sum_shortest_sides ** 2 + max_cuboid_size ** 2).is_integer(): + num_cuboids += ( + min(max_cuboid_size, sum_shortest_sides // 2) + - max(1, sum_shortest_sides - max_cuboid_size) + + 1 + ) + + return max_cuboid_size + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_087/__init__.py b/project_euler/problem_087/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_087/sol1.py b/project_euler/problem_087/sol1.py new file mode 100644 index 000000000000..f444481c17ac --- /dev/null +++ b/project_euler/problem_087/sol1.py @@ -0,0 +1,52 @@ +""" +Project Euler Problem 87: https://projecteuler.net/problem=87 + +The smallest number expressible as the sum of a prime square, prime cube, and prime +fourth power is 28. In fact, there are exactly four numbers below fifty that can be +expressed in such a way: + +28 = 22 + 23 + 24 +33 = 32 + 23 + 24 +49 = 52 + 23 + 24 +47 = 22 + 33 + 24 + +How many numbers below fifty million can be expressed as the sum of a prime square, +prime cube, and prime fourth power? +""" + + +def solution(limit: int = 50000000) -> int: + """ + Return the number of integers less than limit which can be expressed as the sum + of a prime square, prime cube, and prime fourth power. + >>> solution(50) + 4 + """ + ret = set() + prime_square_limit = int((limit - 24) ** (1 / 2)) + + primes = set(range(3, prime_square_limit + 1, 2)) + primes.add(2) + for p in range(3, prime_square_limit + 1, 2): + if p not in primes: + continue + primes.difference_update(set(range(p * p, prime_square_limit + 1, p))) + + for prime1 in primes: + square = prime1 * prime1 + for prime2 in primes: + cube = prime2 * prime2 * prime2 + if square + cube >= limit - 16: + break + for prime3 in primes: + tetr = prime3 * prime3 * prime3 * prime3 + total = square + cube + tetr + if total >= limit: + break + ret.add(total) + + return len(ret) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_089/__init__.py b/project_euler/problem_089/__init__.py new file mode 100644 index 000000000000..792d6005489e --- /dev/null +++ b/project_euler/problem_089/__init__.py @@ -0,0 +1 @@ +# diff --git a/project_euler/problem_089/numeralcleanup_test.txt b/project_euler/problem_089/numeralcleanup_test.txt new file mode 100644 index 000000000000..06142142cca9 --- /dev/null +++ b/project_euler/problem_089/numeralcleanup_test.txt @@ -0,0 +1,5 @@ +IIII +IV +IIIIIIIIII +X +VIIIII diff --git a/project_euler/problem_089/p089_roman.txt b/project_euler/problem_089/p089_roman.txt new file mode 100644 index 000000000000..50651c355a5b --- /dev/null +++ b/project_euler/problem_089/p089_roman.txt @@ -0,0 +1,1000 @@ +MMMMDCLXXII +MMDCCCLXXXIII +MMMDLXVIIII +MMMMDXCV +DCCCLXXII +MMCCCVI +MMMCDLXXXVII +MMMMCCXXI +MMMCCXX +MMMMDCCCLXXIII +MMMCCXXXVII +MMCCCLXXXXIX +MDCCCXXIIII +MMCXCVI +CCXCVIII +MMMCCCXXXII +MDCCXXX +MMMDCCCL +MMMMCCLXXXVI +MMDCCCXCVI +MMMDCII +MMMCCXII +MMMMDCCCCI +MMDCCCXCII +MDCXX +CMLXXXVII +MMMXXI +MMMMCCCXIV +MLXXII +MCCLXXVIIII +MMMMCCXXXXI +MMDCCCLXXII +MMMMXXXI +MMMDCCLXXX +MMDCCCLXXIX +MMMMLXXXV +MCXXI +MDCCCXXXVII +MMCCCLXVII +MCDXXXV +CCXXXIII +CMXX +MMMCLXIV +MCCCLXXXVI +DCCCXCVIII +MMMDCCCCXXXIV +CDXVIIII +MMCCXXXV +MDCCCXXXII +MMMMD +MMDCCLXIX +MMMMCCCLXXXXVI +MMDCCXLII +MMMDCCCVIIII +DCCLXXXIIII +MDCCCCXXXII +MMCXXVII +DCCCXXX +CCLXIX +MMMXI +MMMMCMLXXXXVIII +MMMMDLXXXVII +MMMMDCCCLX +MMCCLIV +CMIX +MMDCCCLXXXIIII +CLXXXII +MMCCCCXXXXV +MMMMDLXXXVIIII +MMMDCCCXXI +MMDCCCCLXXVI +MCCCCLXX +MMCDLVIIII +MMMDCCCLIX +MMMMCCCCXIX +MMMDCCCLXXV +XXXI +CDLXXXIII +MMMCXV +MMDCCLXIII +MMDXXX +MMMMCCCLVII +MMMDCI +MMMMCDLXXXIIII +MMMMCCCXVI +CCCLXXXVIII +MMMMCML +MMMMXXIV +MMMCCCCXXX +DCCX +MMMCCLX +MMDXXXIII +CCCLXIII +MMDCCXIII +MMMCCCXLIV +CLXXXXI +CXVI +MMMMCXXXIII +CLXX +DCCCXVIII +MLXVII +DLXXXX +MMDXXI +MMMMDLXXXXVIII +MXXII +LXI +DCCCCXLIII +MMMMDV +MMMMXXXIV +MDCCCLVIII +MMMCCLXXII +MMMMDCCXXXVI +MMMMLXXXIX +MDCCCLXXXI +MMMMDCCCXV +MMMMCCCCXI +MMMMCCCLIII +MDCCCLXXI +MMCCCCXI +MLXV +MMCDLXII +MMMMDXXXXII +MMMMDCCCXL +MMMMCMLVI +CCLXXXIV +MMMDCCLXXXVI +MMCLII +MMMCCCCXV +MMLXXXIII +MMMV +MMMV +DCCLXII +MMDCCCCXVI +MMDCXLVIII +CCLIIII +CCCXXV +MMDCCLXXXVIIII +MMMMDCLXXVIII +MMMMDCCCXCI +MMMMCCCXX +MMCCXLV +MMMDCCCLXIX +MMCCLXIIII +MMMDCCCXLIX +MMMMCCCLXIX +CMLXXXXI +MCMLXXXIX +MMCDLXI +MMDCLXXVIII +MMMMDCCLXI +MCDXXV +DL +CCCLXXII +MXVIIII +MCCCCLXVIII +CIII +MMMDCCLXXIIII +MMMDVIII +MMMMCCCLXXXXVII +MMDXXVII +MMDCCLXXXXV +MMMMCXLVI +MMMDCCLXXXII +MMMDXXXVI +MCXXII +CLI +DCLXXXIX +MMMCLI +MDCLXIII +MMMMDCCXCVII +MMCCCLXXXV +MMMDCXXVIII +MMMCDLX +MMMCMLII +MMMIV +MMMMDCCCLVIII +MMMDLXXXVIII +MCXXIV +MMMMLXXVI +CLXXIX +MMMCCCCXXVIIII +DCCLXXXV +MMMDCCCVI +LI +CLXXXVI +MMMMCCCLXXVI +MCCCLXVI +CCXXXIX +MMDXXXXI +MMDCCCXLI +DCCCLXXXVIII +MMMMDCCCIV +MDCCCCXV +MMCMVI +MMMMCMLXXXXV +MMDCCLVI +MMMMCCXLVIII +DCCCCIIII +MMCCCCIII +MMMDCCLXXXVIIII +MDCCCLXXXXV +DVII +MMMV +DCXXV +MMDCCCXCV +DCVIII +MMCDLXVI +MCXXVIII +MDCCXCVIII +MMDCLX +MMMDCCLXIV +MMCDLXXVII +MMDLXXXIIII +MMMMCCCXXII +MMMDCCCXLIIII +DCCCCLXVII +MMMCLXXXXIII +MCCXV +MMMMDCXI +MMMMDCLXXXXV +MMMCCCLII +MMCMIX +MMDCCXXV +MMDLXXXVI +MMMMDCXXVIIII +DCCCCXXXVIIII +MMCCXXXIIII +MMDCCLXXVIII +MDCCLXVIIII +MMCCLXXXV +MMMMDCCCLXXXVIII +MMCMXCI +MDXLII +MMMMDCCXIV +MMMMLI +DXXXXIII +MMDCCXI +MMMMCCLXXXIII +MMMDCCCLXXIII +MDCLVII +MMCD +MCCCXXVII +MMMMDCCIIII +MMMDCCXLVI +MMMCLXXXVII +MMMCCVIIII +MCCCCLXXIX +DL +DCCCLXXVI +MMDXCI +MMMMDCCCCXXXVI +MMCII +MMMDCCCXXXXV +MMMCDXLV +MMDCXXXXIV +MMD +MDCCCLXXXX +MMDCXLIII +MMCCXXXII +MMDCXXXXVIIII +DCCCLXXI +MDXCVIIII +MMMMCCLXXVIII +MDCLVIIII +MMMCCCLXXXIX +MDCLXXXV +MDLVIII +MMMMCCVII +MMMMDCXIV +MMMCCCLXIIII +MMIIII +MMMMCCCLXXIII +CCIII +MMMCCLV +MMMDXIII +MMMCCCXC +MMMDCCCXXI +MMMMCCCCXXXII +CCCLVI +MMMCCCLXXXVI +MXVIIII +MMMCCCCXIIII +CLXVII +MMMCCLXX +CCCCLXIV +MMXXXXII +MMMMCCLXXXX +MXL +CCXVI +CCCCLVIIII +MMCCCII +MCCCLVIII +MMMMCCCX +MCDLXXXXIV +MDCCCXIII +MMDCCCXL +MMMMCCCXXIII +DXXXIV +CVI +MMMMDCLXXX +DCCCVII +MMCMLXIIII +MMMDCCCXXXIII +DCCC +MDIII +MMCCCLXVI +MMMCCCCLXXI +MMDCCCCXVIII +CCXXXVII +CCCXXV +MDCCCXII +MMMCMV +MMMMCMXV +MMMMDCXCI +DXXI +MMCCXLVIIII +MMMMCMLII +MDLXXX +MMDCLXVI +CXXI +MMMDCCCLIIII +MMMCXXI +MCCIII +MMDCXXXXI +CCXCII +MMMMDXXXV +MMMCCCLXV +MMMMDLXV +MMMCCCCXXXII +MMMCCCVIII +DCCCCLXXXXII +MMCLXIV +MMMMCXI +MLXXXXVII +MMMCDXXXVIII +MDXXII +MLV +MMMMDLXVI +MMMCXII +XXXIII +MMMMDCCCXXVI +MMMLXVIIII +MMMLX +MMMCDLXVII +MDCCCLVII +MMCXXXVII +MDCCCCXXX +MMDCCCLXIII +MMMMDCXLIX +MMMMCMXLVIII +DCCCLXXVIIII +MDCCCLIII +MMMCMLXI +MMMMCCLXI +MMDCCCLIII +MMMDCCCVI +MMDXXXXIX +MMCLXXXXV +MMDXXX +MMMXIII +DCLXXIX +DCCLXII +MMMMDCCLXVIII +MDCCXXXXIII +CCXXXII +MMMMDCXXV +MMMCCCXXVIII +MDCVIII +MMMCLXXXXIIII +CLXXXI +MDCCCCXXXIII +MMMMDCXXX +MMMDCXXIV +MMMCCXXXVII +MCCCXXXXIIII +CXVIII +MMDCCCCIV +MMMMCDLXXV +MMMDLXIV +MDXCIII +MCCLXXXI +MMMDCCCXXIV +MCXLIII +MMMDCCCI +MCCLXXX +CCXV +MMDCCLXXI +MMDLXXXIII +MMMMDCXVII +MMMCMLXV +MCLXVIII +MMMMCCLXXVI +MMMDCCLXVIIII +MMMMDCCCIX +DLXXXXIX +DCCCXXII +MMMMIII +MMMMCCCLXXVI +DCCCXCIII +DXXXI +MXXXIIII +CCXII +MMMDCCLXXXIIII +MMMCXX +MMMCMXXVII +DCCCXXXX +MMCDXXXVIIII +MMMMDCCXVIII +LV +MMMDCCCCVI +MCCCII +MMCMLXVIIII +MDCCXI +MMMMDLXVII +MMCCCCLXI +MMDCCV +MMMCCCXXXIIII +MMMMDI +MMMDCCCXCV +MMDCCLXXXXI +MMMDXXVI +MMMDCCCLVI +MMDCXXX +MCCCVII +MMMMCCCLXII +MMMMXXV +MMCMXXV +MMLVI +MMDXXX +MMMMCVII +MDC +MCCIII +MMMMDCC +MMCCLXXV +MMDCCCXXXXVI +MMMMCCCLXV +CDXIIII +MLXIIII +CCV +MMMCMXXXI +CCCCLXVI +MDXXXII +MMMMCCCLVIII +MMV +MMMCLII +MCMLI +MMDCCXX +MMMMCCCCXXXVI +MCCLXXXI +MMMCMVI +DCCXXX +MMMMCCCLXV +DCCCXI +MMMMDCCCXIV +CCCXXI +MMDLXXV +CCCCLXXXX +MCCCLXXXXII +MMDCIX +DCCXLIIII +DXIV +MMMMCLII +CDLXI +MMMCXXVII +MMMMDCCCCLXIII +MMMDCLIIII +MCCCCXXXXII +MMCCCLX +CCCCLIII +MDCCLXXVI +MCMXXIII +MMMMDLXXVIII +MMDCCCCLX +MMMCCCLXXXX +MMMCDXXVI +MMMDLVIII +CCCLXI +MMMMDCXXII +MMDCCCXXI +MMDCCXIII +MMMMCLXXXVI +MDCCCCXXVI +MDV +MMDCCCCLXXVI +MMMMCCXXXVII +MMMDCCLXXVIIII +MMMCCCCLXVII +DCCXLI +MMCLXXXVIII +MCCXXXVI +MMDCXLVIII +MMMMCXXXII +MMMMDCCLXVI +MMMMCMLI +MMMMCLXV +MMMMDCCCXCIV +MCCLXXVII +LXXVIIII +DCCLII +MMMCCCXCVI +MMMCLV +MMDCCCXXXXVIII +DCCCXV +MXC +MMDCCLXXXXVII +MMMMCML +MMDCCCLXXVIII +DXXI +MCCCXLI +DCLXXXXI +MMCCCLXXXXVIII +MDCCCCLXXVIII +MMMMDXXV +MMMDCXXXVI +MMMCMXCVII +MMXVIIII +MMMDCCLXXIV +MMMCXXV +DXXXVIII +MMMMCLXVI +MDXII +MMCCCLXX +CCLXXI +DXIV +MMMCLIII +DLII +MMMCCCXLIX +MMCCCCXXVI +MMDCXLIII +MXXXXII +CCCLXXXV +MDCLXXVI +MDCXII +MMMCCCLXXXIII +MMDCCCCLXXXII +MMMMCCCLXXXV +MMDCXXI +DCCCXXX +MMMDCCCCLII +MMMDCCXXII +MMMMCDXCVIII +MMMCCLXVIIII +MMXXV +MMMMCDXIX +MMMMCCCX +MMMCCCCLXVI +MMMMDCLXXVIIII +MMMMDCXXXXIV +MMMCMXII +MMMMXXXIII +MMMMDLXXXII +DCCCLIV +MDXVIIII +MMMCLXXXXV +CCCCXX +MMDIX +MMCMLXXXVIII +DCCXLIII +DCCLX +D +MCCCVII +MMMMCCCLXXXIII +MDCCCLXXIIII +MMMDCCCCLXXXVII +MMMMCCCVII +MMMDCCLXXXXVI +CDXXXIV +MCCLXVIII +MMMMDLX +MMMMDXII +MMMMCCCCLIIII +MCMLXXXXIII +MMMMDCCCIII +MMDCLXXXIII +MDCCCXXXXIV +XXXXVII +MMMDCCCXXXII +MMMDCCCXLII +MCXXXV +MDCXXVIIII +MMMCXXXXIIII +MMMMCDXVII +MMMDXXIII +MMMMCCCCLXI +DCLXXXXVIIII +LXXXXI +CXXXIII +MCDX +MCCLVII +MDCXXXXII +MMMCXXIV +MMMMLXXXX +MMDCCCCXLV +MLXXX +MMDCCCCLX +MCDLIII +MMMCCCLXVII +MMMMCCCLXXIV +MMMDCVIII +DCCCCXXIII +MMXCI +MMDCCIV +MMMMDCCCXXXIV +CCCLXXI +MCCLXXXII +MCMIII +CCXXXI +DCCXXXVIII +MMMMDCCXLVIIII +MMMMCMXXXV +DCCCLXXV +DCCXCI +MMMMDVII +MMMMDCCCLXVIIII +CCCXCV +MMMMDCCXX +MCCCCII +MMMCCCXC +MMMCCCII +MMDCCLXXVII +MMDCLIIII +CCXLIII +MMMDCXVIII +MMMCCCIX +MCXV +MMCCXXV +MLXXIIII +MDCCXXVI +MMMCCCXX +MMDLXX +MMCCCCVI +MMDCCXX +MMMMDCCCCXCV +MDCCCXXXII +MMMMDCCCCXXXX +XCIV +MMCCCCLX +MMXVII +MLXXI +MMMDXXVIII +MDCCCCII +MMMCMLVII +MMCLXXXXVIII +MDCCCCLV +MCCCCLXXIIII +MCCCLII +MCDXLVI +MMMMDXVIII +DCCLXXXIX +MMMDCCLXIV +MDCCCCXLIII +CLXXXXV +MMMMCCXXXVI +MMMDCCCXXI +MMMMCDLXXVII +MCDLIII +MMCCXLVI +DCCCLV +MCDLXX +DCLXXVIII +MMDCXXXIX +MMMMDCLX +MMDCCLI +MMCXXXV +MMMCCXII +MMMMCMLXII +MMMMCCV +MCCCCLXIX +MMMMCCIII +CLXVII +MCCCLXXXXIIII +MMMMDCVIII +MMDCCCLXI +MMLXXIX +CMLXIX +MMDCCCXLVIIII +DCLXII +MMMCCCXLVII +MDCCCXXXV +MMMMDCCXCVI +DCXXX +XXVI +MMLXIX +MMCXI +DCXXXVII +MMMMCCCXXXXVIII +MMMMDCLXI +MMMMDCLXXIIII +MMMMVIII +MMMMDCCCLXII +MDCXCI +MMCCCXXIIII +CCCCXXXXV +MMDCCCXXI +MCVI +MMDCCLXVIII +MMMMCXL +MLXVIII +CMXXVII +CCCLV +MDCCLXXXIX +MMMCCCCLXV +MMDCCLXII +MDLXVI +MMMCCCXVIII +MMMMCCLXXXI +MMCXXVII +MMDCCCLXVIII +MMMCXCII +MMMMDCLVIII +MMMMDCCCXXXXII +MMDCCCCLXXXXVI +MDCCXL +MDCCLVII +MMMMDCCCLXXXVI +DCCXXXIII +MMMMDCCCCLXXXV +MMCCXXXXVIII +MMMCCLXXVIII +MMMDCLXXVIII +DCCCI +MMMMLXXXXVIIII +MMMCCCCLXXII +MMCLXXXVII +CCLXVI +MCDXLIII +MMCXXVIII +MDXIV +CCCXCVIII +CLXXVIII +MMCXXXXVIIII +MMMDCLXXXIV +CMLVIII +MCDLIX +MMMMDCCCXXXII +MMMMDCXXXIIII +MDCXXI +MMMDCXLV +MCLXXVIII +MCDXXII +IV +MCDLXXXXIII +MMMMDCCLXV +CCLI +MMMMDCCCXXXVIII +DCLXII +MCCCLXVII +MMMMDCCCXXXVI +MMDCCXLI +MLXI +MMMCDLXVIII +MCCCCXCIII +XXXIII +MMMDCLXIII +MMMMDCL +DCCCXXXXIIII +MMDLVII +DXXXVII +MCCCCXXIIII +MCVII +MMMMDCCXL +MMMMCXXXXIIII +MCCCCXXIV +MMCLXVIII +MMXCIII +MDCCLXXX +MCCCLIIII +MMDCLXXI +MXI +MCMLIV +MMMCCIIII +DCCLXXXVIIII +MDCLIV +MMMDCXIX +CMLXXXI +DCCLXXXVII +XXV +MMMXXXVI +MDVIIII +CLXIII +MMMCDLVIIII +MMCCCCVII +MMMLXX +MXXXXII +MMMMCCCLXVIII +MMDCCCXXVIII +MMMMDCXXXXI +MMMMDCCCXXXXV +MMMXV +MMMMCCXVIIII +MMDCCXIIII +MMMXXVII +MDCCLVIIII +MMCXXIIII +MCCCLXXIV +DCLVIII +MMMLVII +MMMCXLV +MMXCVII +MMMCCCLXXXVII +MMMMCCXXII +DXII +MMMDLV +MCCCLXXVIII +MMMCLIIII +MMMMCLXXXX +MMMCLXXXIIII +MDCXXIII +MMMMCCXVI +MMMMDLXXXIII +MMMDXXXXIII +MMMMCCCCLV +MMMDLXXXI +MMMCCLXXVI +MMMMXX +MMMMDLVI +MCCCCLXXX +MMMXXII +MMXXII +MMDCCCCXXXI +MMMDXXV +MMMDCLXXXVIIII +MMMDLXXXXVII +MDLXIIII +CMXC +MMMXXXVIII +MDLXXXVIII +MCCCLXXVI +MMCDLIX +MMDCCCXVIII +MDCCCXXXXVI +MMMMCMIV +MMMMDCIIII +MMCCXXXV +XXXXVI +MMMMCCXVII +MMCCXXIV +MCMLVIIII +MLXXXIX +MMMMLXXXIX +CLXXXXIX +MMMDCCCCLVIII +MMMMCCLXXIII +MCCCC +DCCCLIX +MMMCCCLXXXII +MMMCCLXVIIII +MCLXXXV +CDLXXXVII +DCVI +MMX +MMCCXIII +MMMMDCXX +MMMMXXVIII +DCCCLXII +MMMMCCCXLIII +MMMMCLXV +DXCI +MMMMCLXXX +MMMDCCXXXXI +MMMMXXXXVI +DCLX +MMMCCCXI +MCCLXXX +MMCDLXXII +DCCLXXI +MMMCCCXXXVI +MCCCCLXXXVIIII +CDLVIII +DCCLVI +MMMMDCXXXVIII +MMCCCLXXXIII +MMMMDCCLXXV +MMMXXXVI +CCCLXXXXIX +CV +CCCCXIII +CCCCXVI +MDCCCLXXXIIII +MMDCCLXXXII +MMMMCCCCLXXXI +MXXV +MMCCCLXXVIIII +MMMCCXII +MMMMCCXXXIII +MMCCCLXXXVI +MMMDCCCLVIIII +MCCXXXVII +MDCLXXV +XXXV +MMDLI +MMMCCXXX +MMMMCXXXXV +CCCCLIX +MMMMDCCCLXXIII +MMCCCXVII +DCCCXVI +MMMCCCXXXXV +MDCCCCXCV +CLXXXI +MMMMDCCLXX +MMMDCCCIII +MMCLXXVII +MMMDCCXXIX +MMDCCCXCIIII +MMMCDXXIIII +MMMMXXVIII +MMMMDCCCCLXVIII +MDCCCXX +MMMMCDXXI +MMMMDLXXXIX +CCXVI +MDVIII +MMCCLXXI +MMMDCCCLXXI +MMMCCCLXXVI +MMCCLXI +MMMMDCCCXXXIV +DLXXXVI +MMMMDXXXII +MMMXXIIII +MMMMCDIV +MMMMCCCXLVIII +MMMMCXXXVIII +MMMCCCLXVI +MDCCXVIII +MMCXX +CCCLIX +MMMMDCCLXXII +MDCCCLXXV +MMMMDCCCXXIV +DCCCXXXXVIII +MMMDCCCCXXXVIIII +MMMMCCXXXV +MDCLXXXIII +MMCCLXXXIV +MCLXXXXIIII +DXXXXIII +MCCCXXXXVIII +MMCLXXIX +MMMMCCLXIV +MXXII +MMMCXIX +MDCXXXVII +MMDCCVI +MCLXXXXVIII +MMMCXVI +MCCCLX +MMMCDX +CCLXVIIII +MMMCCLX +MCXXVIII +LXXXII +MCCCCLXXXI +MMMI +MMMCCCLXIV +MMMCCCXXVIIII +CXXXVIII +MMCCCXX +MMMCCXXVIIII +MCCLXVI +MMMCCCCXXXXVI +MMDCCXCIX +MCMLXXI +MMCCLXVIII +CDLXXXXIII +MMMMDCCXXII +MMMMDCCLXXXVII +MMMDCCLIV +MMCCLXIII +MDXXXVII +DCCXXXIIII +MCII +MMMDCCCLXXI +MMMLXXIII +MDCCCLIII +MMXXXVIII +MDCCXVIIII +MDCCCCXXXVII +MMCCCXVI +MCMXXII +MMMCCCLVIII +MMMMDCCCXX +MCXXIII +MMMDLXI +MMMMDXXII +MDCCCX +MMDXCVIIII +MMMDCCCCVIII +MMMMDCCCCXXXXVI +MMDCCCXXXV +MMCXCIV +MCMLXXXXIII +MMMCCCLXXVI +MMMMDCLXXXV +CMLXIX +DCXCII +MMXXVIII +MMMMCCCXXX +XXXXVIIII \ No newline at end of file diff --git a/project_euler/problem_089/sol1.py b/project_euler/problem_089/sol1.py new file mode 100644 index 000000000000..11582aa4ab1a --- /dev/null +++ b/project_euler/problem_089/sol1.py @@ -0,0 +1,141 @@ +""" +Project Euler Problem 89: https://projecteuler.net/problem=89 + +For a number written in Roman numerals to be considered valid there are basic rules +which must be followed. Even though the rules allow some numbers to be expressed in +more than one way there is always a "best" way of writing a particular number. + +For example, it would appear that there are at least six ways of writing the number +sixteen: + +IIIIIIIIIIIIIIII +VIIIIIIIIIII +VVIIIIII +XIIIIII +VVVI +XVI + +However, according to the rules only XIIIIII and XVI are valid, and the last example +is considered to be the most efficient, as it uses the least number of numerals. + +The 11K text file, roman.txt (right click and 'Save Link/Target As...'), contains one +thousand numbers written in valid, but not necessarily minimal, Roman numerals; see +About... Roman Numerals for the definitive rules for this problem. + +Find the number of characters saved by writing each of these in their minimal form. + +Note: You can assume that all the Roman numerals in the file contain no more than four +consecutive identical units. +""" + +import os + +SYMBOLS = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000} + + +def parse_roman_numerals(numerals: str) -> int: + """ + Converts a string of roman numerals to an integer. + e.g. + >>> parse_roman_numerals("LXXXIX") + 89 + >>> parse_roman_numerals("IIII") + 4 + """ + + total_value = 0 + + index = 0 + while index < len(numerals) - 1: + current_value = SYMBOLS[numerals[index]] + next_value = SYMBOLS[numerals[index + 1]] + if current_value < next_value: + total_value -= current_value + else: + total_value += current_value + index += 1 + total_value += SYMBOLS[numerals[index]] + + return total_value + + +def generate_roman_numerals(num: int) -> str: + """ + Generates a string of roman numerals for a given integer. + e.g. + >>> generate_roman_numerals(89) + 'LXXXIX' + >>> generate_roman_numerals(4) + 'IV' + """ + + numerals = "" + + m_count = num // 1000 + numerals += m_count * "M" + num %= 1000 + + c_count = num // 100 + if c_count == 9: + numerals += "CM" + c_count -= 9 + elif c_count == 4: + numerals += "CD" + c_count -= 4 + if c_count >= 5: + numerals += "D" + c_count -= 5 + numerals += c_count * "C" + num %= 100 + + x_count = num // 10 + if x_count == 9: + numerals += "XC" + x_count -= 9 + elif x_count == 4: + numerals += "XL" + x_count -= 4 + if x_count >= 5: + numerals += "L" + x_count -= 5 + numerals += x_count * "X" + num %= 10 + + if num == 9: + numerals += "IX" + num -= 9 + elif num == 4: + numerals += "IV" + num -= 4 + if num >= 5: + numerals += "V" + num -= 5 + numerals += num * "I" + + return numerals + + +def solution(roman_numerals_filename: str = "/p089_roman.txt") -> int: + """ + Calculates and returns the answer to project euler problem 89. + + >>> solution("/numeralcleanup_test.txt") + 16 + """ + + savings = 0 + + file1 = open(os.path.dirname(__file__) + roman_numerals_filename, "r") + lines = file1.readlines() + for line in lines: + original = line.strip() + num = parse_roman_numerals(original) + shortened = generate_roman_numerals(num) + savings += len(original) - len(shortened) + + return savings + + +if __name__ == "__main__": + + print(f"{solution() = }") diff --git a/project_euler/problem_101/__init__.py b/project_euler/problem_101/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_101/sol1.py b/project_euler/problem_101/sol1.py new file mode 100644 index 000000000000..e66316090fb2 --- /dev/null +++ b/project_euler/problem_101/sol1.py @@ -0,0 +1,219 @@ +""" +If we are presented with the first k terms of a sequence it is impossible to say with +certainty the value of the next term, as there are infinitely many polynomial functions +that can model the sequence. + +As an example, let us consider the sequence of cube +numbers. This is defined by the generating function, +u(n) = n3: 1, 8, 27, 64, 125, 216, ... + +Suppose we were only given the first two terms of this sequence. Working on the +principle that "simple is best" we should assume a linear relationship and predict the +next term to be 15 (common difference 7). Even if we were presented with the first three +terms, by the same principle of simplicity, a quadratic relationship should be +assumed. + +We shall define OP(k, n) to be the nth term of the optimum polynomial +generating function for the first k terms of a sequence. It should be clear that +OP(k, n) will accurately generate the terms of the sequence for n ≤ k, and potentially +the first incorrect term (FIT) will be OP(k, k+1); in which case we shall call it a +bad OP (BOP). + +As a basis, if we were only given the first term of sequence, it would be most +sensible to assume constancy; that is, for n ≥ 2, OP(1, n) = u(1). + +Hence we obtain the +following OPs for the cubic sequence: + +OP(1, n) = 1 1, 1, 1, 1, ... +OP(2, n) = 7n-6 1, 8, 15, ... +OP(3, n) = 6n^2-11n+6 1, 8, 27, 58, ... +OP(4, n) = n^3 1, 8, 27, 64, 125, ... + +Clearly no BOPs exist for k ≥ 4. + +By considering the sum of FITs generated by the BOPs (indicated in red above), we +obtain 1 + 15 + 58 = 74. + +Consider the following tenth degree polynomial generating function: + +1 - n + n^2 - n^3 + n^4 - n^5 + n^6 - n^7 + n^8 - n^9 + n^10 + +Find the sum of FITs for the BOPs. +""" + + +from typing import Callable, List, Union + +Matrix = List[List[Union[float, int]]] + + +def solve(matrix: Matrix, vector: Matrix) -> Matrix: + """ + Solve the linear system of equations Ax = b (A = "matrix", b = "vector") + for x using Gaussian elimination and back substitution. We assume that A + is an invertible square matrix and that b is a column vector of the + same height. + >>> solve([[1, 0], [0, 1]], [[1],[2]]) + [[1.0], [2.0]] + >>> solve([[2, 1, -1],[-3, -1, 2],[-2, 1, 2]],[[8], [-11],[-3]]) + [[2.0], [3.0], [-1.0]] + """ + size: int = len(matrix) + augmented: Matrix = [[0 for _ in range(size + 1)] for _ in range(size)] + row: int + row2: int + col: int + col2: int + pivot_row: int + ratio: float + + for row in range(size): + for col in range(size): + augmented[row][col] = matrix[row][col] + + augmented[row][size] = vector[row][0] + + row = 0 + col = 0 + while row < size and col < size: + # pivoting + pivot_row = max( + [(abs(augmented[row2][col]), row2) for row2 in range(col, size)] + )[1] + if augmented[pivot_row][col] == 0: + col += 1 + continue + else: + augmented[row], augmented[pivot_row] = augmented[pivot_row], augmented[row] + + for row2 in range(row + 1, size): + ratio = augmented[row2][col] / augmented[row][col] + augmented[row2][col] = 0 + for col2 in range(col + 1, size + 1): + augmented[row2][col2] -= augmented[row][col2] * ratio + + row += 1 + col += 1 + + # back substitution + for col in range(1, size): + for row in range(col): + ratio = augmented[row][col] / augmented[col][col] + for col2 in range(col, size + 1): + augmented[row][col2] -= augmented[col][col2] * ratio + + # round to get rid of numbers like 2.000000000000004 + return [ + [round(augmented[row][size] / augmented[row][row], 10)] for row in range(size) + ] + + +def interpolate(y_list: List[int]) -> Callable[[int], int]: + """ + Given a list of data points (1,y0),(2,y1), ..., return a function that + interpolates the data points. We find the coefficients of the interpolating + polynomial by solving a system of linear equations corresponding to + x = 1, 2, 3... + + >>> interpolate([1])(3) + 1 + >>> interpolate([1, 8])(3) + 15 + >>> interpolate([1, 8, 27])(4) + 58 + >>> interpolate([1, 8, 27, 64])(6) + 216 + """ + + size: int = len(y_list) + matrix: Matrix = [[0 for _ in range(size)] for _ in range(size)] + vector: Matrix = [[0] for _ in range(size)] + coeffs: Matrix + x_val: int + y_val: int + col: int + + for x_val, y_val in enumerate(y_list): + for col in range(size): + matrix[x_val][col] = (x_val + 1) ** (size - col - 1) + vector[x_val][0] = y_val + + coeffs = solve(matrix, vector) + + def interpolated_func(var: int) -> int: + """ + >>> interpolate([1])(3) + 1 + >>> interpolate([1, 8])(3) + 15 + >>> interpolate([1, 8, 27])(4) + 58 + >>> interpolate([1, 8, 27, 64])(6) + 216 + """ + return sum( + round(coeffs[x_val][0]) * (var ** (size - x_val - 1)) + for x_val in range(size) + ) + + return interpolated_func + + +def question_function(variable: int) -> int: + """ + The generating function u as specified in the question. + >>> question_function(0) + 1 + >>> question_function(1) + 1 + >>> question_function(5) + 8138021 + >>> question_function(10) + 9090909091 + """ + return ( + 1 + - variable + + variable ** 2 + - variable ** 3 + + variable ** 4 + - variable ** 5 + + variable ** 6 + - variable ** 7 + + variable ** 8 + - variable ** 9 + + variable ** 10 + ) + + +def solution(func: Callable[[int], int] = question_function, order: int = 10) -> int: + """ + Find the sum of the FITs of the BOPS. For each interpolating polynomial of order + 1, 2, ... , 10, find the first x such that the value of the polynomial at x does + not equal u(x). + >>> solution(lambda n: n ** 3, 3) + 74 + """ + data_points: List[int] = [func(x_val) for x_val in range(1, order + 1)] + + polynomials: List[Callable[[int], int]] = [ + interpolate(data_points[:max_coeff]) for max_coeff in range(1, order + 1) + ] + + ret: int = 0 + poly: int + x_val: int + + for poly in polynomials: + x_val = 1 + while func(x_val) == poly(x_val): + x_val += 1 + + ret += poly(x_val) + + return ret + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_102/__init__.py b/project_euler/problem_102/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_102/p102_triangles.txt b/project_euler/problem_102/p102_triangles.txt new file mode 100644 index 000000000000..3f01a1ac1f41 --- /dev/null +++ b/project_euler/problem_102/p102_triangles.txt @@ -0,0 +1,1000 @@ +-340,495,-153,-910,835,-947 +-175,41,-421,-714,574,-645 +-547,712,-352,579,951,-786 +419,-864,-83,650,-399,171 +-429,-89,-357,-930,296,-29 +-734,-702,823,-745,-684,-62 +-971,762,925,-776,-663,-157 +162,570,628,485,-807,-896 +641,91,-65,700,887,759 +215,-496,46,-931,422,-30 +-119,359,668,-609,-358,-494 +440,929,968,214,760,-857 +-700,785,838,29,-216,411 +-770,-458,-325,-53,-505,633 +-752,-805,349,776,-799,687 +323,5,561,-36,919,-560 +-907,358,264,320,204,274 +-728,-466,350,969,292,-345 +940,836,272,-533,748,185 +411,998,813,520,316,-949 +-152,326,658,-762,148,-651 +330,507,-9,-628,101,174 +551,-496,772,-541,-702,-45 +-164,-489,-90,322,631,-59 +673,366,-4,-143,-606,-704 +428,-609,801,-449,740,-269 +453,-924,-785,-346,-853,111 +-738,555,-181,467,-426,-20 +958,-692,784,-343,505,-569 +620,27,263,54,-439,-726 +804,87,998,859,871,-78 +-119,-453,-709,-292,-115,-56 +-626,138,-940,-476,-177,-274 +-11,160,142,588,446,158 +538,727,550,787,330,810 +420,-689,854,-546,337,516 +872,-998,-607,748,473,-192 +653,440,-516,-985,808,-857 +374,-158,331,-940,-338,-641 +137,-925,-179,771,734,-715 +-314,198,-115,29,-641,-39 +759,-574,-385,355,590,-603 +-189,-63,-168,204,289,305 +-182,-524,-715,-621,911,-255 +331,-816,-833,471,168,126 +-514,581,-855,-220,-731,-507 +129,169,576,651,-87,-458 +783,-444,-881,658,-266,298 +603,-430,-598,585,368,899 +43,-724,962,-376,851,409 +-610,-646,-883,-261,-482,-881 +-117,-237,978,641,101,-747 +579,125,-715,-712,208,534 +672,-214,-762,372,874,533 +-564,965,38,715,367,242 +500,951,-700,-981,-61,-178 +-382,-224,-959,903,-282,-60 +-355,295,426,-331,-591,655 +892,128,958,-271,-993,274 +-454,-619,302,138,-790,-874 +-642,601,-574,159,-290,-318 +266,-109,257,-686,54,975 +162,628,-478,840,264,-266 +466,-280,982,1,904,-810 +721,839,730,-807,777,981 +-129,-430,748,263,943,96 +434,-94,410,-990,249,-704 +237,42,122,-732,44,-51 +909,-116,-229,545,292,717 +824,-768,-807,-370,-262,30 +675,58,332,-890,-651,791 +363,825,-717,254,684,240 +405,-715,900,166,-589,422 +-476,686,-830,-319,634,-807 +633,837,-971,917,-764,207 +-116,-44,-193,-70,908,809 +-26,-252,998,408,70,-713 +-601,645,-462,842,-644,-591 +-160,653,274,113,-138,687 +369,-273,-181,925,-167,-693 +-338,135,480,-967,-13,-840 +-90,-270,-564,695,161,907 +607,-430,869,-713,461,-469 +919,-165,-776,522,606,-708 +-203,465,288,207,-339,-458 +-453,-534,-715,975,838,-677 +-973,310,-350,934,546,-805 +-835,385,708,-337,-594,-772 +-14,914,900,-495,-627,594 +833,-713,-213,578,-296,699 +-27,-748,484,455,915,291 +270,889,739,-57,442,-516 +119,811,-679,905,184,130 +-678,-469,925,553,612,482 +101,-571,-732,-842,644,588 +-71,-737,566,616,957,-663 +-634,-356,90,-207,936,622 +598,443,964,-895,-58,529 +847,-467,929,-742,91,10 +-633,829,-780,-408,222,-30 +-818,57,275,-38,-746,198 +-722,-825,-549,597,-391,99 +-570,908,430,873,-103,-360 +342,-681,512,434,542,-528 +297,850,479,609,543,-357 +9,784,212,548,56,859 +-152,560,-240,-969,-18,713 +140,-133,34,-635,250,-163 +-272,-22,-169,-662,989,-604 +471,-765,355,633,-742,-118 +-118,146,942,663,547,-376 +583,16,162,264,715,-33 +-230,-446,997,-838,561,555 +372,397,-729,-318,-276,649 +92,982,-970,-390,-922,922 +-981,713,-951,-337,-669,670 +-999,846,-831,-504,7,-128 +455,-954,-370,682,-510,45 +822,-960,-892,-385,-662,314 +-668,-686,-367,-246,530,-341 +-723,-720,-926,-836,-142,757 +-509,-134,384,-221,-873,-639 +-803,-52,-706,-669,373,-339 +933,578,631,-616,770,555 +741,-564,-33,-605,-576,275 +-715,445,-233,-730,734,-704 +120,-10,-266,-685,-490,-17 +-232,-326,-457,-946,-457,-116 +811,52,639,826,-200,147 +-329,279,293,612,943,955 +-721,-894,-393,-969,-642,453 +-688,-826,-352,-75,371,79 +-809,-979,407,497,858,-248 +-485,-232,-242,-582,-81,849 +141,-106,123,-152,806,-596 +-428,57,-992,811,-192,478 +864,393,122,858,255,-876 +-284,-780,240,457,354,-107 +956,605,-477,44,26,-678 +86,710,-533,-815,439,327 +-906,-626,-834,763,426,-48 +201,-150,-904,652,475,412 +-247,149,81,-199,-531,-148 +923,-76,-353,175,-121,-223 +427,-674,453,472,-410,585 +931,776,-33,85,-962,-865 +-655,-908,-902,208,869,792 +-316,-102,-45,-436,-222,885 +-309,768,-574,653,745,-975 +896,27,-226,993,332,198 +323,655,-89,260,240,-902 +501,-763,-424,793,813,616 +993,375,-938,-621,672,-70 +-880,-466,-283,770,-824,143 +63,-283,886,-142,879,-116 +-964,-50,-521,-42,-306,-161 +724,-22,866,-871,933,-383 +-344,135,282,966,-80,917 +-281,-189,420,810,362,-582 +-515,455,-588,814,162,332 +555,-436,-123,-210,869,-943 +589,577,232,286,-554,876 +-773,127,-58,-171,-452,125 +-428,575,906,-232,-10,-224 +437,276,-335,-348,605,878 +-964,511,-386,-407,168,-220 +307,513,912,-463,-423,-416 +-445,539,273,886,-18,760 +-396,-585,-670,414,47,364 +143,-506,754,906,-971,-203 +-544,472,-180,-541,869,-465 +-779,-15,-396,890,972,-220 +-430,-564,503,182,-119,456 +89,-10,-739,399,506,499 +954,162,-810,-973,127,870 +890,952,-225,158,828,237 +-868,952,349,465,574,750 +-915,369,-975,-596,-395,-134 +-135,-601,575,582,-667,640 +413,890,-560,-276,-555,-562 +-633,-269,561,-820,-624,499 +371,-92,-784,-593,864,-717 +-971,655,-439,367,754,-951 +172,-347,36,279,-247,-402 +633,-301,364,-349,-683,-387 +-780,-211,-713,-948,-648,543 +72,58,762,-465,-66,462 +78,502,781,-832,713,836 +-431,-64,-484,-392,208,-343 +-64,101,-29,-860,-329,844 +398,391,828,-858,700,395 +578,-896,-326,-604,314,180 +97,-321,-695,185,-357,852 +854,839,283,-375,951,-209 +194,96,-564,-847,162,524 +-354,532,494,621,580,560 +419,-678,-450,926,-5,-924 +-661,905,519,621,-143,394 +-573,268,296,-562,-291,-319 +-211,266,-196,158,564,-183 +18,-585,-398,777,-581,864 +790,-894,-745,-604,-418,70 +848,-339,150,773,11,851 +-954,-809,-53,-20,-648,-304 +658,-336,-658,-905,853,407 +-365,-844,350,-625,852,-358 +986,-315,-230,-159,21,180 +-15,599,45,-286,-941,847 +-613,-68,184,639,-987,550 +334,675,-56,-861,923,340 +-848,-596,960,231,-28,-34 +707,-811,-994,-356,-167,-171 +-470,-764,72,576,-600,-204 +379,189,-542,-576,585,800 +440,540,-445,-563,379,-334 +-155,64,514,-288,853,106 +-304,751,481,-520,-708,-694 +-709,132,594,126,-844,63 +723,471,421,-138,-962,892 +-440,-263,39,513,-672,-954 +775,809,-581,330,752,-107 +-376,-158,335,-708,-514,578 +-343,-769,456,-187,25,413 +548,-877,-172,300,-500,928 +938,-102,423,-488,-378,-969 +-36,564,-55,131,958,-800 +-322,511,-413,503,700,-847 +-966,547,-88,-17,-359,-67 +637,-341,-437,-181,527,-153 +-74,449,-28,3,485,189 +-997,658,-224,-948,702,-807 +-224,736,-896,127,-945,-850 +-395,-106,439,-553,-128,124 +-841,-445,-758,-572,-489,212 +633,-327,13,-512,952,771 +-940,-171,-6,-46,-923,-425 +-142,-442,-817,-998,843,-695 +340,847,-137,-920,-988,-658 +-653,217,-679,-257,651,-719 +-294,365,-41,342,74,-892 +690,-236,-541,494,408,-516 +180,-807,225,790,494,59 +707,605,-246,656,284,271 +65,294,152,824,442,-442 +-321,781,-540,341,316,415 +420,371,-2,545,995,248 +56,-191,-604,971,615,449 +-981,-31,510,592,-390,-362 +-317,-968,913,365,97,508 +832,63,-864,-510,86,202 +-483,456,-636,340,-310,676 +981,-847,751,-508,-962,-31 +-157,99,73,797,63,-172 +220,858,872,924,866,-381 +996,-169,805,321,-164,971 +896,11,-625,-973,-782,76 +578,-280,730,-729,307,-905 +-580,-749,719,-698,967,603 +-821,874,-103,-623,662,-491 +-763,117,661,-644,672,-607 +592,787,-798,-169,-298,690 +296,644,-526,-762,-447,665 +534,-818,852,-120,57,-379 +-986,-549,-329,294,954,258 +-133,352,-660,-77,904,-356 +748,343,215,500,317,-277 +311,7,910,-896,-809,795 +763,-602,-753,313,-352,917 +668,619,-474,-597,-650,650 +-297,563,-701,-987,486,-902 +-461,-740,-657,233,-482,-328 +-446,-250,-986,-458,-629,520 +542,-49,-327,-469,257,-947 +121,-575,-634,-143,-184,521 +30,504,455,-645,-229,-945 +-12,-295,377,764,771,125 +-686,-133,225,-25,-376,-143 +-6,-46,338,270,-405,-872 +-623,-37,582,467,963,898 +-804,869,-477,420,-475,-303 +94,41,-842,-193,-768,720 +-656,-918,415,645,-357,460 +-47,-486,-911,468,-608,-686 +-158,251,419,-394,-655,-895 +272,-695,979,508,-358,959 +-776,650,-918,-467,-690,-534 +-85,-309,-626,167,-366,-429 +-880,-732,-186,-924,970,-875 +517,645,-274,962,-804,544 +721,402,104,640,478,-499 +198,684,-134,-723,-452,-905 +-245,745,239,238,-826,441 +-217,206,-32,462,-981,-895 +-51,989,526,-173,560,-676 +-480,-659,-976,-580,-727,466 +-996,-90,-995,158,-239,642 +302,288,-194,-294,17,924 +-943,969,-326,114,-500,103 +-619,163,339,-880,230,421 +-344,-601,-795,557,565,-779 +590,345,-129,-202,-125,-58 +-777,-195,159,674,775,411 +-939,312,-665,810,121,855 +-971,254,712,815,452,581 +442,-9,327,-750,61,757 +-342,869,869,-160,390,-772 +620,601,565,-169,-69,-183 +-25,924,-817,964,321,-970 +-64,-6,-133,978,825,-379 +601,436,-24,98,-115,940 +-97,502,614,-574,922,513 +-125,262,-946,695,99,-220 +429,-721,719,-694,197,-558 +326,689,-70,-908,-673,338 +-468,-856,-902,-254,-358,305 +-358,530,542,355,-253,-47 +-438,-74,-362,963,988,788 +137,717,467,622,319,-380 +-86,310,-336,851,918,-288 +721,395,646,-53,255,-425 +255,175,912,84,-209,878 +-632,-485,-400,-357,991,-608 +235,-559,992,-297,857,-591 +87,-71,148,130,647,578 +-290,-584,-639,-788,-21,592 +386,984,625,-731,-993,-336 +-538,634,-209,-828,-150,-774 +-754,-387,607,-781,976,-199 +412,-798,-664,295,709,-537 +-412,932,-880,-232,561,852 +-656,-358,-198,-964,-433,-848 +-762,-668,-632,186,-673,-11 +-876,237,-282,-312,-83,682 +403,73,-57,-436,-622,781 +-587,873,798,976,-39,329 +-369,-622,553,-341,817,794 +-108,-616,920,-849,-679,96 +290,-974,234,239,-284,-321 +-22,394,-417,-419,264,58 +-473,-551,69,923,591,-228 +-956,662,-113,851,-581,-794 +-258,-681,413,-471,-637,-817 +-866,926,992,-653,-7,794 +556,-350,602,917,831,-610 +188,245,-906,361,492,174 +-720,384,-818,329,638,-666 +-246,846,890,-325,-59,-850 +-118,-509,620,-762,-256,15 +-787,-536,-452,-338,-399,813 +458,560,525,-311,-608,-419 +494,-811,-825,-127,-812,894 +-801,890,-629,-860,574,925 +-709,-193,-213,138,-410,-403 +861,91,708,-187,5,-222 +789,646,777,154,90,-49 +-267,-830,-114,531,591,-698 +-126,-82,881,-418,82,652 +-894,130,-726,-935,393,-815 +-142,563,654,638,-712,-597 +-759,60,-23,977,100,-765 +-305,595,-570,-809,482,762 +-161,-267,53,963,998,-529 +-300,-57,798,353,703,486 +-990,696,-764,699,-565,719 +-232,-205,566,571,977,369 +740,865,151,-817,-204,-293 +94,445,-768,229,537,-406 +861,620,37,-424,-36,656 +390,-369,952,733,-464,569 +-482,-604,959,554,-705,-626 +-396,-615,-991,108,272,-723 +143,780,535,142,-917,-147 +138,-629,-217,-908,905,115 +915,103,-852,64,-468,-642 +570,734,-785,-268,-326,-759 +738,531,-332,586,-779,24 +870,440,-217,473,-383,415 +-296,-333,-330,-142,-924,950 +118,120,-35,-245,-211,-652 +61,634,153,-243,838,789 +726,-582,210,105,983,537 +-313,-323,758,234,29,848 +-847,-172,-593,733,-56,617 +54,255,-512,156,-575,675 +-873,-956,-148,623,95,200 +700,-370,926,649,-978,157 +-639,-202,719,130,747,222 +194,-33,955,943,505,114 +-226,-790,28,-930,827,783 +-392,-74,-28,714,218,-612 +209,626,-888,-683,-912,495 +487,751,614,933,631,445 +-348,-34,-411,-106,835,321 +-689,872,-29,-800,312,-542 +-52,566,827,570,-862,-77 +471,992,309,-402,389,912 +24,520,-83,-51,555,503 +-265,-317,283,-970,-472,690 +606,526,137,71,-651,150 +217,-518,663,66,-605,-331 +-562,232,-76,-503,205,-323 +842,-521,546,285,625,-186 +997,-927,344,909,-546,974 +-677,419,81,121,-705,771 +719,-379,-944,-797,784,-155 +-378,286,-317,-797,-111,964 +-288,-573,784,80,-532,-646 +-77,407,-248,-797,769,-816 +-24,-637,287,-858,-927,-333 +-902,37,894,-823,141,684 +125,467,-177,-516,686,399 +-321,-542,641,-590,527,-224 +-400,-712,-876,-208,632,-543 +-676,-429,664,-242,-269,922 +-608,-273,-141,930,687,380 +786,-12,498,494,310,326 +-739,-617,606,-960,804,188 +384,-368,-243,-350,-459,31 +-550,397,320,-868,328,-279 +969,-179,853,864,-110,514 +910,793,302,-822,-285,488 +-605,-128,218,-283,-17,-227 +16,324,667,708,750,3 +485,-813,19,585,71,930 +-218,816,-687,-97,-732,-360 +-497,-151,376,-23,3,315 +-412,-989,-610,-813,372,964 +-878,-280,87,381,-311,69 +-609,-90,-731,-679,150,585 +889,27,-162,605,75,-770 +448,617,-988,0,-103,-504 +-800,-537,-69,627,608,-668 +534,686,-664,942,830,920 +-238,775,495,932,-793,497 +-343,958,-914,-514,-691,651 +568,-136,208,359,728,28 +286,912,-794,683,556,-102 +-638,-629,-484,445,-64,-497 +58,505,-801,-110,872,632 +-390,777,353,267,976,369 +-993,515,105,-133,358,-572 +964,996,355,-212,-667,38 +-725,-614,-35,365,132,-196 +237,-536,-416,-302,312,477 +-664,574,-210,224,48,-925 +869,-261,-256,-240,-3,-698 +712,385,32,-34,916,-315 +895,-409,-100,-346,728,-624 +-806,327,-450,889,-781,-939 +-586,-403,698,318,-939,899 +557,-57,-920,659,333,-51 +-441,232,-918,-205,246,1 +783,167,-797,-595,245,-736 +-36,-531,-486,-426,-813,-160 +777,-843,817,313,-228,-572 +735,866,-309,-564,-81,190 +-413,645,101,719,-719,218 +-83,164,767,796,-430,-459 +122,779,-15,-295,-96,-892 +462,379,70,548,834,-312 +-630,-534,124,187,-737,114 +-299,-604,318,-591,936,826 +-879,218,-642,-483,-318,-866 +-691,62,-658,761,-895,-854 +-822,493,687,569,910,-202 +-223,784,304,-5,541,925 +-914,541,737,-662,-662,-195 +-622,615,414,358,881,-878 +339,745,-268,-968,-280,-227 +-364,855,148,-709,-827,472 +-890,-532,-41,664,-612,577 +-702,-859,971,-722,-660,-920 +-539,-605,737,149,973,-802 +800,42,-448,-811,152,511 +-933,377,-110,-105,-374,-937 +-766,152,482,120,-308,390 +-568,775,-292,899,732,890 +-177,-317,-502,-259,328,-511 +612,-696,-574,-660,132,31 +-119,563,-805,-864,179,-672 +425,-627,183,-331,839,318 +-711,-976,-749,152,-916,261 +181,-63,497,211,262,406 +-537,700,-859,-765,-928,77 +892,832,231,-749,-82,613 +816,216,-642,-216,-669,-912 +-6,624,-937,-370,-344,268 +737,-710,-869,983,-324,-274 +565,952,-547,-158,374,-444 +51,-683,645,-845,515,636 +-953,-631,114,-377,-764,-144 +-8,470,-242,-399,-675,-730 +-540,689,-20,47,-607,590 +-329,-710,-779,942,-388,979 +123,829,674,122,203,563 +46,782,396,-33,386,610 +872,-846,-523,-122,-55,-190 +388,-994,-525,974,127,596 +781,-680,796,-34,-959,-62 +-749,173,200,-384,-745,-446 +379,618,136,-250,-224,970 +-58,240,-921,-760,-901,-626 +366,-185,565,-100,515,688 +489,999,-893,-263,-637,816 +838,-496,-316,-513,419,479 +107,676,-15,882,98,-397 +-999,941,-903,-424,670,-325 +171,-979,835,178,169,-984 +-609,-607,378,-681,184,402 +-316,903,-575,-800,224,983 +591,-18,-460,551,-167,918 +-756,405,-117,441,163,-320 +456,24,6,881,-836,-539 +-489,-585,915,651,-892,-382 +-177,-122,73,-711,-386,591 +181,724,530,686,-131,241 +737,288,886,216,233,33 +-548,-386,-749,-153,-85,-982 +-835,227,904,160,-99,25 +-9,-42,-162,728,840,-963 +217,-763,870,771,47,-846 +-595,808,-491,556,337,-900 +-134,281,-724,441,-134,708 +-789,-508,651,-962,661,315 +-839,-923,339,402,41,-487 +300,-790,48,703,-398,-811 +955,-51,462,-685,960,-717 +910,-880,592,-255,-51,-776 +-885,169,-793,368,-565,458 +-905,940,-492,-630,-535,-988 +245,797,763,869,-82,550 +-310,38,-933,-367,-650,824 +-95,32,-83,337,226,990 +-218,-975,-191,-208,-785,-293 +-672,-953,517,-901,-247,465 +681,-148,261,-857,544,-923 +640,341,446,-618,195,769 +384,398,-846,365,671,815 +578,576,-911,907,762,-859 +548,-428,144,-630,-759,-146 +710,-73,-700,983,-97,-889 +-46,898,-973,-362,-817,-717 +151,-81,-125,-900,-478,-154 +483,615,-537,-932,181,-68 +786,-223,518,25,-306,-12 +-422,268,-809,-683,635,468 +983,-734,-694,-608,-110,4 +-786,-196,749,-354,137,-8 +-181,36,668,-200,691,-973 +-629,-838,692,-736,437,-871 +-208,-536,-159,-596,8,197 +-3,370,-686,170,913,-376 +44,-998,-149,-993,-200,512 +-519,136,859,497,536,434 +77,-985,972,-340,-705,-837 +-381,947,250,360,344,322 +-26,131,699,750,707,384 +-914,655,299,193,406,955 +-883,-921,220,595,-546,794 +-599,577,-569,-404,-704,489 +-594,-963,-624,-460,880,-760 +-603,88,-99,681,55,-328 +976,472,139,-453,-531,-860 +192,-290,513,-89,666,432 +417,487,575,293,567,-668 +655,711,-162,449,-980,972 +-505,664,-685,-239,603,-592 +-625,-802,-67,996,384,-636 +365,-593,522,-666,-200,-431 +-868,708,560,-860,-630,-355 +-702,785,-637,-611,-597,960 +-137,-696,-93,-803,408,406 +891,-123,-26,-609,-610,518 +133,-832,-198,555,708,-110 +791,617,-69,487,696,315 +-900,694,-565,517,-269,-416 +914,135,-781,600,-71,-600 +991,-915,-422,-351,-837,313 +-840,-398,-302,21,590,146 +62,-558,-702,-384,-625,831 +-363,-426,-924,-496,792,-908 +73,361,-817,-466,400,922 +-626,-164,-626,860,-524,286 +255,26,-944,809,-606,986 +-457,-256,-103,50,-867,-871 +-223,803,196,480,612,136 +-820,-928,700,780,-977,721 +717,332,53,-933,-128,793 +-602,-648,562,593,890,702 +-469,-875,-527,911,-475,-222 +110,-281,-552,-536,-816,596 +-981,654,413,-981,-75,-95 +-754,-742,-515,894,-220,-344 +795,-52,156,408,-603,76 +474,-157,423,-499,-807,-791 +260,688,40,-52,702,-122 +-584,-517,-390,-881,302,-504 +61,797,665,708,14,668 +366,166,458,-614,564,-983 +72,539,-378,796,381,-824 +-485,201,-588,842,736,379 +-149,-894,-298,705,-303,-406 +660,-935,-580,521,93,633 +-382,-282,-375,-841,-828,171 +-567,743,-100,43,144,122 +-281,-786,-749,-551,296,304 +11,-426,-792,212,857,-175 +594,143,-699,289,315,137 +341,596,-390,107,-631,-804 +-751,-636,-424,-854,193,651 +-145,384,749,675,-786,517 +224,-865,-323,96,-916,258 +-309,403,-388,826,35,-270 +-942,709,222,158,-699,-103 +-589,842,-997,29,-195,-210 +264,426,566,145,-217,623 +217,965,507,-601,-453,507 +-206,307,-982,4,64,-292 +676,-49,-38,-701,550,883 +5,-850,-438,659,745,-773 +933,238,-574,-570,91,-33 +-866,121,-928,358,459,-843 +-568,-631,-352,-580,-349,189 +-737,849,-963,-486,-662,970 +135,334,-967,-71,-365,-792 +789,21,-227,51,990,-275 +240,412,-886,230,591,256 +-609,472,-853,-754,959,661 +401,521,521,314,929,982 +-499,784,-208,71,-302,296 +-557,-948,-553,-526,-864,793 +270,-626,828,44,37,14 +-412,224,617,-593,502,699 +41,-908,81,562,-849,163 +165,917,761,-197,331,-341 +-687,314,799,755,-969,648 +-164,25,578,439,-334,-576 +213,535,874,-177,-551,24 +-689,291,-795,-225,-496,-125 +465,461,558,-118,-568,-909 +567,660,-810,46,-485,878 +-147,606,685,-690,-774,984 +568,-886,-43,854,-738,616 +-800,386,-614,585,764,-226 +-518,23,-225,-732,-79,440 +-173,-291,-689,636,642,-447 +-598,-16,227,410,496,211 +-474,-930,-656,-321,-420,36 +-435,165,-819,555,540,144 +-969,149,828,568,394,648 +65,-848,257,720,-625,-851 +981,899,275,635,465,-877 +80,290,792,760,-191,-321 +-605,-858,594,33,706,593 +585,-472,318,-35,354,-927 +-365,664,803,581,-965,-814 +-427,-238,-480,146,-55,-606 +879,-193,250,-890,336,117 +-226,-322,-286,-765,-836,-218 +-913,564,-667,-698,937,283 +872,-901,810,-623,-52,-709 +473,171,717,38,-429,-644 +225,824,-219,-475,-180,234 +-530,-797,-948,238,851,-623 +85,975,-363,529,598,28 +-799,166,-804,210,-769,851 +-687,-158,885,736,-381,-461 +447,592,928,-514,-515,-661 +-399,-777,-493,80,-544,-78 +-884,631,171,-825,-333,551 +191,268,-577,676,137,-33 +212,-853,709,798,583,-56 +-908,-172,-540,-84,-135,-56 +303,311,406,-360,-240,811 +798,-708,824,59,234,-57 +491,693,-74,585,-85,877 +509,-65,-936,329,-51,722 +-122,858,-52,467,-77,-609 +850,760,547,-495,-953,-952 +-460,-541,890,910,286,724 +-914,843,-579,-983,-387,-460 +989,-171,-877,-326,-899,458 +846,175,-915,540,-1000,-982 +-852,-920,-306,496,530,-18 +338,-991,160,85,-455,-661 +-186,-311,-460,-563,-231,-414 +-932,-302,959,597,793,748 +-366,-402,-788,-279,514,53 +-940,-956,447,-956,211,-285 +564,806,-911,-914,934,754 +575,-858,-277,15,409,-714 +848,462,100,-381,135,242 +330,718,-24,-190,860,-78 +479,458,941,108,-866,-653 +212,980,962,-962,115,841 +-827,-474,-206,881,323,765 +506,-45,-30,-293,524,-133 +832,-173,547,-852,-561,-842 +-397,-661,-708,819,-545,-228 +521,51,-489,852,36,-258 +227,-164,189,465,-987,-882 +-73,-997,641,-995,449,-615 +151,-995,-638,415,257,-400 +-663,-297,-748,537,-734,198 +-585,-401,-81,-782,-80,-105 +99,-21,238,-365,-704,-368 +45,416,849,-211,-371,-1 +-404,-443,795,-406,36,-933 +272,-363,981,-491,-380,77 +713,-342,-366,-849,643,911 +-748,671,-537,813,961,-200 +-194,-909,703,-662,-601,188 +281,500,724,286,267,197 +-832,847,-595,820,-316,637 +520,521,-54,261,923,-10 +4,-808,-682,-258,441,-695 +-793,-107,-969,905,798,446 +-108,-739,-590,69,-855,-365 +380,-623,-930,817,468,713 +759,-849,-236,433,-723,-931 +95,-320,-686,124,-69,-329 +-655,518,-210,-523,284,-866 +144,303,639,70,-171,269 +173,-333,947,-304,55,40 +274,878,-482,-888,-835,375 +-982,-854,-36,-218,-114,-230 +905,-979,488,-485,-479,114 +877,-157,553,-530,-47,-321 +350,664,-881,442,-220,-284 +434,-423,-365,878,-726,584 +535,909,-517,-447,-660,-141 +-966,191,50,353,182,-642 +-785,-634,123,-907,-162,511 +146,-850,-214,814,-704,25 +692,1,521,492,-637,274 +-662,-372,-313,597,983,-647 +-962,-526,68,-549,-819,231 +740,-890,-318,797,-666,948 +-190,-12,-468,-455,948,284 +16,478,-506,-888,628,-154 +272,630,-976,308,433,3 +-169,-391,-132,189,302,-388 +109,-784,474,-167,-265,-31 +-177,-532,283,464,421,-73 +650,635,592,-138,1,-387 +-932,703,-827,-492,-355,686 +586,-311,340,-618,645,-434 +-951,736,647,-127,-303,590 +188,444,903,718,-931,500 +-872,-642,-296,-571,337,241 +23,65,152,125,880,470 +512,823,-42,217,823,-263 +180,-831,-380,886,607,762 +722,443,-149,-216,-115,759 +-19,660,-36,901,923,231 +562,-322,-626,-968,194,-825 +204,-920,938,784,362,150 +-410,-266,-715,559,-672,124 +-198,446,-140,454,-461,-447 +83,-346,830,-493,-759,-382 +-881,601,581,234,-134,-925 +-494,914,-42,899,235,629 +-390,50,956,437,774,-700 +-514,514,44,-512,-576,-313 +63,-688,808,-534,-570,-399 +-726,572,-896,102,-294,-28 +-688,757,401,406,955,-511 +-283,423,-485,480,-767,908 +-541,952,-594,116,-854,451 +-273,-796,236,625,-626,257 +-407,-493,373,826,-309,297 +-750,955,-476,641,-809,713 +8,415,695,226,-111,2 +733,209,152,-920,401,995 +921,-103,-919,66,871,-947 +-907,89,-869,-214,851,-559 +-307,748,524,-755,314,-711 +188,897,-72,-763,482,103 +545,-821,-232,-596,-334,-754 +-217,-788,-820,388,-200,-662 +779,160,-723,-975,-142,-998 +-978,-519,-78,-981,842,904 +-504,-736,-295,21,-472,-482 +391,115,-705,574,652,-446 +813,-988,865,830,-263,487 +194,80,774,-493,-761,-872 +-415,-284,-803,7,-810,670 +-484,-4,881,-872,55,-852 +-379,822,-266,324,-48,748 +-304,-278,406,-60,959,-89 +404,756,577,-643,-332,658 +291,460,125,491,-312,83 +311,-734,-141,582,282,-557 +-450,-661,-981,710,-177,794 +328,264,-787,971,-743,-407 +-622,518,993,-241,-738,229 +273,-826,-254,-917,-710,-111 +809,770,96,368,-818,725 +-488,773,502,-342,534,745 +-28,-414,236,-315,-484,363 +179,-466,-566,713,-683,56 +560,-240,-597,619,916,-940 +893,473,872,-868,-642,-461 +799,489,383,-321,-776,-833 +980,490,-508,764,-512,-426 +917,961,-16,-675,440,559 +-812,212,784,-987,-132,554 +-886,454,747,806,190,231 +910,341,21,-66,708,725 +29,929,-831,-494,-303,389 +-103,492,-271,-174,-515,529 +-292,119,419,788,247,-951 +483,543,-347,-673,664,-549 +-926,-871,-437,337,162,-877 +299,472,-771,5,-88,-643 +-103,525,-725,-998,264,22 +-505,708,550,-545,823,347 +-738,931,59,147,-156,-259 +456,968,-162,889,132,-911 +535,120,968,-517,-864,-541 +24,-395,-593,-766,-565,-332 +834,611,825,-576,280,629 +211,-548,140,-278,-592,929 +-999,-240,-63,-78,793,573 +-573,160,450,987,529,322 +63,353,315,-187,-461,577 +189,-950,-247,656,289,241 +209,-297,397,664,-805,484 +-655,452,435,-556,917,874 +253,-756,262,-888,-778,-214 +793,-451,323,-251,-401,-458 +-396,619,-651,-287,-668,-781 +698,720,-349,742,-807,546 +738,280,680,279,-540,858 +-789,387,530,-36,-551,-491 +162,579,-427,-272,228,710 +689,356,917,-580,729,217 +-115,-638,866,424,-82,-194 +411,-338,-917,172,227,-29 +-612,63,630,-976,-64,-204 +-200,911,583,-571,682,-579 +91,298,396,-183,788,-955 +141,-873,-277,149,-396,916 +321,958,-136,573,541,-777 +797,-909,-469,-877,988,-653 +784,-198,129,883,-203,399 +-68,-810,223,-423,-467,-512 +531,-445,-603,-997,-841,641 +-274,-242,174,261,-636,-158 +-574,494,-796,-798,-798,99 +95,-82,-613,-954,-753,986 +-883,-448,-864,-401,938,-392 +913,930,-542,-988,310,410 +506,-99,43,512,790,-222 +724,31,49,-950,260,-134 +-287,-947,-234,-700,56,588 +-33,782,-144,948,105,-791 +548,-546,-652,-293,881,-520 +691,-91,76,991,-631,742 +-520,-429,-244,-296,724,-48 +778,646,377,50,-188,56 +-895,-507,-898,-165,-674,652 +654,584,-634,177,-349,-620 +114,-980,355,62,182,975 +516,9,-442,-298,274,-579 +-238,262,-431,-896,506,-850 +47,748,846,821,-537,-293 +839,726,593,285,-297,840 +634,-486,468,-304,-887,-567 +-864,914,296,-124,335,233 +88,-253,-523,-956,-554,803 +-587,417,281,-62,-409,-363 +-136,-39,-292,-768,-264,876 +-127,506,-891,-331,-744,-430 +778,584,-750,-129,-479,-94 +-876,-771,-987,-757,180,-641 +-777,-694,411,-87,329,190 +-347,-999,-882,158,-754,232 +-105,918,188,237,-110,-591 +-209,703,-838,77,838,909 +-995,-339,-762,750,860,472 +185,271,-289,173,811,-300 +2,65,-656,-22,36,-139 +765,-210,883,974,961,-905 +-212,295,-615,-840,77,474 +211,-910,-440,703,-11,859 +-559,-4,-196,841,-277,969 +-73,-159,-887,126,978,-371 +-569,633,-423,-33,512,-393 +503,143,-383,-109,-649,-998 +-663,339,-317,-523,-2,596 +690,-380,570,378,-652,132 +72,-744,-930,399,-525,935 +865,-983,115,37,995,826 +594,-621,-872,443,188,-241 +-1000,291,754,234,-435,-869 +-868,901,654,-907,59,181 +-868,-793,-431,596,-446,-564 +900,-944,-680,-796,902,-366 +331,430,943,853,-851,-942 +315,-538,-354,-909,139,721 +170,-884,-225,-818,-808,-657 +-279,-34,-533,-871,-972,552 +691,-986,-800,-950,654,-747 +603,988,899,841,-630,591 +876,-949,809,562,602,-536 +-693,363,-189,495,738,-1000 +-383,431,-633,297,665,959 +-740,686,-207,-803,188,-520 +-820,226,31,-339,10,121 +-312,-844,624,-516,483,621 +-822,-529,69,-278,800,328 +834,-82,-759,420,811,-264 +-960,-240,-921,561,173,46 +-324,909,-790,-814,-2,-785 +976,334,-290,-891,704,-581 +150,-798,689,-823,237,-639 +-551,-320,876,-502,-622,-628 +-136,845,904,595,-702,-261 +-857,-377,-522,-101,-943,-805 +-682,-787,-888,-459,-752,-985 +-571,-81,623,-133,447,643 +-375,-158,72,-387,-324,-696 +-660,-650,340,188,569,526 +727,-218,16,-7,-595,-988 +-966,-684,802,-783,-272,-194 +115,-566,-888,47,712,180 +-237,-69,45,-272,981,-812 +48,897,439,417,50,325 +348,616,180,254,104,-784 +-730,811,-548,612,-736,790 +138,-810,123,930,65,865 +-768,-299,-49,-895,-692,-418 +487,-531,802,-159,-12,634 +808,-179,552,-73,470,717 +720,-644,886,-141,625,144 +-485,-505,-347,-244,-916,66 +600,-565,995,-5,324,227 +-771,-35,904,-482,753,-303 +-701,65,426,-763,-504,-479 +409,733,-823,475,64,718 +865,975,368,893,-413,-433 +812,-597,-970,819,813,624 +193,-642,-381,-560,545,398 +711,28,-316,771,717,-865 +-509,462,809,-136,786,635 +618,-49,484,169,635,547 +-747,685,-882,-496,-332,82 +-501,-851,870,563,290,570 +-279,-829,-509,397,457,816 +-508,80,850,-188,483,-326 +860,-100,360,119,-205,787 +-870,21,-39,-827,-185,932 +826,284,-136,-866,-330,-97 +-944,-82,745,899,-97,365 +929,262,564,632,-115,632 +244,-276,713,330,-897,-214 +-890,-109,664,876,-974,-907 +716,249,816,489,723,141 +-96,-560,-272,45,-70,645 +762,-503,414,-828,-254,-646 +909,-13,903,-422,-344,-10 +658,-486,743,545,50,674 +-241,507,-367,18,-48,-241 +886,-268,884,-762,120,-486 +-412,-528,879,-647,223,-393 +851,810,234,937,-726,797 +-999,942,839,-134,-996,-189 +100,979,-527,-521,378,800 +544,-844,-832,-530,-77,-641 +43,889,31,442,-934,-503 +-330,-370,-309,-439,173,547 +169,945,62,-753,-542,-597 +208,751,-372,-647,-520,70 +765,-840,907,-257,379,918 +334,-135,-689,730,-427,618 +137,-508,66,-695,78,169 +-962,-123,400,-417,151,969 +328,689,666,427,-555,-642 +-907,343,605,-341,-647,582 +-667,-363,-571,818,-265,-399 +525,-938,904,898,725,692 +-176,-802,-858,-9,780,275 +580,170,-740,287,691,-97 +365,557,-375,361,-288,859 +193,737,842,-808,520,282 +-871,65,-799,836,179,-720 +958,-144,744,-789,797,-48 +122,582,662,912,68,757 +595,241,-801,513,388,186 +-103,-677,-259,-731,-281,-857 +921,319,-696,683,-88,-997 +775,200,78,858,648,768 +316,821,-763,68,-290,-741 +564,664,691,504,760,787 +694,-119,973,-385,309,-760 +777,-947,-57,990,74,19 +971,626,-496,-781,-602,-239 +-651,433,11,-339,939,294 +-965,-728,560,569,-708,-247 diff --git a/project_euler/problem_102/sol1.py b/project_euler/problem_102/sol1.py new file mode 100644 index 000000000000..00af726656ce --- /dev/null +++ b/project_euler/problem_102/sol1.py @@ -0,0 +1,81 @@ +""" +Three distinct points are plotted at random on a Cartesian plane, +for which -1000 ≤ x, y ≤ 1000, such that a triangle is formed. + +Consider the following two triangles: + +A(-340,495), B(-153,-910), C(835,-947) + +X(-175,41), Y(-421,-714), Z(574,-645) + +It can be verified that triangle ABC contains the origin, whereas +triangle XYZ does not. + +Using triangles.txt (right click and 'Save Link/Target As...'), a 27K text +file containing the coordinates of one thousand "random" triangles, find +the number of triangles for which the interior contains the origin. + +NOTE: The first two examples in the file represent the triangles in the +example given above. +""" + +from pathlib import Path +from typing import List, Tuple + + +def vector_product(point1: Tuple[int, int], point2: Tuple[int, int]) -> int: + """ + Return the 2-d vector product of two vectors. + >>> vector_product((1, 2), (-5, 0)) + 10 + >>> vector_product((3, 1), (6, 10)) + 24 + """ + return point1[0] * point2[1] - point1[1] * point2[0] + + +def contains_origin(x1: int, y1: int, x2: int, y2: int, x3: int, y3: int) -> bool: + """ + Check if the triangle given by the points A(x1, y1), B(x2, y2), C(x3, y3) + contains the origin. + >>> contains_origin(-340, 495, -153, -910, 835, -947) + True + >>> contains_origin(-175, 41, -421, -714, 574, -645) + False + """ + point_a: Tuple[int, int] = (x1, y1) + point_a_to_b: Tuple[int, int] = (x2 - x1, y2 - y1) + point_a_to_c: Tuple[int, int] = (x3 - x1, y3 - y1) + a: float = -vector_product(point_a, point_a_to_b) / vector_product( + point_a_to_c, point_a_to_b + ) + b: float = +vector_product(point_a, point_a_to_c) / vector_product( + point_a_to_c, point_a_to_b + ) + + return a > 0 and b > 0 and a + b < 1 + + +def solution(filename: str = "p102_triangles.txt") -> int: + """ + Find the number of triangles whose interior contains the origin. + >>> solution("test_triangles.txt") + 1 + """ + data: str = Path(__file__).parent.joinpath(filename).read_text(encoding="utf-8") + + triangles: List[List[int]] = [] + for line in data.strip().split("\n"): + triangles.append([int(number) for number in line.split(",")]) + + ret: int = 0 + triangle: List[int] + + for triangle in triangles: + ret += contains_origin(*triangle) + + return ret + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_102/test_triangles.txt b/project_euler/problem_102/test_triangles.txt new file mode 100644 index 000000000000..5c10cd651e9b --- /dev/null +++ b/project_euler/problem_102/test_triangles.txt @@ -0,0 +1,2 @@ +-340,495,-153,-910,835,-947 +-175,41,-421,-714,574,-645 diff --git a/project_euler/problem_107/__init__.py b/project_euler/problem_107/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_107/p107_network.txt b/project_euler/problem_107/p107_network.txt new file mode 100644 index 000000000000..fcc3c4192b96 --- /dev/null +++ b/project_euler/problem_107/p107_network.txt @@ -0,0 +1,40 @@ +-,-,-,427,668,495,377,678,-,177,-,-,870,-,869,624,300,609,131,-,251,-,-,-,856,221,514,-,591,762,182,56,-,884,412,273,636,-,-,774 +-,-,262,-,-,508,472,799,-,956,578,363,940,143,-,162,122,910,-,729,802,941,922,573,531,539,667,607,-,920,-,-,315,649,937,-,185,102,636,289 +-,262,-,-,926,-,958,158,647,47,621,264,81,-,402,813,649,386,252,391,264,637,349,-,-,-,108,-,727,225,578,699,-,898,294,-,575,168,432,833 +427,-,-,-,366,-,-,635,-,32,962,468,893,854,718,427,448,916,258,-,760,909,529,311,404,-,-,588,680,875,-,615,-,409,758,221,-,-,76,257 +668,-,926,366,-,-,-,250,268,-,503,944,-,677,-,727,793,457,981,191,-,-,-,351,969,925,987,328,282,589,-,873,477,-,-,19,450,-,-,- +495,508,-,-,-,-,-,765,711,819,305,302,926,-,-,582,-,861,-,683,293,-,-,66,-,27,-,-,290,-,786,-,554,817,33,-,54,506,386,381 +377,472,958,-,-,-,-,-,-,120,42,-,134,219,457,639,538,374,-,-,-,966,-,-,-,-,-,449,120,797,358,232,550,-,305,997,662,744,686,239 +678,799,158,635,250,765,-,-,-,35,-,106,385,652,160,-,890,812,605,953,-,-,-,79,-,712,613,312,452,-,978,900,-,901,-,-,225,533,770,722 +-,-,647,-,268,711,-,-,-,283,-,172,-,663,236,36,403,286,986,-,-,810,761,574,53,793,-,-,777,330,936,883,286,-,174,-,-,-,828,711 +177,956,47,32,-,819,120,35,283,-,50,-,565,36,767,684,344,489,565,-,-,103,810,463,733,665,494,644,863,25,385,-,342,470,-,-,-,730,582,468 +-,578,621,962,503,305,42,-,-,50,-,155,519,-,-,256,990,801,154,53,474,650,402,-,-,-,966,-,-,406,989,772,932,7,-,823,391,-,-,933 +-,363,264,468,944,302,-,106,172,-,155,-,-,-,380,438,-,41,266,-,-,104,867,609,-,270,861,-,-,165,-,675,250,686,995,366,191,-,433,- +870,940,81,893,-,926,134,385,-,565,519,-,-,313,851,-,-,-,248,220,-,826,359,829,-,234,198,145,409,68,359,-,814,218,186,-,-,929,203,- +-,143,-,854,677,-,219,652,663,36,-,-,313,-,132,-,433,598,-,-,168,870,-,-,-,128,437,-,383,364,966,227,-,-,807,993,-,-,526,17 +869,-,402,718,-,-,457,160,236,767,-,380,851,132,-,-,596,903,613,730,-,261,-,142,379,885,89,-,848,258,112,-,900,-,-,818,639,268,600,- +624,162,813,427,727,582,639,-,36,684,256,438,-,-,-,-,539,379,664,561,542,-,999,585,-,-,321,398,-,-,950,68,193,-,697,-,390,588,848,- +300,122,649,448,793,-,538,890,403,344,990,-,-,433,596,539,-,-,73,-,318,-,-,500,-,968,-,291,-,-,765,196,504,757,-,542,-,395,227,148 +609,910,386,916,457,861,374,812,286,489,801,41,-,598,903,379,-,-,-,946,136,399,-,941,707,156,757,258,251,-,807,-,-,-,461,501,-,-,616,- +131,-,252,258,981,-,-,605,986,565,154,266,248,-,613,664,73,-,-,686,-,-,575,627,817,282,-,698,398,222,-,649,-,-,-,-,-,654,-,- +-,729,391,-,191,683,-,953,-,-,53,-,220,-,730,561,-,946,686,-,-,389,729,553,304,703,455,857,260,-,991,182,351,477,867,-,-,889,217,853 +251,802,264,760,-,293,-,-,-,-,474,-,-,168,-,542,318,136,-,-,-,-,392,-,-,-,267,407,27,651,80,927,-,974,977,-,-,457,117,- +-,941,637,909,-,-,966,-,810,103,650,104,826,870,261,-,-,399,-,389,-,-,-,202,-,-,-,-,867,140,403,962,785,-,511,-,1,-,707,- +-,922,349,529,-,-,-,-,761,810,402,867,359,-,-,999,-,-,575,729,392,-,-,388,939,-,959,-,83,463,361,-,-,512,931,-,224,690,369,- +-,573,-,311,351,66,-,79,574,463,-,609,829,-,142,585,500,941,627,553,-,202,388,-,164,829,-,620,523,639,936,-,-,490,-,695,-,505,109,- +856,531,-,404,969,-,-,-,53,733,-,-,-,-,379,-,-,707,817,304,-,-,939,164,-,-,616,716,728,-,889,349,-,963,150,447,-,292,586,264 +221,539,-,-,925,27,-,712,793,665,-,270,234,128,885,-,968,156,282,703,-,-,-,829,-,-,-,822,-,-,-,736,576,-,697,946,443,-,205,194 +514,667,108,-,987,-,-,613,-,494,966,861,198,437,89,321,-,757,-,455,267,-,959,-,616,-,-,-,349,156,339,-,102,790,359,-,439,938,809,260 +-,607,-,588,328,-,449,312,-,644,-,-,145,-,-,398,291,258,698,857,407,-,-,620,716,822,-,-,293,486,943,-,779,-,6,880,116,775,-,947 +591,-,727,680,282,290,120,452,777,863,-,-,409,383,848,-,-,251,398,260,27,867,83,523,728,-,349,293,-,212,684,505,341,384,9,992,507,48,-,- +762,920,225,875,589,-,797,-,330,25,406,165,68,364,258,-,-,-,222,-,651,140,463,639,-,-,156,486,212,-,-,349,723,-,-,186,-,36,240,752 +182,-,578,-,-,786,358,978,936,385,989,-,359,966,112,950,765,807,-,991,80,403,361,936,889,-,339,943,684,-,-,965,302,676,725,-,327,134,-,147 +56,-,699,615,873,-,232,900,883,-,772,675,-,227,-,68,196,-,649,182,927,962,-,-,349,736,-,-,505,349,965,-,474,178,833,-,-,555,853,- +-,315,-,-,477,554,550,-,286,342,932,250,814,-,900,193,504,-,-,351,-,785,-,-,-,576,102,779,341,723,302,474,-,689,-,-,-,451,-,- +884,649,898,409,-,817,-,901,-,470,7,686,218,-,-,-,757,-,-,477,974,-,512,490,963,-,790,-,384,-,676,178,689,-,245,596,445,-,-,343 +412,937,294,758,-,33,305,-,174,-,-,995,186,807,-,697,-,461,-,867,977,511,931,-,150,697,359,6,9,-,725,833,-,245,-,949,-,270,-,112 +273,-,-,221,19,-,997,-,-,-,823,366,-,993,818,-,542,501,-,-,-,-,-,695,447,946,-,880,992,186,-,-,-,596,949,-,91,-,768,273 +636,185,575,-,450,54,662,225,-,-,391,191,-,-,639,390,-,-,-,-,-,1,224,-,-,443,439,116,507,-,327,-,-,445,-,91,-,248,-,344 +-,102,168,-,-,506,744,533,-,730,-,-,929,-,268,588,395,-,654,889,457,-,690,505,292,-,938,775,48,36,134,555,451,-,270,-,248,-,371,680 +-,636,432,76,-,386,686,770,828,582,-,433,203,526,600,848,227,616,-,217,117,707,369,109,586,205,809,-,-,240,-,853,-,-,-,768,-,371,-,540 +774,289,833,257,-,381,239,722,711,468,933,-,-,17,-,-,148,-,-,853,-,-,-,-,264,194,260,947,-,752,147,-,-,343,112,273,344,680,540,- diff --git a/project_euler/problem_107/sol1.py b/project_euler/problem_107/sol1.py new file mode 100644 index 000000000000..80a10e499f76 --- /dev/null +++ b/project_euler/problem_107/sol1.py @@ -0,0 +1,128 @@ +""" +The following undirected network consists of seven vertices and twelve edges +with a total weight of 243. + +The same network can be represented by the matrix below. + + A B C D E F G +A - 16 12 21 - - - +B 16 - - 17 20 - - +C 12 - - 28 - 31 - +D 21 17 28 - 18 19 23 +E - 20 - 18 - - 11 +F - - 31 19 - - 27 +G - - - 23 11 27 - + +However, it is possible to optimise the network by removing some edges and still +ensure that all points on the network remain connected. The network which achieves +the maximum saving is shown below. It has a weight of 93, representing a saving of +243 - 93 = 150 from the original network. + +Using network.txt (right click and 'Save Link/Target As...'), a 6K text file +containing a network with forty vertices, and given in matrix form, find the maximum +saving which can be achieved by removing redundant edges whilst ensuring that the +network remains connected. + +Solution: + We use Prim's algorithm to find a Minimum Spanning Tree. + Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm +""" + +import os +from typing import Dict, List, Mapping, Set, Tuple + +EdgeT = Tuple[int, int] + + +class Graph: + """ + A class representing an undirected weighted graph. + """ + + def __init__(self, vertices: Set[int], edges: Mapping[EdgeT, int]) -> None: + self.vertices: Set[int] = vertices + self.edges: Dict[EdgeT, int] = { + (min(edge), max(edge)): weight for edge, weight in edges.items() + } + + def add_edge(self, edge: EdgeT, weight: int) -> None: + """ + Add a new edge to the graph. + >>> graph = Graph({1, 2}, {(2, 1): 4}) + >>> graph.add_edge((3, 1), 5) + >>> sorted(graph.vertices) + [1, 2, 3] + >>> sorted([(v,k) for k,v in graph.edges.items()]) + [(4, (1, 2)), (5, (1, 3))] + """ + self.vertices.add(edge[0]) + self.vertices.add(edge[1]) + self.edges[(min(edge), max(edge))] = weight + + def prims_algorithm(self) -> "Graph": + """ + Run Prim's algorithm to find the minimum spanning tree. + Reference: https://en.wikipedia.org/wiki/Prim%27s_algorithm + >>> graph = Graph({1,2,3,4},{(1,2):5, (1,3):10, (1,4):20, (2,4):30, (3,4):1}) + >>> mst = graph.prims_algorithm() + >>> sorted(mst.vertices) + [1, 2, 3, 4] + >>> sorted(mst.edges) + [(1, 2), (1, 3), (3, 4)] + """ + subgraph: Graph = Graph({min(self.vertices)}, {}) + min_edge: EdgeT + min_weight: int + edge: EdgeT + weight: int + + while len(subgraph.vertices) < len(self.vertices): + min_weight = max(self.edges.values()) + 1 + for edge, weight in self.edges.items(): + if (edge[0] in subgraph.vertices) ^ (edge[1] in subgraph.vertices): + if weight < min_weight: + min_edge = edge + min_weight = weight + + subgraph.add_edge(min_edge, min_weight) + + return subgraph + + +def solution(filename: str = "p107_network.txt") -> int: + """ + Find the maximum saving which can be achieved by removing redundant edges + whilst ensuring that the network remains connected. + >>> solution("test_network.txt") + 150 + """ + script_dir: str = os.path.abspath(os.path.dirname(__file__)) + network_file: str = os.path.join(script_dir, filename) + adjacency_matrix: List[List[str]] + edges: Dict[EdgeT, int] = dict() + data: List[str] + edge1: int + edge2: int + + with open(network_file, "r") as f: + data = f.read().strip().split("\n") + + adjaceny_matrix = [line.split(",") for line in data] + + for edge1 in range(1, len(adjaceny_matrix)): + for edge2 in range(edge1): + if adjaceny_matrix[edge1][edge2] != "-": + edges[(edge2, edge1)] = int(adjaceny_matrix[edge1][edge2]) + + graph: Graph = Graph(set(range(len(adjaceny_matrix))), edges) + + subgraph: Graph = graph.prims_algorithm() + + initial_total: int = sum(graph.edges.values()) + optimal_total: int = sum(subgraph.edges.values()) + + return initial_total - optimal_total + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_107/test_network.txt b/project_euler/problem_107/test_network.txt new file mode 100644 index 000000000000..f5f2accb5720 --- /dev/null +++ b/project_euler/problem_107/test_network.txt @@ -0,0 +1,7 @@ +-,16,12,21,-,-,- +16,-,-,17,20,-,- +12,-,-,28,-,31,- +21,17,28,-,18,19,23 +-,20,-,18,-,-,11 +-,-,31,19,-,-,27 +-,-,-,23,11,27,- diff --git a/project_euler/problem_109/__init__.py b/project_euler/problem_109/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_109/sol1.py b/project_euler/problem_109/sol1.py new file mode 100644 index 000000000000..91c71eb9f4cb --- /dev/null +++ b/project_euler/problem_109/sol1.py @@ -0,0 +1,89 @@ +""" +In the game of darts a player throws three darts at a target board which is +split into twenty equal sized sections numbered one to twenty. + +The score of a dart is determined by the number of the region that the dart +lands in. A dart landing outside the red/green outer ring scores zero. The black +and cream regions inside this ring represent single scores. However, the red/green +outer ring and middle ring score double and treble scores respectively. + +At the centre of the board are two concentric circles called the bull region, or +bulls-eye. The outer bull is worth 25 points and the inner bull is a double, +worth 50 points. + +There are many variations of rules but in the most popular game the players will +begin with a score 301 or 501 and the first player to reduce their running total +to zero is a winner. However, it is normal to play a "doubles out" system, which +means that the player must land a double (including the double bulls-eye at the +centre of the board) on their final dart to win; any other dart that would reduce +their running total to one or lower means the score for that set of three darts +is "bust". + +When a player is able to finish on their current score it is called a "checkout" +and the highest checkout is 170: T20 T20 D25 (two treble 20s and double bull). + +There are exactly eleven distinct ways to checkout on a score of 6: + +D3 +D1 D2 +S2 D2 +D2 D1 +S4 D1 +S1 S1 D2 +S1 T1 D1 +S1 S3 D1 +D1 D1 D1 +D1 S2 D1 +S2 S2 D1 + +Note that D1 D2 is considered different to D2 D1 as they finish on different +doubles. However, the combination S1 T1 D1 is considered the same as T1 S1 D1. + +In addition we shall not include misses in considering combinations; for example, +D3 is the same as 0 D3 and 0 0 D3. + +Incredibly there are 42336 distinct ways of checking out in total. + +How many distinct ways can a player checkout with a score less than 100? + +Solution: + We first construct a list of the possible dart values, separated by type. + We then iterate through the doubles, followed by the possible 2 following throws. + If the total of these three darts is less than the given limit, we increment + the counter. +""" + +from itertools import combinations_with_replacement + + +def solution(limit: int = 100) -> int: + """ + Count the number of distinct ways a player can checkout with a score + less than limit. + >>> solution(171) + 42336 + >>> solution(50) + 12577 + """ + singles: list[int] = [x for x in range(1, 21)] + [25] + doubles: list[int] = [2 * x for x in range(1, 21)] + [50] + triples: list[int] = [3 * x for x in range(1, 21)] + all_values: list[int] = singles + doubles + triples + [0] + + num_checkouts: int = 0 + double: int + throw1: int + throw2: int + checkout_total: int + + for double in doubles: + for throw1, throw2 in combinations_with_replacement(all_values, 2): + checkout_total = double + throw1 + throw2 + if checkout_total < limit: + num_checkouts += 1 + + return num_checkouts + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_121/__init__.py b/project_euler/problem_121/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_121/sol1.py b/project_euler/problem_121/sol1.py new file mode 100644 index 000000000000..93679cf4a897 --- /dev/null +++ b/project_euler/problem_121/sol1.py @@ -0,0 +1,64 @@ +""" +A bag contains one red disc and one blue disc. In a game of chance a player takes a +disc at random and its colour is noted. After each turn the disc is returned to the +bag, an extra red disc is added, and another disc is taken at random. + +The player pays £1 to play and wins if they have taken more blue discs than red +discs at the end of the game. + +If the game is played for four turns, the probability of a player winning is exactly +11/120, and so the maximum prize fund the banker should allocate for winning in this +game would be £10 before they would expect to incur a loss. Note that any payout will +be a whole number of pounds and also includes the original £1 paid to play the game, +so in the example given the player actually wins £9. + +Find the maximum prize fund that should be allocated to a single game in which +fifteen turns are played. + + +Solution: + For each 15-disc sequence of red and blue for which there are more red than blue, + we calculate the probability of that sequence and add it to the total probability + of the player winning. The inverse of this probability gives an upper bound for + the prize if the banker wants to avoid an expected loss. +""" + +from itertools import product + + +def solution(num_turns: int = 15) -> int: + """ + Find the maximum prize fund that should be allocated to a single game in which + fifteen turns are played. + >>> solution(4) + 10 + >>> solution(10) + 225 + """ + total_prob: float = 0.0 + prob: float + num_blue: int + num_red: int + ind: int + col: int + series: tuple[int, ...] + + for series in product(range(2), repeat=num_turns): + num_blue = series.count(1) + num_red = num_turns - num_blue + if num_red >= num_blue: + continue + prob = 1.0 + for ind, col in enumerate(series, 2): + if col == 0: + prob *= (ind - 1) / ind + else: + prob *= 1 / ind + + total_prob += prob + + return int(1 / total_prob) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_123/__init__.py b/project_euler/problem_123/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_123/sol1.py b/project_euler/problem_123/sol1.py new file mode 100644 index 000000000000..85350c8bae49 --- /dev/null +++ b/project_euler/problem_123/sol1.py @@ -0,0 +1,99 @@ +""" +Problem 123: https://projecteuler.net/problem=123 + +Name: Prime square remainders + +Let pn be the nth prime: 2, 3, 5, 7, 11, ..., and +let r be the remainder when (pn−1)^n + (pn+1)^n is divided by pn^2. + +For example, when n = 3, p3 = 5, and 43 + 63 = 280 ≡ 5 mod 25. +The least value of n for which the remainder first exceeds 10^9 is 7037. + +Find the least value of n for which the remainder first exceeds 10^10. + + +Solution: + +n=1: (p-1) + (p+1) = 2p +n=2: (p-1)^2 + (p+1)^2 + = p^2 + 1 - 2p + p^2 + 1 + 2p (Using (p+b)^2 = (p^2 + b^2 + 2pb), + (p-b)^2 = (p^2 + b^2 - 2pb) and b = 1) + = 2p^2 + 2 +n=3: (p-1)^3 + (p+1)^3 (Similarly using (p+b)^3 & (p-b)^3 formula and so on) + = 2p^3 + 6p +n=4: 2p^4 + 12p^2 + 2 +n=5: 2p^5 + 20p^3 + 10p + +As you could see, when the expression is divided by p^2. +Except for the last term, the rest will result in the remainder 0. + +n=1: 2p +n=2: 2 +n=3: 6p +n=4: 2 +n=5: 10p + +So it could be simplified as, + r = 2pn when n is odd + r = 2 when n is even. +""" + +from typing import Dict, Generator + + +def sieve() -> Generator[int, None, None]: + """ + Returns a prime number generator using sieve method. + >>> type(sieve()) + + >>> primes = sieve() + >>> next(primes) + 2 + >>> next(primes) + 3 + >>> next(primes) + 5 + >>> next(primes) + 7 + >>> next(primes) + 11 + >>> next(primes) + 13 + """ + factor_map: Dict[int, int] = {} + prime = 2 + while True: + factor = factor_map.pop(prime, None) + if factor: + x = factor + prime + while x in factor_map: + x += factor + factor_map[x] = factor + else: + factor_map[prime * prime] = prime + yield prime + prime += 1 + + +def solution(limit: float = 1e10) -> int: + """ + Returns the least value of n for which the remainder first exceeds 10^10. + >>> solution(1e8) + 2371 + >>> solution(1e9) + 7037 + """ + primes = sieve() + + n = 1 + while True: + prime = next(primes) + if (2 * prime * n) > limit: + return n + # Ignore the next prime as the reminder will be 2. + next(primes) + n += 2 + + +if __name__ == "__main__": + print(solution()) diff --git a/project_euler/problem_129/__init__.py b/project_euler/problem_129/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_129/sol1.py b/project_euler/problem_129/sol1.py new file mode 100644 index 000000000000..8afe82df162e --- /dev/null +++ b/project_euler/problem_129/sol1.py @@ -0,0 +1,57 @@ +""" +Project Euler Problem 129: https://projecteuler.net/problem=129 + +A number consisting entirely of ones is called a repunit. We shall define R(k) to be +a repunit of length k; for example, R(6) = 111111. + +Given that n is a positive integer and GCD(n, 10) = 1, it can be shown that there +always exists a value, k, for which R(k) is divisible by n, and let A(n) be the least +such value of k; for example, A(7) = 6 and A(41) = 5. + +The least value of n for which A(n) first exceeds ten is 17. + +Find the least value of n for which A(n) first exceeds one-million. +""" + + +def least_divisible_repunit(divisor: int) -> int: + """ + Return the least value k such that the Repunit of length k is divisible by divisor. + >>> least_divisible_repunit(7) + 6 + >>> least_divisible_repunit(41) + 5 + >>> least_divisible_repunit(1234567) + 34020 + """ + if divisor % 5 == 0 or divisor % 2 == 0: + return 0 + repunit = 1 + repunit_index = 1 + while repunit: + repunit = (10 * repunit + 1) % divisor + repunit_index += 1 + return repunit_index + + +def solution(limit: int = 1000000) -> int: + """ + Return the least value of n for which least_divisible_repunit(n) + first exceeds limit. + >>> solution(10) + 17 + >>> solution(100) + 109 + >>> solution(1000) + 1017 + """ + divisor = limit - 1 + if divisor % 2 == 0: + divisor += 1 + while least_divisible_repunit(divisor) <= limit: + divisor += 2 + return divisor + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_135/__init__.py b/project_euler/problem_135/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_135/sol1.py b/project_euler/problem_135/sol1.py new file mode 100644 index 000000000000..d71a0439c7e9 --- /dev/null +++ b/project_euler/problem_135/sol1.py @@ -0,0 +1,61 @@ +""" +Project Euler Problem 135: https://projecteuler.net/problem=135 + +Given the positive integers, x, y, and z, +are consecutive terms of an arithmetic progression, +the least value of the positive integer, n, +for which the equation, +x2 − y2 − z2 = n, has exactly two solutions is n = 27: + +342 − 272 − 202 = 122 − 92 − 62 = 27 + +It turns out that n = 1155 is the least value +which has exactly ten solutions. + +How many values of n less than one million +have exactly ten distinct solutions? + + +Taking x,y,z of the form a+d,a,a-d respectively, +the given equation reduces to a*(4d-a)=n. +Calculating no of solutions for every n till 1 million by fixing a +,and n must be multiple of a. +Total no of steps=n*(1/1+1/2+1/3+1/4..+1/n) +,so roughly O(nlogn) time complexity. + +""" + + +def solution(limit: int = 1000000) -> int: + """ + returns the values of n less than or equal to the limit + have exactly ten distinct solutions. + >>> solution(100) + 0 + >>> solution(10000) + 45 + >>> solution(50050) + 292 + """ + limit = limit + 1 + frequency = [0] * limit + for first_term in range(1, limit): + for n in range(first_term, limit, first_term): + common_difference = first_term + n / first_term + if common_difference % 4: # d must be divisble by 4 + continue + else: + common_difference /= 4 + if ( + first_term > common_difference + and first_term < 4 * common_difference + ): # since x,y,z are positive integers + frequency[n] += 1 # so z>0 and a>d ,also 4d bool: + """ + Check if number is a perfect square. + + >>> is_sq(1) + True + >>> is_sq(1000001) + False + >>> is_sq(1000000) + True + """ + sq: int = int(number ** 0.5) + return number == sq * sq + + +def add_three( + x_num: int, x_den: int, y_num: int, y_den: int, z_num: int, z_den: int +) -> Tuple[int, int]: + """ + Given the numerators and denominators of three fractions, return the + numerator and denominator of their sum in lowest form. + >>> add_three(1, 3, 1, 3, 1, 3) + (1, 1) + >>> add_three(2, 5, 4, 11, 12, 3) + (262, 55) + """ + top: int = x_num * y_den * z_den + y_num * x_den * z_den + z_num * x_den * y_den + bottom: int = x_den * y_den * z_den + hcf: int = gcd(top, bottom) + top //= hcf + bottom //= hcf + return top, bottom + + +def solution(order: int = 35) -> int: + """ + Find the sum of the numerator and denominator of the sum of all s(x,y,z) for + golden triples (x,y,z) of the given order. + + >>> solution(5) + 296 + >>> solution(10) + 12519 + >>> solution(20) + 19408891927 + """ + unique_s: set = set() + hcf: int + total: Fraction = Fraction(0) + fraction_sum: Tuple[int, int] + + for x_num in range(1, order + 1): + for x_den in range(x_num + 1, order + 1): + for y_num in range(1, order + 1): + for y_den in range(y_num + 1, order + 1): + # n=1 + z_num = x_num * y_den + x_den * y_num + z_den = x_den * y_den + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + # n=2 + z_num = ( + x_num * x_num * y_den * y_den + x_den * x_den * y_num * y_num + ) + z_den = x_den * x_den * y_den * y_den + if is_sq(z_num) and is_sq(z_den): + z_num = int(sqrt(z_num)) + z_den = int(sqrt(z_den)) + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + # n=-1 + z_num = x_num * y_num + z_den = x_den * y_num + x_num * y_den + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + # n=2 + z_num = x_num * x_num * y_num * y_num + z_den = ( + x_den * x_den * y_num * y_num + x_num * x_num * y_den * y_den + ) + if is_sq(z_num) and is_sq(z_den): + z_num = int(sqrt(z_num)) + z_den = int(sqrt(z_den)) + hcf = gcd(z_num, z_den) + z_num //= hcf + z_den //= hcf + if 0 < z_num < z_den <= order: + fraction_sum = add_three( + x_num, x_den, y_num, y_den, z_num, z_den + ) + unique_s.add(fraction_sum) + + for num, den in unique_s: + total += Fraction(num, den) + + return total.denominator + total.numerator + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_188/__init__.py b/project_euler/problem_188/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_188/sol1.py b/project_euler/problem_188/sol1.py new file mode 100644 index 000000000000..6473c63620ed --- /dev/null +++ b/project_euler/problem_188/sol1.py @@ -0,0 +1,68 @@ +""" +Project Euler Problem 188: https://projecteuler.net/problem=188 + +The hyperexponentiation of a number + +The hyperexponentiation or tetration of a number a by a positive integer b, +denoted by a↑↑b or b^a, is recursively defined by: + +a↑↑1 = a, +a↑↑(k+1) = a(a↑↑k). + +Thus we have e.g. 3↑↑2 = 3^3 = 27, hence 3↑↑3 = 3^27 = 7625597484987 and +3↑↑4 is roughly 103.6383346400240996*10^12. + +Find the last 8 digits of 1777↑↑1855. + +References: + - https://en.wikipedia.org/wiki/Tetration +""" + + +# small helper function for modular exponentiation +def _modexpt(base: int, exponent: int, modulo_value: int) -> int: + """ + Returns the modular exponentiation, that is the value + of `base ** exponent % modulo_value`, without calculating + the actual number. + >>> _modexpt(2, 4, 10) + 6 + >>> _modexpt(2, 1024, 100) + 16 + >>> _modexpt(13, 65535, 7) + 6 + """ + + if exponent == 1: + return base + if exponent % 2 == 0: + x = _modexpt(base, exponent / 2, modulo_value) % modulo_value + return (x * x) % modulo_value + else: + return (base * _modexpt(base, exponent - 1, modulo_value)) % modulo_value + + +def solution(base: int = 1777, height: int = 1855, digits: int = 8) -> int: + """ + Returns the last 8 digits of the hyperexponentiation of base by + height, i.e. the number base↑↑height: + + >>> solution(base=3, height=2) + 27 + >>> solution(base=3, height=3) + 97484987 + >>> solution(base=123, height=456, digits=4) + 2547 + """ + + # calculate base↑↑height by right-assiciative repeated modular + # exponentiation + result = base + for i in range(1, height): + result = _modexpt(base, result, 10 ** digits) + + return result + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_203/__init__.py b/project_euler/problem_203/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_203/sol1.py b/project_euler/problem_203/sol1.py new file mode 100644 index 000000000000..227b476da131 --- /dev/null +++ b/project_euler/problem_203/sol1.py @@ -0,0 +1,188 @@ +""" +Project Euler Problem 203: https://projecteuler.net/problem=203 + +The binomial coefficients (n k) can be arranged in triangular form, Pascal's +triangle, like this: + 1 + 1 1 + 1 2 1 + 1 3 3 1 + 1 4 6 4 1 + 1 5 10 10 5 1 + 1 6 15 20 15 6 1 +1 7 21 35 35 21 7 1 + ......... + +It can be seen that the first eight rows of Pascal's triangle contain twelve +distinct numbers: 1, 2, 3, 4, 5, 6, 7, 10, 15, 20, 21 and 35. + +A positive integer n is called squarefree if no square of a prime divides n. +Of the twelve distinct numbers in the first eight rows of Pascal's triangle, +all except 4 and 20 are squarefree. The sum of the distinct squarefree numbers +in the first eight rows is 105. + +Find the sum of the distinct squarefree numbers in the first 51 rows of +Pascal's triangle. + +References: +- https://en.wikipedia.org/wiki/Pascal%27s_triangle +""" + +import math +from typing import List, Set + + +def get_pascal_triangle_unique_coefficients(depth: int) -> Set[int]: + """ + Returns the unique coefficients of a Pascal's triangle of depth "depth". + + The coefficients of this triangle are symmetric. A further improvement to this + method could be to calculate the coefficients once per level. Nonetheless, + the current implementation is fast enough for the original problem. + + >>> get_pascal_triangle_unique_coefficients(1) + {1} + >>> get_pascal_triangle_unique_coefficients(2) + {1} + >>> get_pascal_triangle_unique_coefficients(3) + {1, 2} + >>> get_pascal_triangle_unique_coefficients(8) + {1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21} + """ + coefficients = {1} + previous_coefficients = [1] + for step in range(2, depth + 1): + coefficients_begins_one = previous_coefficients + [0] + coefficients_ends_one = [0] + previous_coefficients + previous_coefficients = [] + for x, y in zip(coefficients_begins_one, coefficients_ends_one): + coefficients.add(x + y) + previous_coefficients.append(x + y) + return coefficients + + +def get_primes_squared(max_number: int) -> List[int]: + """ + Calculates all primes between 2 and round(sqrt(max_number)) and returns + them squared up. + + >>> get_primes_squared(2) + [] + >>> get_primes_squared(4) + [4] + >>> get_primes_squared(10) + [4, 9] + >>> get_primes_squared(100) + [4, 9, 25, 49] + """ + max_prime = round(math.sqrt(max_number)) + non_primes = set() + primes = [] + for num in range(2, max_prime + 1): + if num in non_primes: + continue + + counter = 2 + while num * counter <= max_prime: + non_primes.add(num * counter) + counter += 1 + + primes.append(num ** 2) + return primes + + +def get_squared_primes_to_use( + num_to_look: int, squared_primes: List[int], previous_index: int +) -> int: + """ + Returns an int indicating the last index on which squares of primes + in primes are lower than num_to_look. + + This method supposes that squared_primes is sorted in ascending order and that + each num_to_look is provided in ascending order as well. Under these + assumptions, it needs a previous_index parameter that tells what was + the index returned by the method for the previous num_to_look. + + If all the elements in squared_primes are greater than num_to_look, then the + method returns -1. + + >>> get_squared_primes_to_use(1, [4, 9, 16, 25], 0) + -1 + >>> get_squared_primes_to_use(4, [4, 9, 16, 25], 0) + 1 + >>> get_squared_primes_to_use(16, [4, 9, 16, 25], 1) + 3 + """ + idx = max(previous_index, 0) + + while idx < len(squared_primes) and squared_primes[idx] <= num_to_look: + idx += 1 + + if idx == 0 and squared_primes[idx] > num_to_look: + return -1 + + if idx == len(squared_primes) and squared_primes[-1] > num_to_look: + return -1 + + return idx + + +def get_squarefree( + unique_coefficients: Set[int], squared_primes: List[int] +) -> Set[int]: + """ + Calculates the squarefree numbers inside unique_coefficients given a + list of square of primes. + + Based on the definition of a non-squarefree number, then any non-squarefree + n can be decomposed as n = p*p*r, where p is positive prime number and r + is a positive integer. + + Under the previous formula, any coefficient that is lower than p*p is + squarefree as r cannot be negative. On the contrary, if any r exists such + that n = p*p*r, then the number is non-squarefree. + + >>> get_squarefree({1}, []) + set() + >>> get_squarefree({1, 2}, []) + set() + >>> get_squarefree({1, 2, 3, 4, 5, 6, 7, 35, 10, 15, 20, 21}, [4, 9, 25]) + {1, 2, 3, 5, 6, 7, 35, 10, 15, 21} + """ + + if len(squared_primes) == 0: + return set() + + non_squarefrees = set() + prime_squared_idx = 0 + for num in sorted(unique_coefficients): + prime_squared_idx = get_squared_primes_to_use( + num, squared_primes, prime_squared_idx + ) + if prime_squared_idx == -1: + continue + if any(num % prime == 0 for prime in squared_primes[:prime_squared_idx]): + non_squarefrees.add(num) + + return unique_coefficients.difference(non_squarefrees) + + +def solution(n: int = 51) -> int: + """ + Returns the sum of squarefrees for a given Pascal's Triangle of depth n. + + >>> solution(1) + 0 + >>> solution(8) + 105 + >>> solution(9) + 175 + """ + unique_coefficients = get_pascal_triangle_unique_coefficients(n) + primes = get_primes_squared(max(unique_coefficients)) + squarefrees = get_squarefree(unique_coefficients, primes) + return sum(squarefrees) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_206/__init__.py b/project_euler/problem_206/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_206/sol1.py b/project_euler/problem_206/sol1.py new file mode 100644 index 000000000000..ffac2b32aa77 --- /dev/null +++ b/project_euler/problem_206/sol1.py @@ -0,0 +1,74 @@ +""" +Project Euler Problem 206: https://projecteuler.net/problem=206 + +Find the unique positive integer whose square has the form 1_2_3_4_5_6_7_8_9_0, +where each “_” is a single digit. + +----- + +Instead of computing every single permutation of that number and going +through a 10^9 search space, we can narrow it down considerably. + +If the square ends in a 0, then the square root must also end in a 0. Thus, +the last missing digit must be 0 and the square root is a multiple of 10. +We can narrow the search space down to the first 8 digits and multiply the +result of that by 10 at the end. + +Now the last digit is a 9, which can only happen if the square root ends +in a 3 or 7. From this point, we can try one of two different methods to find +the answer: + +1. Start at the lowest possible base number whose square would be in the +format, and count up. The base we would start at is 101010103, whose square is +the closest number to 10203040506070809. Alternate counting up by 4 and 6 so +the last digit of the base is always a 3 or 7. + +2. Start at the highest possible base number whose square would be in the +format, and count down. That base would be 138902663, whose square is the +closest number to 1929394959697989. Alternate counting down by 6 and 4 so the +last digit of the base is always a 3 or 7. + +The solution does option 2 because the answer happens to be much closer to the +starting point. +""" + + +def is_square_form(num: int) -> bool: + """ + Determines if num is in the form 1_2_3_4_5_6_7_8_9 + + >>> is_square_form(1) + False + >>> is_square_form(112233445566778899) + True + >>> is_square_form(123456789012345678) + False + """ + digit = 9 + + while num > 0: + if num % 10 != digit: + return False + num //= 100 + digit -= 1 + + return True + + +def solution() -> int: + """ + Returns the first integer whose square is of the form 1_2_3_4_5_6_7_8_9_0 + """ + num = 138902663 + + while not is_square_form(num * num): + if num % 10 == 3: + num -= 6 # (3 - 6) % 10 = 7 + else: + num -= 4 # (7 - 4) % 10 = 3 + + return num * 10 + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_207/__init__.py b/project_euler/problem_207/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_207/sol1.py b/project_euler/problem_207/sol1.py new file mode 100644 index 000000000000..fb901fde1624 --- /dev/null +++ b/project_euler/problem_207/sol1.py @@ -0,0 +1,98 @@ +""" + +Project Euler Problem 207: https://projecteuler.net/problem=207 + +Problem Statement: +For some positive integers k, there exists an integer partition of the form +4**t = 2**t + k, where 4**t, 2**t, and k are all positive integers and t is a real +number. The first two such partitions are 4**1 = 2**1 + 2 and +4**1.5849625... = 2**1.5849625... + 6. +Partitions where t is also an integer are called perfect. +For any m ≥ 1 let P(m) be the proportion of such partitions that are perfect with +k ≤ m. +Thus P(6) = 1/2. +In the following table are listed some values of P(m) + + P(5) = 1/1 + P(10) = 1/2 + P(15) = 2/3 + P(20) = 1/2 + P(25) = 1/2 + P(30) = 2/5 + ... + P(180) = 1/4 + P(185) = 3/13 + +Find the smallest m for which P(m) < 1/12345 + +Solution: +Equation 4**t = 2**t + k solved for t gives: + t = log2(sqrt(4*k+1)/2 + 1/2) +For t to be real valued, sqrt(4*k+1) must be an integer which is implemented in +function check_t_real(k). For a perfect partition t must be an integer. +To speed up significantly the search for partitions, instead of incrementing k by one +per iteration, the next valid k is found by k = (i**2 - 1) / 4 with an integer i and +k has to be a positive integer. If this is the case a partition is found. The partition +is perfect if t os an integer. The integer i is increased with increment 1 until the +proportion perfect partitions / total partitions drops under the given value. + +""" + +import math + + +def check_partition_perfect(positive_integer: int) -> bool: + """ + + Check if t = f(positive_integer) = log2(sqrt(4*positive_integer+1)/2 + 1/2) is a + real number. + + >>> check_partition_perfect(2) + True + + >>> check_partition_perfect(6) + False + + """ + + exponent = math.log2(math.sqrt(4 * positive_integer + 1) / 2 + 1 / 2) + + return exponent == int(exponent) + + +def solution(max_proportion: float = 1 / 12345) -> int: + """ + Find m for which the proportion of perfect partitions to total partitions is lower + than max_proportion + + >>> solution(1) > 5 + True + + >>> solution(1/2) > 10 + True + + >>> solution(3 / 13) > 185 + True + + """ + + total_partitions = 0 + perfect_partitions = 0 + + integer = 3 + while True: + partition_candidate = (integer ** 2 - 1) / 4 + # if candidate is an integer, then there is a partition for k + if partition_candidate == int(partition_candidate): + partition_candidate = int(partition_candidate) + total_partitions += 1 + if check_partition_perfect(partition_candidate): + perfect_partitions += 1 + if perfect_partitions > 0: + if perfect_partitions / total_partitions < max_proportion: + return partition_candidate + integer += 1 + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_301/__init__.py b/project_euler/problem_301/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_301/sol1.py b/project_euler/problem_301/sol1.py new file mode 100644 index 000000000000..b1d434c189b7 --- /dev/null +++ b/project_euler/problem_301/sol1.py @@ -0,0 +1,58 @@ +""" +Project Euler Problem 301: https://projecteuler.net/problem=301 + +Problem Statement: +Nim is a game played with heaps of stones, where two players take +it in turn to remove any number of stones from any heap until no stones remain. + +We'll consider the three-heap normal-play version of +Nim, which works as follows: +- At the start of the game there are three heaps of stones. +- On each player's turn, the player may remove any positive + number of stones from any single heap. +- The first player unable to move (because no stones remain) loses. + +If (n1, n2, n3) indicates a Nim position consisting of heaps of size +n1, n2, and n3, then there is a simple function, which you may look up +or attempt to deduce for yourself, X(n1, n2, n3) that returns: +- zero if, with perfect strategy, the player about to + move will eventually lose; or +- non-zero if, with perfect strategy, the player about + to move will eventually win. + +For example X(1,2,3) = 0 because, no matter what the current player does, +the opponent can respond with a move that leaves two heaps of equal size, +at which point every move by the current player can be mirrored by the +opponent until no stones remain; so the current player loses. To illustrate: +- current player moves to (1,2,1) +- opponent moves to (1,0,1) +- current player moves to (0,0,1) +- opponent moves to (0,0,0), and so wins. + +For how many positive integers n <= 2^30 does X(n,2n,3n) = 0? +""" + + +def solution(exponent: int = 30) -> int: + """ + For any given exponent x >= 0, 1 <= n <= 2^x. + This function returns how many Nim games are lost given that + each Nim game has three heaps of the form (n, 2*n, 3*n). + >>> solution(0) + 1 + >>> solution(2) + 3 + >>> solution(10) + 144 + """ + # To find how many total games were lost for a given exponent x, + # we need to find the Fibonacci number F(x+2). + fibonacci_index = exponent + 2 + phi = (1 + 5 ** 0.5) / 2 + fibonacci = (phi ** fibonacci_index - (phi - 1) ** fibonacci_index) / 5 ** 0.5 + + return int(fibonacci) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/pytest.ini b/pytest.ini index a26de5e638dc..488379278230 100644 --- a/pytest.ini +++ b/pytest.ini @@ -2,3 +2,4 @@ [pytest] markers = mat_ops: mark a test as utilizing matrix operations. +addopts = --durations=10 diff --git a/quantum/README.md b/quantum/README.md index be5bd0843f4f..423d34fa3364 100644 --- a/quantum/README.md +++ b/quantum/README.md @@ -6,3 +6,10 @@ Started at https://github.com/TheAlgorithms/Python/issues/1831 * Google: https://research.google/teams/applied-science/quantum * IBM: https://qiskit.org and https://github.com/Qiskit * Rigetti: https://rigetti.com and https://github.com/rigetti + +## IBM Qiskit +- Start using by installing `pip install qiskit`, refer the [docs](https://qiskit.org/documentation/install.html) for more info. +- Tutorials & References + - https://github.com/Qiskit/qiskit-tutorials + - https://quantum-computing.ibm.com/docs/iql/first-circuit + - https://medium.com/qiskit/how-to-program-a-quantum-computer-982a9329ed02 diff --git a/quantum/deutsch_jozsa.py b/quantum/deutsch_jozsa.py new file mode 100755 index 000000000000..304eea196e03 --- /dev/null +++ b/quantum/deutsch_jozsa.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Deutsch-Josza Algorithm is one of the first examples of a quantum +algorithm that is exponentially faster than any possible deterministic +classical algorithm + +Premise: +We are given a hidden Boolean function f, +which takes as input a string of bits, and returns either 0 or 1: + +f({x0,x1,x2,...}) -> 0 or 1, where xn is 0 or 1 + +The property of the given Boolean function is that it is guaranteed to +either be balanced or constant. A constant function returns all 0's +or all 1's for any input, while a balanced function returns 0's for +exactly half of all inputs and 1's for the other half. Our task is to +determine whether the given function is balanced or constant. + +References: +- https://en.wikipedia.org/wiki/Deutsch-Jozsa_algorithm +- https://qiskit.org/textbook/ch-algorithms/deutsch-jozsa.html +""" + +import numpy as np +import qiskit as q + + +def dj_oracle(case: str, num_qubits: int) -> q.QuantumCircuit: + """ + Returns a Quantum Circuit for the Oracle function. + The circuit returned can represent balanced or constant function, + according to the arguments passed + """ + # This circuit has num_qubits+1 qubits: the size of the input, + # plus one output qubit + oracle_qc = q.QuantumCircuit(num_qubits + 1) + + # First, let's deal with the case in which oracle is balanced + if case == "balanced": + # First generate a random number that tells us which CNOTs to + # wrap in X-gates: + b = np.random.randint(1, 2 ** num_qubits) + # Next, format 'b' as a binary string of length 'n', padded with zeros: + b_str = format(b, f"0{num_qubits}b") + # Next, we place the first X-gates. Each digit in our binary string + # correspopnds to a qubit, if the digit is 0, we do nothing, if it's 1 + # we apply an X-gate to that qubit: + for index, bit in enumerate(b_str): + if bit == "1": + oracle_qc.x(index) + # Do the controlled-NOT gates for each qubit, using the output qubit + # as the target: + for index in range(num_qubits): + oracle_qc.cx(index, num_qubits) + # Next, place the final X-gates + for index, bit in enumerate(b_str): + if bit == "1": + oracle_qc.x(index) + + # Case in which oracle is constant + if case == "constant": + # First decide what the fixed output of the oracle will be + # (either always 0 or always 1) + output = np.random.randint(2) + if output == 1: + oracle_qc.x(num_qubits) + + oracle_gate = oracle_qc.to_gate() + oracle_gate.name = "Oracle" # To show when we display the circuit + return oracle_gate + + +def dj_algorithm(oracle: q.QuantumCircuit, num_qubits: int) -> q.QuantumCircuit: + """ + Returns the complete Deustch-Jozsa Quantum Circuit, + adding Input & Output registers and Hadamard & Measurement Gates, + to the Oracle Circuit passed in arguments + """ + dj_circuit = q.QuantumCircuit(num_qubits + 1, num_qubits) + # Set up the output qubit: + dj_circuit.x(num_qubits) + dj_circuit.h(num_qubits) + # And set up the input register: + for qubit in range(num_qubits): + dj_circuit.h(qubit) + # Let's append the oracle gate to our circuit: + dj_circuit.append(oracle, range(num_qubits + 1)) + # Finally, perform the H-gates again and measure: + for qubit in range(num_qubits): + dj_circuit.h(qubit) + + for i in range(num_qubits): + dj_circuit.measure(i, i) + + return dj_circuit + + +def deutsch_jozsa(case: str, num_qubits: int) -> q.result.counts.Counts: + """ + Main function that builds the circuit using other helper functions, + runs the experiment 1000 times & returns the resultant qubit counts + >>> deutsch_jozsa("constant", 3) + {'000': 1000} + >>> deutsch_jozsa("balanced", 3) + {'111': 1000} + """ + # Use Aer's qasm_simulator + simulator = q.Aer.get_backend("qasm_simulator") + + oracle_gate = dj_oracle(case, num_qubits) + dj_circuit = dj_algorithm(oracle_gate, num_qubits) + + # Execute the circuit on the qasm simulator + job = q.execute(dj_circuit, simulator, shots=1000) + + # Return the histogram data of the results of the experiment. + return job.result().get_counts(dj_circuit) + + +if __name__ == "__main__": + print(f"Deutsch Jozsa - Constant Oracle: {deutsch_jozsa('constant', 3)}") + print(f"Deutsch Jozsa - Balanced Oracle: {deutsch_jozsa('balanced', 3)}") diff --git a/quantum/half_adder.py b/quantum/half_adder.py new file mode 100755 index 000000000000..4af704e640be --- /dev/null +++ b/quantum/half_adder.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +""" +Build a half-adder quantum circuit that takes two bits as input, +encodes them into qubits, then runs the half-adder circuit calculating +the sum and carry qubits, observed over 1000 runs of the experiment +. + +References: +https://en.wikipedia.org/wiki/Adder_(electronics) +https://qiskit.org/textbook/ch-states/atoms-computation.html#4.2-Remembering-how-to-add- +""" + +import qiskit as q + + +def half_adder(bit0: int, bit1: int) -> q.result.counts.Counts: + """ + >>> half_adder(0, 0) + {'00': 1000} + >>> half_adder(0, 1) + {'01': 1000} + >>> half_adder(1, 0) + {'01': 1000} + >>> half_adder(1, 1) + {'10': 1000} + """ + # Use Aer's qasm_simulator + simulator = q.Aer.get_backend("qasm_simulator") + + qc_ha = q.QuantumCircuit(4, 2) + # encode inputs in qubits 0 and 1 + if bit0 == 1: + qc_ha.x(0) + if bit1 == 1: + qc_ha.x(1) + qc_ha.barrier() + + # use cnots to write XOR of the inputs on qubit2 + qc_ha.cx(0, 2) + qc_ha.cx(1, 2) + + # use ccx / toffoli gate to write AND of the inputs on qubit3 + qc_ha.ccx(0, 1, 3) + qc_ha.barrier() + + # extract outputs + qc_ha.measure(2, 0) # extract XOR value + qc_ha.measure(3, 1) # extract AND value + + # Execute the circuit on the qasm simulator + job = q.execute(qc_ha, simulator, shots=1000) + + # Return the histogram data of the results of the experiment. + return job.result().get_counts(qc_ha) + + +if __name__ == "__main__": + counts = half_adder(1, 1) + print(f"Half Adder Output Qubit Counts: {counts}") diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py new file mode 100644 index 000000000000..dc0c2103b2e5 --- /dev/null +++ b/quantum/ripple_adder_classic.py @@ -0,0 +1,108 @@ +# https://github.com/rupansh/QuantumComputing/blob/master/rippleadd.py +# https://en.wikipedia.org/wiki/Adder_(electronics)#Full_adder +# https://en.wikipedia.org/wiki/Controlled_NOT_gate + +from qiskit import Aer, QuantumCircuit, execute +from qiskit.providers import BaseBackend + + +def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: + """ + Generates a Quantum Circuit which stores two classical integers + Returns the circuit and binary representation of the integers + """ + x, y = bin(val1)[2:], bin(val2)[2:] # Remove leading '0b' + + # Ensure that both strings are of the same length + if len(x) > len(y): + y = y.zfill(len(x)) + else: + x = x.zfill(len(y)) + + # We need (3 * number of bits in the larger number)+1 qBits + # The second parameter is the number of classical registers, to measure the result + circuit = QuantumCircuit((len(x) * 3) + 1, len(x) + 1) + + # We are essentially "not-ing" the bits that are 1 + # Reversed because its easier to perform ops on more significant bits + for i in range(len(x)): + if x[::-1][i] == "1": + circuit.x(i) + for j in range(len(y)): + if y[::-1][j] == "1": + circuit.x(len(x) + j) + + return circuit, x, y + + +def full_adder( + circuit: QuantumCircuit, + input1_loc: int, + input2_loc: int, + carry_in: int, + carry_out: int, +): + """ + Quantum Equivalent of a Full Adder Circuit + CX/CCX is like 2-way/3-way XOR + """ + circuit.ccx(input1_loc, input2_loc, carry_out) + circuit.cx(input1_loc, input2_loc) + circuit.ccx(input2_loc, carry_in, carry_out) + circuit.cx(input2_loc, carry_in) + circuit.cx(input1_loc, input2_loc) + + +def ripple_adder( + val1: int, val2: int, backend: BaseBackend = Aer.get_backend("qasm_simulator") +) -> int: + """ + Quantum Equivalent of a Ripple Adder Circuit + Uses qasm_simulator backend by default + + Currently only adds 'emulated' Classical Bits + but nothing prevents us from doing this with hadamard'd bits :) + + Only supports adding +ve Integers + + >>> ripple_adder(3, 4) + 7 + >>> ripple_adder(10, 4) + 14 + >>> ripple_adder(-1, 10) + Traceback (most recent call last): + ... + ValueError: Both Integers must be positive! + """ + + if val1 < 0 or val2 < 0: + raise ValueError("Both Integers must be positive!") + + # Store the Integers + circuit, x, y = store_two_classics(val1, val2) + + """ + We are essentially using each bit of x & y respectively as full_adder's input + the carry_input is used from the previous circuit (for circuit num > 1) + + the carry_out is just below carry_input because + it will be essentially the carry_input for the next full_adder + """ + for i in range(len(x)): + full_adder(circuit, i, len(x) + i, len(x) + len(y) + i, len(x) + len(y) + i + 1) + circuit.barrier() # Optional, just for aesthetics + + # Measure the resultant qBits + for i in range(len(x) + 1): + circuit.measure([(len(x) * 2) + i], [i]) + + res = execute(circuit, backend, shots=1).result() + + # The result is in binary. Convert it back to int + return int(list(res.get_counts().keys())[0], 2) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/requirements.txt b/requirements.txt index 67d9bbbd8448..349d88944656 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ beautifulsoup4 fake_useragent -keras +keras; python_version < '3.9' lxml matplotlib numpy @@ -11,6 +11,7 @@ qiskit requests scikit-fuzzy sklearn +statsmodels sympy -tensorflow +tensorflow; python_version < '3.9' xgboost diff --git a/scheduling/first_come_first_served.py b/scheduling/first_come_first_served.py index c5f61720f97e..b51fc9fe0c04 100644 --- a/scheduling/first_come_first_served.py +++ b/scheduling/first_come_first_served.py @@ -2,10 +2,10 @@ # In this Algorithm we just care about the order that the processes arrived # without carring about their duration time # https://en.wikipedia.org/wiki/Scheduling_(computing)#First_come,_first_served -from __future__ import annotations +from typing import List -def calculate_waiting_times(duration_times: list[int]) -> list[int]: +def calculate_waiting_times(duration_times: List[int]) -> List[int]: """ This function calculates the waiting time of some processes that have a specified duration time. @@ -24,8 +24,8 @@ def calculate_waiting_times(duration_times: list[int]) -> list[int]: def calculate_turnaround_times( - duration_times: list[int], waiting_times: list[int] -) -> list[int]: + duration_times: List[int], waiting_times: List[int] +) -> List[int]: """ This function calculates the turnaround time of some processes. Return: The time difference between the completion time and the @@ -44,7 +44,7 @@ def calculate_turnaround_times( ] -def calculate_average_turnaround_time(turnaround_times: list[int]) -> float: +def calculate_average_turnaround_time(turnaround_times: List[int]) -> float: """ This function calculates the average of the turnaround times Return: The average of the turnaround times. @@ -58,7 +58,7 @@ def calculate_average_turnaround_time(turnaround_times: list[int]) -> float: return sum(turnaround_times) / len(turnaround_times) -def calculate_average_waiting_time(waiting_times: list[int]) -> float: +def calculate_average_waiting_time(waiting_times: List[int]) -> float: """ This function calculates the average of the waiting times Return: The average of the waiting times. diff --git a/scheduling/round_robin.py b/scheduling/round_robin.py index e8d54dd9a553..4a79301c1816 100644 --- a/scheduling/round_robin.py +++ b/scheduling/round_robin.py @@ -3,12 +3,11 @@ In Round Robin each process is assigned a fixed time slot in a cyclic way. https://en.wikipedia.org/wiki/Round-robin_scheduling """ -from __future__ import annotations - from statistics import mean +from typing import List -def calculate_waiting_times(burst_times: list[int]) -> list[int]: +def calculate_waiting_times(burst_times: List[int]) -> List[int]: """ Calculate the waiting times of a list of processes that have a specified duration. @@ -41,8 +40,8 @@ def calculate_waiting_times(burst_times: list[int]) -> list[int]: def calculate_turn_around_times( - burst_times: list[int], waiting_times: list[int] -) -> list[int]: + burst_times: List[int], waiting_times: List[int] +) -> List[int]: """ >>> calculate_turn_around_times([1, 2, 3, 4], [0, 1, 3]) [1, 3, 6] diff --git a/scheduling/shortest_job_first.py b/scheduling/shortest_job_first.py index f9e2ad975627..a49d037d6a23 100644 --- a/scheduling/shortest_job_first.py +++ b/scheduling/shortest_job_first.py @@ -3,17 +3,17 @@ Please note arrival time and burst Please use spaces to separate times entered. """ -from __future__ import annotations +from typing import List import pandas as pd def calculate_waitingtime( - arrival_time: list[int], burst_time: list[int], no_of_processes: int -) -> list[int]: + arrival_time: List[int], burst_time: List[int], no_of_processes: int +) -> List[int]: """ Calculate the waiting time of each processes - Return: list of waiting times. + Return: List of waiting times. >>> calculate_waitingtime([1,2,3,4],[3,3,5,1],4) [0, 3, 5, 0] >>> calculate_waitingtime([1,2,3],[2,5,1],3) @@ -72,8 +72,8 @@ def calculate_waitingtime( def calculate_turnaroundtime( - burst_time: list[int], no_of_processes: int, waiting_time: list[int] -) -> list[int]: + burst_time: List[int], no_of_processes: int, waiting_time: List[int] +) -> List[int]: """ Calculate the turn around time of each Processes Return: list of turn around times. @@ -91,8 +91,8 @@ def calculate_turnaroundtime( def calculate_average_times( - waiting_time: list[int], turn_around_time: list[int], no_of_processes: int -): + waiting_time: List[int], turn_around_time: List[int], no_of_processes: int +) -> None: """ This function calculates the average of the waiting & turnaround times Prints: Average Waiting time & Average Turn Around Time diff --git a/project_euler/project_euler_answers.json b/scripts/project_euler_answers.json similarity index 100% rename from project_euler/project_euler_answers.json rename to scripts/project_euler_answers.json diff --git a/scripts/validate_filenames.py b/scripts/validate_filenames.py index e75bf6c18b07..419295fe679d 100755 --- a/scripts/validate_filenames.py +++ b/scripts/validate_filenames.py @@ -9,7 +9,6 @@ filepaths = list(good_file_paths()) assert filepaths, "good_file_paths() failed!" - upper_files = [file for file in filepaths if file != file.lower()] if upper_files: print(f"{len(upper_files)} files contain uppercase characters:") diff --git a/project_euler/validate_solutions.py b/scripts/validate_solutions.py similarity index 52% rename from project_euler/validate_solutions.py rename to scripts/validate_solutions.py index 6cc1d6498e37..fd804ea5aa31 100755 --- a/project_euler/validate_solutions.py +++ b/scripts/validate_solutions.py @@ -1,15 +1,17 @@ #!/usr/bin/env python3 import importlib.util import json +import os import pathlib from types import ModuleType from typing import Dict, List import pytest +import requests PROJECT_EULER_DIR_PATH = pathlib.Path.cwd().joinpath("project_euler") -PROJECT_EULER_ANSWERS_PATH = PROJECT_EULER_DIR_PATH.joinpath( - "project_euler_answers.json" +PROJECT_EULER_ANSWERS_PATH = pathlib.Path.cwd().joinpath( + "scripts", "project_euler_answers.json" ) with open(PROJECT_EULER_ANSWERS_PATH) as file_handle: @@ -24,7 +26,7 @@ def convert_path_to_module(file_path: pathlib.Path) -> ModuleType: return module -def collect_solution_file_paths() -> List[pathlib.Path]: +def all_solution_file_paths() -> List[pathlib.Path]: """Collects all the solution file path in the Project Euler directory""" solution_file_paths = [] for problem_dir_path in PROJECT_EULER_DIR_PATH.iterdir(): @@ -37,12 +39,51 @@ def collect_solution_file_paths() -> List[pathlib.Path]: return solution_file_paths +def get_files_url() -> str: + """Return the pull request number which triggered this action.""" + with open(os.environ["GITHUB_EVENT_PATH"]) as file: + event = json.load(file) + return event["pull_request"]["url"] + "/files" + + +def added_solution_file_path() -> List[pathlib.Path]: + """Collects only the solution file path which got added in the current + pull request. + + This will only be triggered if the script is ran from GitHub Actions. + """ + solution_file_paths = [] + headers = { + "Accept": "application/vnd.github.v3+json", + "Authorization": "token " + os.environ["GITHUB_TOKEN"], + } + files = requests.get(get_files_url(), headers=headers).json() + for file in files: + filepath = pathlib.Path.cwd().joinpath(file["filename"]) + if ( + filepath.suffix != ".py" + or filepath.name.startswith(("_", "test")) + or not filepath.name.startswith("sol") + ): + continue + solution_file_paths.append(filepath) + return solution_file_paths + + +def collect_solution_file_paths() -> List[pathlib.Path]: + if os.environ.get("CI") and os.environ.get("GITHUB_EVENT_NAME") == "pull_request": + # Return only if there are any, otherwise default to all solutions + if filepaths := added_solution_file_path(): + return filepaths + return all_solution_file_paths() + + @pytest.mark.parametrize( "solution_path", collect_solution_file_paths(), ids=lambda path: f"{path.parent.name}/{path.name}", ) -def test_project_euler(solution_path: pathlib.Path): +def test_project_euler(solution_path: pathlib.Path) -> None: """Testing for all Project Euler solutions""" # problem_[extract this part] and pad it with zeroes for width 3 problem_number: str = solution_path.parent.name[8:].zfill(3) diff --git a/searches/binary_search.py b/searches/binary_search.py index d0f6296168fa..35e0dd0596d2 100644 --- a/searches/binary_search.py +++ b/searches/binary_search.py @@ -1,18 +1,21 @@ +#!/usr/bin/env python3 + """ This is pure Python implementation of binary search algorithms For doctests run following command: -python -m doctest -v binary_search.py -or python3 -m doctest -v binary_search.py For manual testing run: -python binary_search.py +python3 binary_search.py """ import bisect +from typing import List, Optional -def bisect_left(sorted_collection, item, lo=0, hi=None): +def bisect_left( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> int: """ Locates the first element in a sorted array that is larger or equal to a given value. @@ -43,7 +46,7 @@ def bisect_left(sorted_collection, item, lo=0, hi=None): >>> bisect_left([0, 5, 7, 10, 15], 6, 2) 2 """ - if hi is None: + if hi < 0: hi = len(sorted_collection) while lo < hi: @@ -56,7 +59,9 @@ def bisect_left(sorted_collection, item, lo=0, hi=None): return lo -def bisect_right(sorted_collection, item, lo=0, hi=None): +def bisect_right( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> int: """ Locates the first element in a sorted array that is larger than a given value. @@ -86,7 +91,7 @@ def bisect_right(sorted_collection, item, lo=0, hi=None): >>> bisect_right([0, 5, 7, 10, 15], 6, 2) 2 """ - if hi is None: + if hi < 0: hi = len(sorted_collection) while lo < hi: @@ -99,7 +104,9 @@ def bisect_right(sorted_collection, item, lo=0, hi=None): return lo -def insort_left(sorted_collection, item, lo=0, hi=None): +def insort_left( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> None: """ Inserts a given value into a sorted array before other values with the same value. @@ -140,7 +147,9 @@ def insort_left(sorted_collection, item, lo=0, hi=None): sorted_collection.insert(bisect_left(sorted_collection, item, lo, hi), item) -def insort_right(sorted_collection, item, lo=0, hi=None): +def insort_right( + sorted_collection: List[int], item: int, lo: int = 0, hi: int = -1 +) -> None: """ Inserts a given value into a sorted array after other values with the same value. @@ -181,7 +190,7 @@ def insort_right(sorted_collection, item, lo=0, hi=None): sorted_collection.insert(bisect_right(sorted_collection, item, lo, hi), item) -def binary_search(sorted_collection, item): +def binary_search(sorted_collection: List[int], item: int) -> Optional[int]: """Pure implementation of binary search algorithm in Python Be careful collection must be ascending sorted, otherwise result will be @@ -219,7 +228,7 @@ def binary_search(sorted_collection, item): return None -def binary_search_std_lib(sorted_collection, item): +def binary_search_std_lib(sorted_collection: List[int], item: int) -> Optional[int]: """Pure implementation of binary search algorithm in Python using stdlib Be careful collection must be ascending sorted, otherwise result will be @@ -248,7 +257,9 @@ def binary_search_std_lib(sorted_collection, item): return None -def binary_search_by_recursion(sorted_collection, item, left, right): +def binary_search_by_recursion( + sorted_collection: List[int], item: int, left: int, right: int +) -> Optional[int]: """Pure implementation of binary search algorithm in Python by recursion @@ -286,41 +297,12 @@ def binary_search_by_recursion(sorted_collection, item, left, right): return binary_search_by_recursion(sorted_collection, item, midpoint + 1, right) -def __assert_sorted(collection): - """Check if collection is ascending sorted, if not - raises :py:class:`ValueError` - - :param collection: collection - :return: True if collection is ascending sorted - :raise: :py:class:`ValueError` if collection is not ascending sorted - - Examples: - >>> __assert_sorted([0, 1, 2, 4]) - True - - >>> __assert_sorted([10, -1, 5]) - Traceback (most recent call last): - ... - ValueError: Collection must be ascending sorted - """ - if collection != sorted(collection): - raise ValueError("Collection must be ascending sorted") - return True - - if __name__ == "__main__": - import sys - user_input = input("Enter numbers separated by comma:\n").strip() - collection = [int(item) for item in user_input.split(",")] - try: - __assert_sorted(collection) - except ValueError: - sys.exit("Sequence must be ascending sorted to apply binary search") - - target_input = input("Enter a single number to be found in the list:\n") - target = int(target_input) + collection = sorted(int(item) for item in user_input.split(",")) + target = int(input("Enter a single number to be found in the list:\n")) result = binary_search(collection, target) - if result is not None: - print(f"{target} found at positions: {result}") + if result is None: + print(f"{target} was not found in {collection}.") else: - print("Not found") + print(f"{target} was found at position {result} in {collection}.") diff --git a/traversals/binary_tree_traversals.py b/searches/binary_tree_traversal.py similarity index 99% rename from traversals/binary_tree_traversals.py rename to searches/binary_tree_traversal.py index cb471ba55bac..f919a2962354 100644 --- a/traversals/binary_tree_traversals.py +++ b/searches/binary_tree_traversal.py @@ -188,7 +188,7 @@ def pre_order_iter(node: TreeNode) -> None: """ if not isinstance(node, TreeNode) or not node: return - stack: List[TreeNode] = [] + stack: list[TreeNode] = [] n = node while n or stack: while n: # start from root node, find its left child @@ -218,7 +218,7 @@ def in_order_iter(node: TreeNode) -> None: """ if not isinstance(node, TreeNode) or not node: return - stack: List[TreeNode] = [] + stack: list[TreeNode] = [] n = node while n or stack: while n: diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 70622ebefb4e..bb24e781a6c1 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -60,7 +60,7 @@ def get_neighbors(self): def __hash__(self): """ - hash the string represetation of the current search state. + hash the string representation of the current search state. """ return hash(str(self)) diff --git a/searches/quick_select.py b/searches/quick_select.py index 17dca395f73c..5ede8c4dd07f 100644 --- a/searches/quick_select.py +++ b/searches/quick_select.py @@ -44,8 +44,7 @@ def quick_select(items: list, index: int): if index >= len(items) or index < 0: return None - pivot = random.randint(0, len(items) - 1) - pivot = items[pivot] + pivot = items[random.randint(0, len(items) - 1)] count = 0 smaller, equal, larger = _partition(items, pivot) count = len(equal) diff --git a/searches/ternary_search.py b/searches/ternary_search.py index b01db3eb845f..9422a4ccb966 100644 --- a/searches/ternary_search.py +++ b/searches/ternary_search.py @@ -6,7 +6,6 @@ Time Complexity : O(log3 N) Space Complexity : O(1) """ -import sys from typing import List # This is the precision for this function which can be altered. @@ -15,90 +14,156 @@ # This is the linear search that will occur after the search space has become smaller. -def lin_search(left: int, right: int, A: List[int], target: int): - for i in range(left, right + 1): - if A[i] == target: - return i -# This is the iterative method of the ternary search algorithm. -def ite_ternary_search(A: List[int], target: int): - left = 0 - right = len(A) - 1 - while True: - if left < right: +def lin_search(left: int, right: int, array: List[int], target: int) -> int: + """Perform linear search in list. Returns -1 if element is not found. + + Parameters + ---------- + left : int + left index bound. + right : int + right index bound. + array : List[int] + List of elements to be searched on + target : int + Element that is searched + + Returns + ------- + int + index of element that is looked for. + + Examples + -------- + >>> lin_search(0, 4, [4, 5, 6, 7], 7) + 3 + >>> lin_search(0, 3, [4, 5, 6, 7], 7) + -1 + >>> lin_search(0, 2, [-18, 2], -18) + 0 + >>> lin_search(0, 1, [5], 5) + 0 + >>> lin_search(0, 3, ['a', 'c', 'd'], 'c') + 1 + >>> lin_search(0, 3, [.1, .4 , -.1], .1) + 0 + >>> lin_search(0, 3, [.1, .4 , -.1], -.1) + 2 + """ + for i in range(left, right): + if array[i] == target: + return i + return -1 + + +def ite_ternary_search(array: List[int], target: int) -> int: + """Iterative method of the ternary search algorithm. + >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] + >>> ite_ternary_search(test_list, 3) + -1 + >>> ite_ternary_search(test_list, 13) + 4 + >>> ite_ternary_search([4, 5, 6, 7], 4) + 0 + >>> ite_ternary_search([4, 5, 6, 7], -10) + -1 + >>> ite_ternary_search([-18, 2], -18) + 0 + >>> ite_ternary_search([5], 5) + 0 + >>> ite_ternary_search(['a', 'c', 'd'], 'c') + 1 + >>> ite_ternary_search(['a', 'c', 'd'], 'f') + -1 + >>> ite_ternary_search([], 1) + -1 + >>> ite_ternary_search([.1, .4 , -.1], .1) + 0 + """ - if right - left < precision: - return lin_search(left, right, A, target) + left = 0 + right = len(array) + while left <= right: + if right - left < precision: + return lin_search(left, right, array, target) - oneThird = (left + right) / 3 + 1 - twoThird = 2 * (left + right) / 3 + 1 + one_third = (left + right) / 3 + 1 + two_third = 2 * (left + right) / 3 + 1 - if A[oneThird] == target: - return oneThird - elif A[twoThird] == target: - return twoThird + if array[one_third] == target: + return one_third + elif array[two_third] == target: + return two_third - elif target < A[oneThird]: - right = oneThird - 1 - elif A[twoThird] < target: - left = twoThird + 1 + elif target < array[one_third]: + right = one_third - 1 + elif array[two_third] < target: + left = two_third + 1 - else: - left = oneThird + 1 - right = twoThird - 1 else: - return None - -# This is the recursive method of the ternary search algorithm. -def rec_ternary_search(left: int, right: int, A: List[int], target: int): + left = one_third + 1 + right = two_third - 1 + else: + return -1 + + +def rec_ternary_search(left: int, right: int, array: List[int], target: int) -> int: + """Recursive method of the ternary search algorithm. + + >>> test_list = [0, 1, 2, 8, 13, 17, 19, 32, 42] + >>> rec_ternary_search(0, len(test_list), test_list, 3) + -1 + >>> rec_ternary_search(4, len(test_list), test_list, 42) + 8 + >>> rec_ternary_search(0, 2, [4, 5, 6, 7], 4) + 0 + >>> rec_ternary_search(0, 3, [4, 5, 6, 7], -10) + -1 + >>> rec_ternary_search(0, 1, [-18, 2], -18) + 0 + >>> rec_ternary_search(0, 1, [5], 5) + 0 + >>> rec_ternary_search(0, 2, ['a', 'c', 'd'], 'c') + 1 + >>> rec_ternary_search(0, 2, ['a', 'c', 'd'], 'f') + -1 + >>> rec_ternary_search(0, 0, [], 1) + -1 + >>> rec_ternary_search(0, 3, [.1, .4 , -.1], .1) + 0 + """ if left < right: - if right - left < precision: - return lin_search(left, right, A, target) - - oneThird = (left + right) / 3 + 1 - twoThird = 2 * (left + right) / 3 + 1 - - if A[oneThird] == target: - return oneThird - elif A[twoThird] == target: - return twoThird - - elif target < A[oneThird]: - return rec_ternary_search(left, oneThird - 1, A, target) - elif A[twoThird] < target: - return rec_ternary_search(twoThird + 1, right, A, target) - + return lin_search(left, right, array, target) + one_third = (left + right) / 3 + 1 + two_third = 2 * (left + right) / 3 + 1 + + if array[one_third] == target: + return one_third + elif array[two_third] == target: + return two_third + + elif target < array[one_third]: + return rec_ternary_search(left, one_third - 1, array, target) + elif array[two_third] < target: + return rec_ternary_search(two_third + 1, right, array, target) else: - return rec_ternary_search(oneThird + 1, twoThird - 1, A, target) + return rec_ternary_search(one_third + 1, two_third - 1, array, target) else: - return None - - -# This function is to check if the array is sorted. -def __assert_sorted(collection: List[int]) -> bool: - if collection != sorted(collection): - raise ValueError("Collection must be sorted") - return True + return -1 if __name__ == "__main__": - user_input = input("Enter numbers separated by coma:\n").strip() - collection = [int(item) for item in user_input.split(",")] - - try: - __assert_sorted(collection) - except ValueError: - sys.exit("Sequence must be sorted to apply the ternary search") - - target_input = input("Enter a single number to be found in the list:\n") - target = int(target_input) + user_input = input("Enter numbers separated by comma:\n").strip() + collection = [int(item.strip()) for item in user_input.split(",")] + assert collection == sorted(collection), f"List must be ordered.\n{collection}." + target = int(input("Enter the number to be found in the list:\n").strip()) result1 = ite_ternary_search(collection, target) result2 = rec_ternary_search(0, len(collection) - 1, collection, target) - - if result2 is not None: + if result2 != -1: print(f"Iterative search: {target} found at positions: {result1}") print(f"Recursive search: {target} found at positions: {result2}") else: diff --git a/sorts/bitonic_sort.py b/sorts/bitonic_sort.py index be3499de13cd..c718973e5ecb 100644 --- a/sorts/bitonic_sort.py +++ b/sorts/bitonic_sort.py @@ -1,58 +1,96 @@ -# Python program for Bitonic Sort. Note that this program -# works only when size of input is a power of 2. - - -# The parameter dir indicates the sorting direction, ASCENDING -# or DESCENDING; if (a[i] > a[j]) agrees with the direction, -# then a[i] and a[j] are interchanged. -def compAndSwap(a, i, j, dire): - if (dire == 1 and a[i] > a[j]) or (dire == 0 and a[i] < a[j]): - a[i], a[j] = a[j], a[i] - - # It recursively sorts a bitonic sequence in ascending order, - - -# if dir = 1, and in descending order otherwise (means dir=0). -# The sequence to be sorted starts at index position low, -# the parameter cnt is the number of elements to be sorted. -def bitonic_merge(a, low, cnt, dire): - if cnt > 1: - k = int(cnt / 2) - for i in range(low, low + k): - compAndSwap(a, i, i + k, dire) - bitonic_merge(a, low, k, dire) - bitonic_merge(a, low + k, k, dire) - - # This function first produces a bitonic sequence by recursively - - -# sorting its two halves in opposite sorting orders, and then -# calls bitonic_merge to make them in the same order -def bitonic_sort(a, low, cnt, dire): - if cnt > 1: - k = int(cnt / 2) - bitonic_sort(a, low, k, 1) - bitonic_sort(a, low + k, k, 0) - bitonic_merge(a, low, cnt, dire) - - # Caller of bitonic_sort for sorting the entire array of length N - - -# in ASCENDING order -def sort(a, N, up): - bitonic_sort(a, 0, N, up) +""" +Python program for Bitonic Sort. + +Note that this program works only when size of input is a power of 2. +""" +from typing import List + + +def comp_and_swap(array: List[int], index1: int, index2: int, direction: int) -> None: + """Compare the value at given index1 and index2 of the array and swap them as per + the given direction. + + The parameter direction indicates the sorting direction, ASCENDING(1) or + DESCENDING(0); if (a[i] > a[j]) agrees with the direction, then a[i] and a[j] are + interchanged. + + >>> arr = [12, 42, -21, 1] + >>> comp_and_swap(arr, 1, 2, 1) + >>> print(arr) + [12, -21, 42, 1] + + >>> comp_and_swap(arr, 1, 2, 0) + >>> print(arr) + [12, 42, -21, 1] + + >>> comp_and_swap(arr, 0, 3, 1) + >>> print(arr) + [1, 42, -21, 12] + + >>> comp_and_swap(arr, 0, 3, 0) + >>> print(arr) + [12, 42, -21, 1] + """ + if (direction == 1 and array[index1] > array[index2]) or ( + direction == 0 and array[index1] < array[index2] + ): + array[index1], array[index2] = array[index2], array[index1] + + +def bitonic_merge(array: List[int], low: int, length: int, direction: int) -> None: + """ + It recursively sorts a bitonic sequence in ascending order, if direction = 1, and in + descending if direction = 0. + The sequence to be sorted starts at index position low, the parameter length is the + number of elements to be sorted. + + >>> arr = [12, 42, -21, 1] + >>> bitonic_merge(arr, 0, 4, 1) + >>> print(arr) + [-21, 1, 12, 42] + + >>> bitonic_merge(arr, 0, 4, 0) + >>> print(arr) + [42, 12, 1, -21] + """ + if length > 1: + middle = int(length / 2) + for i in range(low, low + middle): + comp_and_swap(array, i, i + middle, direction) + bitonic_merge(array, low, middle, direction) + bitonic_merge(array, low + middle, middle, direction) + + +def bitonic_sort(array: List[int], low: int, length: int, direction: int) -> None: + """ + This function first produces a bitonic sequence by recursively sorting its two + halves in opposite sorting orders, and then calls bitonic_merge to make them in the + same order. + + >>> arr = [12, 34, 92, -23, 0, -121, -167, 145] + >>> bitonic_sort(arr, 0, 8, 1) + >>> arr + [-167, -121, -23, 0, 12, 34, 92, 145] + + >>> bitonic_sort(arr, 0, 8, 0) + >>> arr + [145, 92, 34, 12, 0, -23, -121, -167] + """ + if length > 1: + middle = int(length / 2) + bitonic_sort(array, low, middle, 1) + bitonic_sort(array, low + middle, middle, 0) + bitonic_merge(array, low, length, direction) if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item.strip()) for item in user_input.split(",")] - a = [] - - n = int(input().strip()) - for i in range(n): - a.append(int(input().strip())) - up = 1 + bitonic_sort(unsorted, 0, len(unsorted), 1) + print("\nSorted array in ascending order is: ", end="") + print(*unsorted, sep=", ") - sort(a, n, up) - print("\n\nSorted array is") - for i in range(n): - print("%d" % a[i]) + bitonic_merge(unsorted, 0, len(unsorted), 0) + print("Sorted array in descending order is: ", end="") + print(*unsorted, sep=", ") diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index a0566be662e3..1ac76774f4ba 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -27,6 +27,7 @@ Source: https://en.wikipedia.org/wiki/Bucket_sort """ +from typing import List def bucket_sort(my_list: list) -> list: @@ -51,7 +52,7 @@ def bucket_sort(my_list: list) -> list: return [] min_value, max_value = min(my_list), max(my_list) bucket_count = int(max_value - min_value) + 1 - buckets = [[] for _ in range(bucket_count)] + buckets: List[list] = [[] for _ in range(bucket_count)] for i in range(len(my_list)): buckets[(int(my_list[i] - min_value) // bucket_count)].append(my_list[i]) diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index 42015abc5f97..b738ff31d768 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -33,7 +33,8 @@ def cocktail_shaker_sort(unsorted: list) -> list: swapped = True if not swapped: - return unsorted + break + return unsorted if __name__ == "__main__": diff --git a/sorts/intro_sort.py b/sorts/intro_sort.py new file mode 100644 index 000000000000..f0e3645adbb7 --- /dev/null +++ b/sorts/intro_sort.py @@ -0,0 +1,173 @@ +""" +Introspective Sort is hybrid sort (Quick Sort + Heap Sort + Insertion Sort) +if the size of the list is under 16, use insertion sort +https://en.wikipedia.org/wiki/Introsort +""" +import math + + +def insertion_sort(array: list, start: int = 0, end: int = 0) -> list: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> insertion_sort(array, 0, len(array)) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + """ + end = end or len(array) + for i in range(start, end): + temp_index = i + temp_index_value = array[i] + while temp_index != start and temp_index_value < array[temp_index - 1]: + array[temp_index] = array[temp_index - 1] + temp_index -= 1 + array[temp_index] = temp_index_value + return array + + +def heapify(array: list, index: int, heap_size: int) -> None: # Max Heap + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> heapify(array, len(array) // 2 ,len(array)) + """ + largest = index + left_index = 2 * index + 1 # Left Node + right_index = 2 * index + 2 # Right Node + + if left_index < heap_size and array[largest] < array[left_index]: + largest = left_index + + if right_index < heap_size and array[largest] < array[right_index]: + largest = right_index + + if largest != index: + array[index], array[largest] = array[largest], array[index] + heapify(array, largest, heap_size) + + +def heap_sort(array: list) -> list: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> heap_sort(array) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + """ + n = len(array) + + for i in range(n // 2, -1, -1): + heapify(array, i, n) + + for i in range(n - 1, 0, -1): + array[i], array[0] = array[0], array[i] + heapify(array, 0, i) + + return array + + +def median_of_3( + array: list, first_index: int, middle_index: int, last_index: int +) -> int: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> median_of_3(array, 0, 0 + ((len(array) - 0) // 2) + 1, len(array) - 1) + 12 + """ + if (array[first_index] > array[middle_index]) != ( + array[first_index] > array[last_index] + ): + return array[first_index] + elif (array[middle_index] > array[first_index]) != ( + array[middle_index] > array[last_index] + ): + return array[middle_index] + else: + return array[last_index] + + +def partition(array: list, low: int, high: int, pivot: int) -> int: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> partition(array, 0, len(array), 12) + 8 + """ + i = low + j = high + while True: + while array[i] < pivot: + i += 1 + j -= 1 + while pivot < array[j]: + j -= 1 + if i >= j: + return i + array[i], array[j] = array[j], array[i] + i += 1 + + +def sort(array: list) -> list: + """ + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + Examples: + >>> sort([4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12]) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + + >>> sort([-1, -5, -3, -13, -44]) + [-44, -13, -5, -3, -1] + + >>> sort([]) + [] + + >>> sort([5]) + [5] + + >>> sort([-3, 0, -7, 6, 23, -34]) + [-34, -7, -3, 0, 6, 23] + + >>> sort([1.7, 1.0, 3.3, 2.1, 0.3 ]) + [0.3, 1.0, 1.7, 2.1, 3.3] + + >>> sort(['d', 'a', 'b', 'e', 'c']) + ['a', 'b', 'c', 'd', 'e'] + """ + if len(array) == 0: + return array + max_depth = 2 * math.ceil(math.log2(len(array))) + size_threshold = 16 + return intro_sort(array, 0, len(array), size_threshold, max_depth) + + +def intro_sort( + array: list, start: int, end: int, size_threshold: int, max_depth: int +) -> list: + """ + >>> array = [4, 2, 6, 8, 1, 7, 8, 22, 14, 56, 27, 79, 23, 45, 14, 12] + + >>> max_depth = 2 * math.ceil(math.log2(len(array))) + + >>> intro_sort(array, 0, len(array), 16, max_depth) + [1, 2, 4, 6, 7, 8, 8, 12, 14, 14, 22, 23, 27, 45, 56, 79] + """ + while end - start > size_threshold: + if max_depth == 0: + return heap_sort(array) + max_depth -= 1 + pivot = median_of_3(array, start, start + ((end - start) // 2) + 1, end - 1) + p = partition(array, start, end, pivot) + intro_sort(array, p, end, size_threshold, max_depth) + end = p + return insertion_sort(array, start, end) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + user_input = input("Enter numbers separated by a comma : ").strip() + unsorted = [float(item) for item in user_input.split(",")] + print(sort(unsorted)) diff --git a/sorts/odd_even_sort.py b/sorts/odd_even_sort.py new file mode 100644 index 000000000000..557337ee77bc --- /dev/null +++ b/sorts/odd_even_sort.py @@ -0,0 +1,47 @@ +"""For reference +https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort +""" + + +def odd_even_sort(input_list: list) -> list: + """this algorithm uses the same idea of bubblesort, + but by first dividing in two phase (odd and even). + Originally developed for use on parallel processors + with local interconnections. + :param collection: mutable ordered sequence of elements + :return: same collection in ascending order + Examples: + >>> odd_even_sort([5 , 4 ,3 ,2 ,1]) + [1, 2, 3, 4, 5] + >>> odd_even_sort([]) + [] + >>> odd_even_sort([-10 ,-1 ,10 ,2]) + [-10, -1, 2, 10] + >>> odd_even_sort([1 ,2 ,3 ,4]) + [1, 2, 3, 4] + """ + sorted = False + while sorted is False: # Until all the indices are traversed keep looping + sorted = True + for i in range(0, len(input_list) - 1, 2): # iterating over all even indices + if input_list[i] > input_list[i + 1]: + + input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] + # swapping if elements not in order + sorted = False + + for i in range(1, len(input_list) - 1, 2): # iterating over all odd indices + if input_list[i] > input_list[i + 1]: + input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] + # swapping if elements not in order + sorted = False + return input_list + + +if __name__ == "__main__": + print("Enter list to be sorted") + input_list = [int(x) for x in input().split()] + # inputing elements of the list in one line + sorted_list = odd_even_sort(input_list) + print("The sorted list is") + print(sorted_list) diff --git a/sorts/patience_sort.py b/sorts/patience_sort.py new file mode 100644 index 000000000000..87f5a4078612 --- /dev/null +++ b/sorts/patience_sort.py @@ -0,0 +1,65 @@ +from bisect import bisect_left +from functools import total_ordering +from heapq import merge +from typing import List + +""" +A pure Python implementation of the patience sort algorithm + +For more information: https://en.wikipedia.org/wiki/Patience_sorting + +This algorithm is based on the card game patience + +For doctests run following command: +python3 -m doctest -v patience_sort.py + +For manual testing run: +python3 patience_sort.py +""" + + +@total_ordering +class Stack(list): + def __lt__(self, other): + return self[-1] < other[-1] + + def __eq__(self, other): + return self[-1] == other[-1] + + +def patience_sort(collection: list) -> list: + """A pure implementation of quick sort algorithm in Python + + :param collection: some mutable ordered collection with heterogeneous + comparable items inside + :return: the same collection ordered by ascending + + Examples: + >>> patience_sort([1, 9, 5, 21, 17, 6]) + [1, 5, 6, 9, 17, 21] + + >>> patience_sort([]) + [] + + >>> patience_sort([-3, -17, -48]) + [-48, -17, -3] + """ + stacks: List[Stack] = [] + # sort into stacks + for element in collection: + new_stacks = Stack([element]) + i = bisect_left(stacks, new_stacks) + if i != len(stacks): + stacks[i].append(element) + else: + stacks.append(new_stacks) + + # use a heap-based merge to merge stack efficiently + collection[:] = merge(*[reversed(stack) for stack in stacks]) + return collection + + +if __name__ == "__main__": + user_input = input("Enter numbers separated by a comma:\n").strip() + unsorted = [int(item) for item in user_input.split(",")] + print(patience_sort(unsorted)) diff --git a/sorts/pigeon_sort.py b/sorts/pigeon_sort.py index cc6205f804dc..3d81f0643865 100644 --- a/sorts/pigeon_sort.py +++ b/sorts/pigeon_sort.py @@ -9,9 +9,10 @@ For manual testing run: python pigeon_sort.py """ +from typing import List -def pigeon_sort(array): +def pigeon_sort(array: List[int]) -> List[int]: """ Implementation of pigeon hole sort algorithm :param array: Collection of comparable items @@ -26,29 +27,17 @@ def pigeon_sort(array): if len(array) == 0: return array - # Manually finds the minimum and maximum of the array. - min = array[0] - max = array[0] - - for i in range(len(array)): - if array[i] < min: - min = array[i] - elif array[i] > max: - max = array[i] + _min, _max = min(array), max(array) # Compute the variables - holes_range = max - min + 1 - holes = [0 for _ in range(holes_range)] - holes_repeat = [0 for _ in range(holes_range)] + holes_range = _max - _min + 1 + holes, holes_repeat = [0] * holes_range, [0] * holes_range # Make the sorting. - for i in range(len(array)): - index = array[i] - min - if holes[index] != array[i]: - holes[index] = array[i] - holes_repeat[index] += 1 - else: - holes_repeat[index] += 1 + for i in array: + index = i - _min + holes[index] = i + holes_repeat[index] += 1 # Makes the array back by replacing the numbers. index = 0 @@ -63,6 +52,9 @@ def pigeon_sort(array): if __name__ == "__main__": + import doctest + + doctest.testmod() user_input = input("Enter numbers separated by comma:\n") unsorted = [int(x) for x in user_input.split(",")] print(pigeon_sort(unsorted)) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index c6687a7fa8d5..6f51f6eca7db 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -7,6 +7,7 @@ For manual testing run: python3 quick_sort.py """ +from typing import List def quick_sort(collection: list) -> list: @@ -26,8 +27,8 @@ def quick_sort(collection: list) -> list: if len(collection) < 2: return collection pivot = collection.pop() # Use the last element as the first pivot - greater = [] # All elements greater than pivot - lesser = [] # All elements less than or equal to pivot + greater: List[int] = [] # All elements greater than pivot + lesser: List[int] = [] # All elements less than or equal to pivot for element in collection: (greater if element > pivot else lesser).append(element) return quick_sort(lesser) + [pivot] + quick_sort(greater) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index 18c6e0f876d2..1a6db6a364f0 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -18,6 +18,53 @@ def quick_sort_3partition(sorting: list, left: int, right: int) -> None: quick_sort_3partition(sorting, b + 1, right) +def quick_sort_lomuto_partition(sorting: list, left: int, right: int) -> None: + """ + A pure Python implementation of quick sort algorithm(in-place) + with Lomuto partition scheme: + https://en.wikipedia.org/wiki/Quicksort#Lomuto_partition_scheme + + :param sorting: sort list + :param left: left endpoint of sorting + :param right: right endpoint of sorting + :return: None + + Examples: + >>> nums1 = [0, 5, 3, 1, 2] + >>> quick_sort_lomuto_partition(nums1, 0, 4) + >>> nums1 + [0, 1, 2, 3, 5] + >>> nums2 = [] + >>> quick_sort_lomuto_partition(nums2, 0, 0) + >>> nums2 + [] + >>> nums3 = [-2, 5, 0, -4] + >>> quick_sort_lomuto_partition(nums3, 0, 3) + >>> nums3 + [-4, -2, 0, 5] + """ + if left < right: + pivot_index = lomuto_partition(sorting, left, right) + quick_sort_lomuto_partition(sorting, left, pivot_index - 1) + quick_sort_lomuto_partition(sorting, pivot_index + 1, right) + + +def lomuto_partition(sorting: list, left: int, right: int) -> int: + """ + Example: + >>> lomuto_partition([1,5,7,6], 0, 3) + 2 + """ + pivot = sorting[right] + store_index = left + for i in range(left, right): + if sorting[i] < pivot: + sorting[store_index], sorting[i] = sorting[i], sorting[store_index] + store_index += 1 + sorting[right], sorting[store_index] = sorting[store_index], sorting[right] + return store_index + + def three_way_radix_quicksort(sorting: list) -> list: """ Three-way radix quicksort: diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 7942462ea10d..b802b5278119 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -1,13 +1,28 @@ +""" +This is a pure Python implementation of the quick sort algorithm +For doctests run following command: +python -m doctest -v radix_sort.py +or +python3 -m doctest -v radix_sort.py +For manual testing run: +python radix_sort.py +""" from __future__ import annotations +from typing import List -def radix_sort(list_of_ints: list[int]) -> list[int]: + +def radix_sort(list_of_ints: List[int]) -> List[int]: """ - radix_sort(range(15)) == sorted(range(15)) + Examples: + >>> radix_sort([0, 5, 3, 2, 2]) + [0, 2, 2, 3, 5] + + >>> radix_sort(list(range(15))) == sorted(range(15)) True - radix_sort(reversed(range(15))) == sorted(range(15)) + >>> radix_sort(list(range(14,-1,-1))) == sorted(range(15)) True - radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) + >>> radix_sort([1,100,10,1000]) == sorted([1,100,10,1000]) True """ RADIX = 10 @@ -15,7 +30,7 @@ def radix_sort(list_of_ints: list[int]) -> list[int]: max_digit = max(list_of_ints) while placement <= max_digit: # declare and initialize empty buckets - buckets = [list() for _ in range(RADIX)] + buckets: List[list] = [list() for _ in range(RADIX)] # split list_of_ints between the buckets for i in list_of_ints: tmp = int((i / placement) % RADIX) @@ -29,3 +44,9 @@ def radix_sort(list_of_ints: list[int]) -> list[int]: # move to next placement *= RADIX return list_of_ints + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 66dd08157df1..89f88b4a961b 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import List + def rec_insertion_sort(collection: list, n: int): """ @@ -70,6 +72,6 @@ def insert_next(collection: list, index: int): if __name__ == "__main__": numbers = input("Enter integers separated by spaces: ") - numbers = [int(num) for num in numbers.split()] - rec_insertion_sort(numbers, len(numbers)) - print(numbers) + number_list: List[int] = [int(num) for num in numbers.split()] + rec_insertion_sort(number_list, len(number_list)) + print(number_list) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index bf3c2c7f9cc6..10ae9ba407ec 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -1,13 +1,5 @@ """ -This is a pure Python implementation of the shell sort algorithm - -For doctests run following command: -python -m doctest -v shell_sort.py -or -python3 -m doctest -v shell_sort.py - -For manual testing run: -python shell_sort.py +https://en.wikipedia.org/wiki/Shellsort#Pseudocode """ @@ -19,26 +11,30 @@ def shell_sort(collection): >>> shell_sort([0, 5, 3, 2, 2]) [0, 2, 2, 3, 5] - >>> shell_sort([]) [] - >>> shell_sort([-2, -5, -45]) [-45, -5, -2] """ # Marcin Ciura's gap sequence - gaps = [701, 301, 132, 57, 23, 10, 4, 1] + gaps = [701, 301, 132, 57, 23, 10, 4, 1] for gap in gaps: for i in range(gap, len(collection)): + insert_value = collection[i] j = i - while j >= gap and collection[j] < collection[j - gap]: - collection[j], collection[j - gap] = collection[j - gap], collection[j] + while j >= gap and collection[j - gap] > insert_value: + collection[j] = collection[j - gap] j -= gap + if j != i: + collection[j] = insert_value return collection if __name__ == "__main__": + from doctest import testmod + + testmod() user_input = input("Enter numbers separated by a comma:\n").strip() unsorted = [int(item) for item in user_input.split(",")] print(shell_sort(unsorted)) diff --git a/sorts/slowsort.py b/sorts/slowsort.py new file mode 100644 index 000000000000..53bb14554ee2 --- /dev/null +++ b/sorts/slowsort.py @@ -0,0 +1,63 @@ +""" +Slowsort is a sorting algorithm. It is of humorous nature and not useful. +It's based on the principle of multiply and surrender, +a tongue-in-cheek joke of divide and conquer. +It was published in 1986 by Andrei Broder and Jorge Stolfi +in their paper Pessimal Algorithms and Simplexity Analysis +(a parody of optimal algorithms and complexity analysis). + +Source: https://en.wikipedia.org/wiki/Slowsort +""" + +from typing import Optional + + +def slowsort( + sequence: list, start: Optional[int] = None, end: Optional[int] = None +) -> None: + """ + Sorts sequence[start..end] (both inclusive) in-place. + start defaults to 0 if not given. + end defaults to len(sequence) - 1 if not given. + It returns None. + >>> seq = [1, 6, 2, 5, 3, 4, 4, 5]; slowsort(seq); seq + [1, 2, 3, 4, 4, 5, 5, 6] + >>> seq = []; slowsort(seq); seq + [] + >>> seq = [2]; slowsort(seq); seq + [2] + >>> seq = [1, 2, 3, 4]; slowsort(seq); seq + [1, 2, 3, 4] + >>> seq = [4, 3, 2, 1]; slowsort(seq); seq + [1, 2, 3, 4] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, 2, 7); seq + [9, 8, 2, 3, 4, 5, 6, 7, 1, 0] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, end = 4); seq + [5, 6, 7, 8, 9, 4, 3, 2, 1, 0] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, start = 5); seq + [9, 8, 7, 6, 5, 0, 1, 2, 3, 4] + """ + if start is None: + start = 0 + + if end is None: + end = len(sequence) - 1 + + if start >= end: + return + + mid = (start + end) // 2 + + slowsort(sequence, start, mid) + slowsort(sequence, mid + 1, end) + + if sequence[end] < sequence[mid]: + sequence[end], sequence[mid] = sequence[mid], sequence[end] + + slowsort(sequence, start, end - 1) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/other/anagrams.py b/strings/anagrams.py similarity index 95% rename from other/anagrams.py rename to strings/anagrams.py index 0be013d5bc47..1a7c675d6719 100644 --- a/other/anagrams.py +++ b/strings/anagrams.py @@ -6,7 +6,7 @@ start_time = time.time() print("creating word list...") path = os.path.split(os.path.realpath(__file__)) -with open(path[0] + "/words") as f: +with open(path[0] + "/words.txt") as f: word_list = sorted(list({word.strip().lower() for word in f})) diff --git a/other/autocomplete_using_trie.py b/strings/autocomplete_using_trie.py similarity index 100% rename from other/autocomplete_using_trie.py rename to strings/autocomplete_using_trie.py diff --git a/other/detecting_english_programmatically.py b/strings/detecting_english_programmatically.py similarity index 100% rename from other/detecting_english_programmatically.py rename to strings/detecting_english_programmatically.py diff --git a/other/dictionary.txt b/strings/dictionary.txt similarity index 100% rename from other/dictionary.txt rename to strings/dictionary.txt diff --git a/other/frequency_finder.py b/strings/frequency_finder.py similarity index 100% rename from other/frequency_finder.py rename to strings/frequency_finder.py diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 54948a96670b..540a21c93da3 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -69,8 +69,4 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: second_word = input("Enter the second word:\n").strip() result = levenshtein_distance(first_word, second_word) - print( - "Levenshtein distance between {} and {} is {}".format( - first_word, second_word, result - ) - ) + print(f"Levenshtein distance between {first_word} and {second_word} is {result}") diff --git a/other/palindrome.py b/strings/palindrome.py similarity index 100% rename from other/palindrome.py rename to strings/palindrome.py diff --git a/strings/reverse_letters.py b/strings/reverse_letters.py new file mode 100644 index 000000000000..10b8a6d72a0f --- /dev/null +++ b/strings/reverse_letters.py @@ -0,0 +1,19 @@ +def reverse_letters(input_str: str) -> str: + """ + Reverses letters in a given string without adjusting the position of the words + >>> reverse_letters('The cat in the hat') + 'ehT tac ni eht tah' + >>> reverse_letters('The quick brown fox jumped over the lazy dog.') + 'ehT kciuq nworb xof depmuj revo eht yzal .god' + >>> reverse_letters('Is this true?') + 'sI siht ?eurt' + >>> reverse_letters("I love Python") + 'I evol nohtyP' + """ + return " ".join([word[::-1] for word in input_str.split()]) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/strings/swap_case.py b/strings/swap_case.py index 71e8aeb3a205..107fda4b52ec 100644 --- a/strings/swap_case.py +++ b/strings/swap_case.py @@ -11,14 +11,9 @@ GITHUB.COM/MAYUR200 """ -import re -# This re.compile() function saves the pattern from 'a' to 'z' and 'A' to 'Z' -# into 'regexp' variable -regexp = re.compile("[^a-zA-Z]+") - -def swap_case(sentence): +def swap_case(sentence: str) -> str: """ This function will convert all lowercase letters to uppercase letters and vice versa. @@ -30,13 +25,13 @@ def swap_case(sentence): for char in sentence: if char.isupper(): new_string += char.lower() - if char.islower(): + elif char.islower(): new_string += char.upper() - if regexp.search(char): + else: new_string += char return new_string if __name__ == "__main__": - print(swap_case(input("Please input sentence:"))) + print(swap_case(input("Please input sentence: "))) diff --git a/strings/upper.py b/strings/upper.py index 411802a2a22f..5edd40b79808 100644 --- a/strings/upper.py +++ b/strings/upper.py @@ -12,9 +12,9 @@ def upper(word: str) -> str: 'WH[]32' """ - # converting to ascii value int value and checking to see if char is a lower letter - # if it is a capital letter it is getting shift by 32 which makes it a capital case - # letter + # Converting to ascii value int value and checking to see if char is a lower letter + # if it is a lowercase letter it is getting shift by 32 which makes it an uppercase + # case letter return "".join(chr(ord(char) - 32) if "a" <= char <= "z" else char for char in word) diff --git a/other/word_patterns.py b/strings/word_patterns.py similarity index 100% rename from other/word_patterns.py rename to strings/word_patterns.py diff --git a/other/words b/strings/words.txt similarity index 100% rename from other/words rename to strings/words.txt diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py index d22ed017878c..85ea5d940d85 100644 --- a/web_programming/covid_stats_via_xpath.py +++ b/web_programming/covid_stats_via_xpath.py @@ -7,7 +7,7 @@ from collections import namedtuple import requests -from lxml import html +from lxml import html # type: ignore covid_data = namedtuple("covid_data", "cases deaths recovered") diff --git a/web_programming/crawl_google_scholar_citation.py b/web_programming/crawl_google_scholar_citation.py new file mode 100644 index 000000000000..d023380c0818 --- /dev/null +++ b/web_programming/crawl_google_scholar_citation.py @@ -0,0 +1,32 @@ +""" +Get the citation from google scholar +using title and year of publication, and volume and pages of journal. +""" + +import requests +from bs4 import BeautifulSoup + + +def get_citation(base_url: str, params: dict) -> str: + """ + Return the citation number. + """ + soup = BeautifulSoup(requests.get(base_url, params=params).content, "html.parser") + div = soup.find("div", attrs={"class": "gs_ri"}) + anchors = div.find("div", attrs={"class": "gs_fl"}).find_all("a") + return anchors[2].get_text() + + +if __name__ == "__main__": + params = { + "title": ( + "Precisely geometry controlled microsupercapacitors for ultrahigh areal " + "capacitance, volumetric capacitance, and energy density" + ), + "journal": "Chem. Mater.", + "volume": 30, + "pages": "3979-3990", + "year": 2018, + "hl": "en", + } + print(get_citation("http://scholar.google.com/scholar_lookup", params=params)) diff --git a/web_programming/currency_converter.py b/web_programming/currency_converter.py new file mode 100644 index 000000000000..447595b0b646 --- /dev/null +++ b/web_programming/currency_converter.py @@ -0,0 +1,192 @@ +""" +This is used to convert the currency using the Amdoren Currency API +https://www.amdoren.com +""" + +import os + +import requests + +URL_BASE = "https://www.amdoren.com/api/currency.php" +TESTING = os.getenv("CI", False) +API_KEY = os.getenv("AMDOREN_API_KEY", "") +if not API_KEY and not TESTING: + raise KeyError("Please put your API key in an environment variable.") + + +# Currency and their description +list_of_currencies = """ +AED United Arab Emirates Dirham +AFN Afghan Afghani +ALL Albanian Lek +AMD Armenian Dram +ANG Netherlands Antillean Guilder +AOA Angolan Kwanza +ARS Argentine Peso +AUD Australian Dollar +AWG Aruban Florin +AZN Azerbaijani Manat +BAM Bosnia & Herzegovina Convertible Mark +BBD Barbadian Dollar +BDT Bangladeshi Taka +BGN Bulgarian Lev +BHD Bahraini Dinar +BIF Burundian Franc +BMD Bermudian Dollar +BND Brunei Dollar +BOB Bolivian Boliviano +BRL Brazilian Real +BSD Bahamian Dollar +BTN Bhutanese Ngultrum +BWP Botswana Pula +BYN Belarus Ruble +BZD Belize Dollar +CAD Canadian Dollar +CDF Congolese Franc +CHF Swiss Franc +CLP Chilean Peso +CNY Chinese Yuan +COP Colombian Peso +CRC Costa Rican Colon +CUC Cuban Convertible Peso +CVE Cape Verdean Escudo +CZK Czech Republic Koruna +DJF Djiboutian Franc +DKK Danish Krone +DOP Dominican Peso +DZD Algerian Dinar +EGP Egyptian Pound +ERN Eritrean Nakfa +ETB Ethiopian Birr +EUR Euro +FJD Fiji Dollar +GBP British Pound Sterling +GEL Georgian Lari +GHS Ghanaian Cedi +GIP Gibraltar Pound +GMD Gambian Dalasi +GNF Guinea Franc +GTQ Guatemalan Quetzal +GYD Guyanaese Dollar +HKD Hong Kong Dollar +HNL Honduran Lempira +HRK Croatian Kuna +HTG Haiti Gourde +HUF Hungarian Forint +IDR Indonesian Rupiah +ILS Israeli Shekel +INR Indian Rupee +IQD Iraqi Dinar +IRR Iranian Rial +ISK Icelandic Krona +JMD Jamaican Dollar +JOD Jordanian Dinar +JPY Japanese Yen +KES Kenyan Shilling +KGS Kyrgystani Som +KHR Cambodian Riel +KMF Comorian Franc +KPW North Korean Won +KRW South Korean Won +KWD Kuwaiti Dinar +KYD Cayman Islands Dollar +KZT Kazakhstan Tenge +LAK Laotian Kip +LBP Lebanese Pound +LKR Sri Lankan Rupee +LRD Liberian Dollar +LSL Lesotho Loti +LYD Libyan Dinar +MAD Moroccan Dirham +MDL Moldovan Leu +MGA Malagasy Ariary +MKD Macedonian Denar +MMK Myanma Kyat +MNT Mongolian Tugrik +MOP Macau Pataca +MRO Mauritanian Ouguiya +MUR Mauritian Rupee +MVR Maldivian Rufiyaa +MWK Malawi Kwacha +MXN Mexican Peso +MYR Malaysian Ringgit +MZN Mozambican Metical +NAD Namibian Dollar +NGN Nigerian Naira +NIO Nicaragua Cordoba +NOK Norwegian Krone +NPR Nepalese Rupee +NZD New Zealand Dollar +OMR Omani Rial +PAB Panamanian Balboa +PEN Peruvian Nuevo Sol +PGK Papua New Guinean Kina +PHP Philippine Peso +PKR Pakistani Rupee +PLN Polish Zloty +PYG Paraguayan Guarani +QAR Qatari Riyal +RON Romanian Leu +RSD Serbian Dinar +RUB Russian Ruble +RWF Rwanda Franc +SAR Saudi Riyal +SBD Solomon Islands Dollar +SCR Seychellois Rupee +SDG Sudanese Pound +SEK Swedish Krona +SGD Singapore Dollar +SHP Saint Helena Pound +SLL Sierra Leonean Leone +SOS Somali Shilling +SRD Surinamese Dollar +SSP South Sudanese Pound +STD Sao Tome and Principe Dobra +SYP Syrian Pound +SZL Swazi Lilangeni +THB Thai Baht +TJS Tajikistan Somoni +TMT Turkmenistani Manat +TND Tunisian Dinar +TOP Tonga Paanga +TRY Turkish Lira +TTD Trinidad and Tobago Dollar +TWD New Taiwan Dollar +TZS Tanzanian Shilling +UAH Ukrainian Hryvnia +UGX Ugandan Shilling +USD United States Dollar +UYU Uruguayan Peso +UZS Uzbekistan Som +VEF Venezuelan Bolivar +VND Vietnamese Dong +VUV Vanuatu Vatu +WST Samoan Tala +XAF Central African CFA franc +XCD East Caribbean Dollar +XOF West African CFA franc +XPF CFP Franc +YER Yemeni Rial +ZAR South African Rand +ZMW Zambian Kwacha +""" + + +def convert_currency( + from_: str = "USD", to: str = "INR", amount: float = 1.0, api_key: str = API_KEY +) -> str: + """https://www.amdoren.com/currency-api/""" + params = locals() + params["from"] = params.pop("from_") + res = requests.get(URL_BASE, params=params).json() + return str(res["amount"]) if res["error"] == 0 else res["error_message"] + + +if __name__ == "__main__": + print( + convert_currency( + input("Enter from currency: ").strip(), + input("Enter to currency: ").strip(), + float(input("Enter the amount: ").strip()), + ) + ) diff --git a/web_programming/emails_from_url.py b/web_programming/emails_from_url.py index 01dee274f015..0571ac3313a3 100644 --- a/web_programming/emails_from_url.py +++ b/web_programming/emails_from_url.py @@ -8,18 +8,19 @@ import re from html.parser import HTMLParser +from typing import Optional from urllib import parse import requests class Parser(HTMLParser): - def __init__(self, domain: str): - HTMLParser.__init__(self) - self.data = [] + def __init__(self, domain: str) -> None: + super().__init__() + self.urls: list[str] = [] self.domain = domain - def handle_starttag(self, tag: str, attrs: str) -> None: + def handle_starttag(self, tag: str, attrs: list[tuple[str, Optional[str]]]) -> None: """ This function parse html to take takes url from tags """ @@ -29,10 +30,10 @@ def handle_starttag(self, tag: str, attrs: str) -> None: for name, value in attrs: # If href is defined, and not empty nor # print it. if name == "href" and value != "#" and value != "": - # If not already in data. - if value not in self.data: + # If not already in urls. + if value not in self.urls: url = parse.urljoin(self.domain, value) - self.data.append(url) + self.urls.append(url) # Get main domain name (example.com) @@ -59,7 +60,7 @@ def get_sub_domain_name(url: str) -> str: return parse.urlparse(url).netloc -def emails_from_url(/service/url: str = "https://github.com/") -> list: +def emails_from_url(/service/url: str = "https://github.com/") -> list[str]: """ This function takes url and return all valid urls """ @@ -78,7 +79,7 @@ def emails_from_url(/service/url: str = "https://github.com/") -> list: # Get links and loop through valid_emails = set() - for link in parser.data: + for link in parser.urls: # open URL. # read = requests.get(link) try: diff --git a/web_programming/fetch_github_info.py b/web_programming/fetch_github_info.py index 227598bb20ab..c9198460f211 100644 --- a/web_programming/fetch_github_info.py +++ b/web_programming/fetch_github_info.py @@ -1,26 +1,50 @@ #!/usr/bin/env python3 - """ Created by sarathkaul on 14/11/19 +Updated by lawric1 on 24/11/20 -Basic authentication using an API password is deprecated and will soon no longer work. -Visit https://developer.github.com/changes/2020-02-14-deprecating-password-auth -for more information around suggested workarounds and removal dates. -""" +Authentication will be made via access token. +To generate your personal access token visit https://github.com/settings/tokens. + +NOTE: +Never hardcode any credential information in the code. Always use an environment +file to store the private information and use the `os` module to get the information +during runtime. +Create a ".env" file in the root directory and write these two lines in that file +with your token:: + +#!/usr/bin/env bash +export USER_TOKEN="" +""" +import os +from typing import Any, Dict import requests -_GITHUB_API = "https://api.github.com/user" +BASE_URL = "https://api.github.com/" +# https://docs.github.com/en/free-pro-team@latest/rest/reference/users#get-the-authenticated-user +AUTHENTICATED_USER_ENDPOINT = BASE_URL + "/user" -def fetch_github_info(auth_user: str, auth_pass: str) -> dict: +# https://github.com/settings/tokens +USER_TOKEN = os.environ.get("USER_TOKEN", "") + + +def fetch_github_info(auth_token: str) -> Dict[Any, Any]: """ Fetch GitHub info of a user using the requests module """ - return requests.get(_GITHUB_API, auth=(auth_user, auth_pass)).json() - - -if __name__ == "__main__": - for key, value in fetch_github_info("", "").items(): - print(f"{key}: {value}") + headers = { + "Authorization": f"token {auth_token}", + "Accept": "application/vnd.github.v3+json", + } + return requests.get(AUTHENTICATED_USER_ENDPOINT, headers=headers).json() + + +if __name__ == "__main__": # pragma: no cover + if USER_TOKEN: + for key, value in fetch_github_info(USER_TOKEN).items(): + print(f"{key}: {value}") + else: + raise ValueError("'USER_TOKEN' field cannot be empty.") diff --git a/web_programming/instagram_crawler.py b/web_programming/instagram_crawler.py index c81635bd3593..4536257a984e 100644 --- a/web_programming/instagram_crawler.py +++ b/web_programming/instagram_crawler.py @@ -23,7 +23,7 @@ class InstagramUser: """ Class Instagram crawl instagram user information - Usage: (doctest failing on Travis CI) + Usage: (doctest failing on GitHub Actions) # >>> instagram_user = InstagramUser("github") # >>> instagram_user.is_verified True @@ -102,10 +102,10 @@ def test_instagram_user(username: str = "github") -> None: A self running doctest >>> test_instagram_user() """ - from os import getenv + import os - if getenv("CONTINUOUS_INTEGRATION"): - return # test failing on Travis CI + if os.environ.get("CI"): + return None # test failing on GitHub Actions instagram_user = InstagramUser(username) assert instagram_user.user_data assert isinstance(instagram_user.user_data, dict) diff --git a/web_programming/instagram_pic.py b/web_programming/instagram_pic.py new file mode 100644 index 000000000000..8521da674d7d --- /dev/null +++ b/web_programming/instagram_pic.py @@ -0,0 +1,16 @@ +from datetime import datetime + +import requests +from bs4 import BeautifulSoup + +if __name__ == "__main__": + url = input("Enter image url: ").strip() + print(f"Downloading image from {url} ...") + soup = BeautifulSoup(requests.get(url).content, "html.parser") + # The image URL is in the content field of the first meta tag with property og:image + image_url = soup.find("meta", {"property": "og:image"})["content"] + image_data = requests.get(image_url).content + file_name = f"{datetime.now():%Y-%m-%d_%H:%M:%S}.jpg" + with open(file_name, "wb") as fp: + fp.write(image_data) + print(f"Done. Image saved to disk as {file_name}.") diff --git a/web_programming/instagram_video.py b/web_programming/instagram_video.py new file mode 100644 index 000000000000..243cece1a50e --- /dev/null +++ b/web_programming/instagram_video.py @@ -0,0 +1,17 @@ +from datetime import datetime + +import requests + + +def download_video(url: str) -> bytes: + base_url = "https://downloadgram.net/wp-json/wppress/video-downloader/video?url=" + video_url = requests.get(base_url + url).json()[0]["urls"][0]["src"] + return requests.get(video_url).content + + +if __name__ == "__main__": + url = input("Enter Video/IGTV url: ").strip() + file_name = f"{datetime.now():%Y-%m-%d_%H:%M:%S}.mp4" + with open(file_name, "wb") as fp: + fp.write(download_video(url)) + print(f"Done. Video saved to disk as {file_name}.") diff --git a/web_programming/slack_message.py b/web_programming/slack_message.py index 8ea9d5d0add2..f35aa3ca587e 100644 --- a/web_programming/slack_message.py +++ b/web_programming/slack_message.py @@ -13,7 +13,7 @@ def send_slack_message(message_body: str, slack_url: str) -> None: ) -if __name__ == "main": +if __name__ == "__main__": # Set the slack url to the one provided by Slack when you create the webhook at # https://my.slack.com/services/new/incoming-webhook/ send_slack_message("", "") diff --git a/web_programming/test_fetch_github_info.py b/web_programming/test_fetch_github_info.py new file mode 100644 index 000000000000..2da97c782df7 --- /dev/null +++ b/web_programming/test_fetch_github_info.py @@ -0,0 +1,27 @@ +import json + +import requests + +from .fetch_github_info import AUTHENTICATED_USER_ENDPOINT, fetch_github_info + + +def test_fetch_github_info(monkeypatch): + class FakeResponse: + def __init__(self, content) -> None: + assert isinstance(content, (bytes, str)) + self.content = content + + def json(self): + return json.loads(self.content) + + def mock_response(*args, **kwargs): + assert args[0] == AUTHENTICATED_USER_ENDPOINT + assert "Authorization" in kwargs["headers"] + assert kwargs["headers"]["Authorization"].startswith("token ") + assert "Accept" in kwargs["headers"] + return FakeResponse(b'{"login":"test","id":1}') + + monkeypatch.setattr(requests, "get", mock_response) + result = fetch_github_info("token") + assert result["login"] == "test" + assert result["id"] == 1