Merge branch 'master' into patch-1

This commit is contained in:
Tianyi Zheng 2023-07-31 14:17:59 -07:00 committed by GitHub
commit c96dc36e04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
890 changed files with 70576 additions and 9226 deletions

View File

@ -1,4 +0,0 @@
[report]
sort = Cover
omit =
.env/*

8
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,8 @@
# https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/README.md
ARG VARIANT=3.11-bookworm
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
COPY requirements.txt /tmp/pip-tmp/
RUN python3 -m pip install --upgrade pip \
&& python3 -m pip install --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
&& pipx install pre-commit ruff \
&& pre-commit install

View File

@ -0,0 +1,42 @@
{
"name": "Python 3",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
"args": {
// Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6
// Append -bullseye or -buster to pin to an OS version.
// Use -bullseye variants on local on arm64/Apple Silicon.
"VARIANT": "3.11-bookworm",
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.linting.enabled": true,
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance"
]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

10
.github/CODEOWNERS vendored
View File

@ -31,11 +31,11 @@
# /data_structures/ @cclauss # TODO: Uncomment this line after Hacktoberfest # /data_structures/ @cclauss # TODO: Uncomment this line after Hacktoberfest
/digital_image_processing/ @mateuszz0000 # /digital_image_processing/
# /divide_and_conquer/ # /divide_and_conquer/
/dynamic_programming/ @Kush1101 # /dynamic_programming/
# /file_transfer/ # /file_transfer/
@ -59,7 +59,7 @@
# /machine_learning/ # /machine_learning/
/maths/ @Kush1101 # /maths/
# /matrix/ # /matrix/
@ -69,7 +69,7 @@
# /other/ @cclauss # TODO: Uncomment this line after Hacktoberfest # /other/ @cclauss # TODO: Uncomment this line after Hacktoberfest
/project_euler/ @dhruvmanila @Kush1101 /project_euler/ @dhruvmanila
# /quantum/ # /quantum/
@ -79,7 +79,7 @@
# /searches/ # /searches/
/sorts/ @mateuszz0000 # /sorts/
# /strings/ @cclauss # TODO: Uncomment this line after Hacktoberfest # /strings/ @cclauss # TODO: Uncomment this line after Hacktoberfest

54
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,54 @@
name: Bug report
description: Create a bug report to help us address errors in the repository
labels: [bug]
body:
- type: markdown
attributes:
value: >
Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/bug).
Usage questions such as "How do I...?" belong on the
[Discord](https://discord.gg/c7MnfGFGa6) and will be closed.
- type: input
attributes:
label: "Repository commit"
description: >
The commit hash for `TheAlgorithms/Python` repository. You can get this
by running the command `git rev-parse HEAD` locally.
placeholder: "a0b0f414ae134aa1772d33bb930e5a960f9979e8"
validations:
required: true
- type: input
attributes:
label: "Python version (python --version)"
placeholder: "Python 3.10.7"
validations:
required: true
- type: textarea
attributes:
label: "Dependencies version (pip freeze)"
description: >
This is the output of the command `pip freeze --all`. Note that the
actual output might be different as compared to the placeholder text.
placeholder: |
appnope==0.1.3
asttokens==2.0.8
backcall==0.2.0
...
validations:
required: true
- type: textarea
attributes:
label: "Expected behavior"
description: "Describe the behavior you expect. May include images or videos."
validations:
required: true
- type: textarea
attributes:
label: "Actual behavior"
validations:
required: true

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Discord community
url: https://discord.gg/c7MnfGFGa6
about: Have any questions or need any help? Please contact us via Discord

View File

@ -0,0 +1,19 @@
name: Feature request
description: Suggest features, propose improvements, discuss new ideas.
labels: [enhancement]
body:
- type: markdown
attributes:
value: >
Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/enhancement).
Usage questions such as "How do I...?" belong on the
[Discord](https://discord.gg/c7MnfGFGa6) and will be closed.
- type: textarea
attributes:
label: "Feature description"
description: >
This could be new algorithms, data structures or improving any existing
implementations.
validations:
required: true

19
.github/ISSUE_TEMPLATE/other.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Other
description: Use this for any other issues. PLEASE do not create blank issues
labels: ["awaiting triage"]
body:
- type: textarea
id: issuedescription
attributes:
label: What would you like to share?
description: Provide a clear and concise explanation of your issue.
validations:
required: true
- type: textarea
id: extrainfo
attributes:
label: Additional information
description: Is there anything else we should know about this issue?
validations:
required: false

View File

@ -1,4 +1,4 @@
### **Describe your change:** ### Describe your change:
@ -6,7 +6,7 @@
* [ ] Fix a bug or typo in an existing algorithm? * [ ] Fix a bug or typo in an existing algorithm?
* [ ] Documentation change? * [ ] Documentation change?
### **Checklist:** ### Checklist:
* [ ] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md). * [ ] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md).
* [ ] This pull request is all my own work -- I have not plagiarized. * [ ] This pull request is all my own work -- I have not plagiarized.
* [ ] I know that pull requests will not be merged if they fail the automated tests. * [ ] I know that pull requests will not be merged if they fail the automated tests.
@ -16,5 +16,5 @@
* [ ] All functions and variable names follow Python naming conventions. * [ ] All functions and variable names follow Python naming conventions.
* [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html). * [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html).
* [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing. * [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing.
* [ ] All new algorithms have a URL in its comments that points to Wikipedia or other similar explanation. * [ ] All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
* [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. * [ ] If this pull request resolves one or more open issues then the description above includes the issue number(s) with a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue): "Fixes #ISSUE-NUMBER".

4
.github/stale.yml vendored
View File

@ -45,7 +45,7 @@ pulls:
closeComment: > closeComment: >
Please reopen this pull request once you commit the changes requested 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 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) some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms/community)
or ping one of the reviewers. Thank you for your contributions! or ping one of the reviewers. Thank you for your contributions!
issues: issues:
@ -59,5 +59,5 @@ issues:
closeComment: > closeComment: >
Please reopen this issue once you add more information and updates here. 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 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 from our [Gitter](https://gitter.im/TheAlgorithms/community) or ping one of the
reviewers. Thank you for your contributions! reviewers. Thank you for your contributions!

View File

@ -9,20 +9,25 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with: with:
python-version: "3.9" python-version: 3.11
- uses: actions/cache@v2 - uses: actions/cache@v3
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }} key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip setuptools six wheel python -m pip install --upgrade pip setuptools six wheel
python -m pip install mypy pytest-cov -r requirements.txt python -m pip install pytest-cov -r requirements.txt
- run: mypy --install-types --non-interactive .
- name: Run tests - name: Run tests
run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . # TODO: #8818 Re-enable quantum tests
run: pytest
--ignore=quantum/q_fourier_transform.py
--ignore=project_euler/
--ignore=scripts/validate_solutions.py
--cov-report=term-missing:skip-covered
--cov=. .
- if: ${{ success() }} - if: ${{ success() }}
run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md

View File

@ -6,8 +6,10 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 # v1, NOT v2 - uses: actions/checkout@v1 # v1, NOT v2 or v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Write DIRECTORY.md - name: Write DIRECTORY.md
run: | run: |
scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md

View File

@ -1,22 +0,0 @@
name: pre-commit
on: [push, pull_request]
jobs:
pre-commit:
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@21.4b0
- name: Install pre-commit
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade pre-commit
- run: pre-commit run --verbose --all-files --show-diff-on-failure

View File

@ -14,8 +14,10 @@ jobs:
project-euler: project-euler:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Install pytest and pytest-cov - name: Install pytest and pytest-cov
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
@ -24,8 +26,10 @@ jobs:
validate-solutions: validate-solutions:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-python@v2 - uses: actions/setup-python@v4
with:
python-version: 3.x
- name: Install pytest and requests - name: Install pytest and requests
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip

16
.github/workflows/ruff.yml vendored Normal file
View File

@ -0,0 +1,16 @@
# https://beta.ruff.rs
name: ruff
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pip install --user ruff
- run: ruff --format=github .

1
.gitignore vendored
View File

@ -107,3 +107,4 @@ venv.bak/
.idea .idea
.try .try
.vscode/ .vscode/
.vs/

View File

@ -1,56 +1,42 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0 rev: v4.4.0
hooks: hooks:
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
- id: check-toml
- id: check-yaml - id: check-yaml
- id: end-of-file-fixer - id: end-of-file-fixer
types: [python] types: [python]
- id: trailing-whitespace - id: trailing-whitespace
exclude: |
(?x)^(
data_structures/heap/binomial_heap.py
)$
- id: requirements-txt-fixer - id: requirements-txt-fixer
- repo: https://github.com/MarcoGorelli/auto-walrus
rev: v0.2.2
hooks:
- id: auto-walrus
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.280
hooks:
- id: ruff
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 21.4b0 rev: 23.7.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/PyCQA/isort
rev: 5.8.0
hooks:
- id: isort
args:
- --profile=black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.9.1
hooks:
- id: flake8
args:
- --ignore=E203,W503
- --max-complexity=25
- --max-line-length=88
# FIXME: fix mypy errors and then uncomment this
# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v0.782
# hooks:
# - id: mypy
# args:
# - --ignore-missing-imports
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.0.0 rev: v2.2.5
hooks: hooks:
- id: codespell - id: codespell
args: additional_dependencies:
- --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim - tomli
- --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt"
- --quiet-level=2 - repo: https://github.com/tox-dev/pyproject-fmt
exclude: | rev: "0.13.0"
(?x)^( hooks:
strings/dictionary.txt | - id: pyproject-fmt
strings/words.txt |
project_euler/problem_022/p022_names.txt
)$
- repo: local - repo: local
hooks: hooks:
- id: validate-filenames - id: validate-filenames
@ -58,3 +44,18 @@ repos:
entry: ./scripts/validate_filenames.py entry: ./scripts/validate_filenames.py
language: script language: script
pass_filenames: false pass_filenames: false
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.13
hooks:
- id: validate-pyproject
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1
hooks:
- id: mypy
args:
- --ignore-missing-imports
- --install-types # See mirrors-mypy README.md
- --non-interactive
additional_dependencies: [types-requests]

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"githubPullRequests.ignoredPullRequestBranches": [
"master"
]
}

View File

@ -2,18 +2,18 @@
## Before contributing ## Before contributing
Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before sending your pull requests, make sure that you __read the whole guidelines__. If you have any doubt on the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community in [Gitter](https://gitter.im/TheAlgorithms). Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before sending your pull requests, make sure that you __read the whole guidelines__. If you have any doubt on the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community in [Gitter](https://gitter.im/TheAlgorithms/community).
## Contributing ## Contributing
### Contributor ### Contributor
We are very happy that you consider implementing algorithms and data structure for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that: We are very happy that you are considering implementing algorithms and data structures for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that:
- You did your work - no plagiarism allowed - You did your work - no plagiarism allowed
- Any plagiarized work will not be merged. - Any plagiarized work will not be merged.
- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged - 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 - Your 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 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. __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.
@ -23,9 +23,16 @@ __Improving comments__ and __writing proper tests__ are also highly welcome.
We appreciate any contribution, from fixing a grammar mistake in a comment to implementing complex algorithms. Please read this section if you are contributing your work. We appreciate any contribution, from fixing a grammar mistake in a comment to implementing complex algorithms. Please read this section if you are contributing your work.
Your contribution will be tested by our [automated testing on Travis CI](https://travis-ci.org/TheAlgorithms/Python/pull_requests) to save time and mental energy. After you have submitted your pull request, you should see the Travis tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the Travis output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help. Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the GitHub Actions output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help.
Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the commit message of pull requests that resolve open issues. GitHub will use this tag to auto close the issue when the PR is merged. If you are interested in resolving an [open issue](https://github.com/TheAlgorithms/Python/issues), simply make a pull request with your proposed fix. __We do not assign issues in this repo__ so please do not ask for permission to work on an issue.
Please help us keep our issue list small by adding `Fixes #{$ISSUE_NUMBER}` to the description of pull requests that resolve open issues.
For example, if your pull request fixes issue #10, then please add the following to its description:
```
Fixes #10
```
GitHub will use this tag to [auto-close the issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if and when the PR is merged.
#### What is an Algorithm? #### What is an Algorithm?
@ -53,7 +60,7 @@ Algorithms in this repo should not be how-to examples for existing Python packag
Use [pre-commit](https://pre-commit.com/#installation) to automatically format your code to match our coding style: Use [pre-commit](https://pre-commit.com/#installation) to automatically format your code to match our coding style:
```bash ```bash
python3 -m pip install pre-commit # required only once python3 -m pip install pre-commit # only required the first time
pre-commit install 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: 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:
@ -66,8 +73,8 @@ pre-commit run --all-files --show-diff-on-failure
We want your work to be readable by others; therefore, we encourage you to note the following: We want your work to be readable by others; therefore, we encourage you to note the following:
- Please write in Python 3.9+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will. - Please write in Python 3.11+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
- Please focus hard on naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments. - Please focus hard on the naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments.
- Single letter variable names are *old school* so please avoid them unless their life only spans a few lines. - Single letter variable names are *old school* so please avoid them unless their life only spans a few lines.
- Expand acronyms because `gcd()` is hard to understand but `greatest_common_divisor()` is not. - Expand acronyms because `gcd()` is hard to understand but `greatest_common_divisor()` is not.
- Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc. - Please follow the [Python Naming Conventions](https://pep8.org/#prescriptive-naming-conventions) so variable_names and function_names should be lower_case, CONSTANTS in UPPERCASE, ClassNames should be CamelCase, etc.
@ -81,11 +88,11 @@ We want your work to be readable by others; therefore, we encourage you to note
black . 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. - All submissions will need to pass the test `ruff .` before they will be accepted so if possible, try this test locally on your Python file(s) before submitting your pull request.
```bash ```bash
python3 -m pip install flake8 # only required the first time python3 -m pip install ruff # only required the first time
flake8 . --ignore=E203,W503 --max-line-length=88 --show-source ruff .
``` ```
- Original code submission require docstrings or comments to describe your work. - Original code submission require docstrings or comments to describe your work.
@ -102,7 +109,7 @@ We want your work to be readable by others; therefore, we encourage you to note
This is too trivial. Comments are expected to be explanatory. For comments, you can write them above, on or below a line of code, as long as you are consistent within the same piece of code. This is too trivial. Comments are expected to be explanatory. For comments, you can write them above, on or below a line of code, as long as you are consistent within the same piece of code.
We encourage you to put docstrings inside your functions but please pay attention to indentation of docstrings. The following is a good example: We encourage you to put docstrings inside your functions but please pay attention to the indentation of docstrings. The following is a good example:
```python ```python
def sum_ab(a, b): def sum_ab(a, b):
@ -160,7 +167,7 @@ We want your work to be readable by others; therefore, we encourage you to note
- [__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. - [__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. - 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. - 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 #### 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. - 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.
@ -170,13 +177,13 @@ We want your work to be readable by others; therefore, we encourage you to note
- If possible, follow the standard *within* the folder you are submitting to. - If possible, follow the standard *within* the folder you are submitting to.
- If you have modified/added code work, make sure the code compiles before submitting. - If you have modified/added code work, make sure the code compiles before submitting.
- If you have modified/added documentation work, ensure your language is concise and contains no grammar errors. - If you have modified/added documentation work, ensure your language is concise and contains no grammar errors.
- Do not update the README.md or DIRECTORY.md file which will be periodically autogenerated by our Travis CI processes. - Do not update the README.md or DIRECTORY.md file which will be periodically autogenerated by our GitHub Actions processes.
- Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended). - Add a corresponding explanation to [Algorithms-Explanation](https://github.com/TheAlgorithms/Algorithms-Explanation) (Optional but recommended).
- All submissions will be tested with [__mypy__](http://www.mypy-lang.org) so we encourage to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so. - All submissions will be tested with [__mypy__](http://www.mypy-lang.org) so we encourage you to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so.
- Most importantly, - Most importantly,
- __Be consistent in the use of these guidelines when submitting.__ - __Be consistent in the use of these guidelines when submitting.__
- __Join__ [Gitter](https://gitter.im/TheAlgorithms) __now!__ - __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms/community) __now!__
- Happy coding! - Happy coding!
Writer [@poyea](https://github.com/poyea), Jun 2019. Writer [@poyea](https://github.com/poyea), Jun 2019.

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2016-2021 The Algorithms Copyright (c) 2016-2022 TheAlgorithms and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -12,23 +12,17 @@
<a href="https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md"> <a href="https://github.com/TheAlgorithms/Python/blob/master/CONTRIBUTING.md">
<img src="https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square" height="20" alt="Contributions Welcome"> <img src="https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square" height="20" alt="Contributions Welcome">
</a> </a>
<a href="https://www.paypal.me/TheAlgorithms/100">
<img src="https://img.shields.io/badge/Donate-PayPal-green.svg?logo=paypal&style=flat-square" height="20" alt="Donate">
</a>
<img src="https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square" height="20"> <img src="https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square" height="20">
<a href="https://discord.gg/c7MnfGFGa6"> <a href="https://the-algorithms.com/discord">
<img src="https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square" height="20" alt="Discord chat"> <img src="https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square" height="20" alt="Discord chat">
</a> </a>
<a href="https://gitter.im/TheAlgorithms"> <a href="https://gitter.im/TheAlgorithms/community">
<img src="https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square" height="20" alt="Gitter chat"> <img src="https://img.shields.io/badge/Chat-Gitter-ff69b4.svg?label=Chat&logo=gitter&style=flat-square" height="20" alt="Gitter chat">
</a> </a>
<!-- Second row: --> <!-- Second row: -->
<br> <br>
<a href="https://github.com/TheAlgorithms/Python/actions"> <a href="https://github.com/TheAlgorithms/Python/actions">
<img src="https://img.shields.io/github/workflow/status/TheAlgorithms/Python/build?label=CI&logo=github&style=flat-square" height="20" alt="GitHub Workflow Status"> <img src="https://img.shields.io/github/actions/workflow/status/TheAlgorithms/Python/build.yml?branch=master&label=CI&logo=github&style=flat-square" height="20" alt="GitHub Workflow Status">
</a>
<a href="https://lgtm.com/projects/g/TheAlgorithms/Python/alerts">
<img src="https://img.shields.io/lgtm/alerts/github/TheAlgorithms/Python.svg?label=LGTM&logo=LGTM&style=flat-square" height="20" alt="LGTM">
</a> </a>
<a href="https://github.com/pre-commit/pre-commit"> <a href="https://github.com/pre-commit/pre-commit">
<img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" height="20" alt="pre-commit"> <img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" height="20" alt="pre-commit">
@ -40,7 +34,7 @@
<h3>All algorithms implemented in Python - for education</h3> <h3>All algorithms implemented in Python - for education</h3>
</div> </div>
Implementations are for learning purposes only. As they may be less efficient than the implementations in the Python standard library, use them at your discretion. Implementations are for learning purposes only. They may be less efficient than the implementations in the Python standard library. Use them at your discretion.
## Getting Started ## Getting Started
@ -48,8 +42,8 @@ Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribut
## Community Channels ## Community Channels
We're on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms)! Community channels are great for you to ask questions and get help. Please join us! We are on [Discord](https://the-algorithms.com/discord) and [Gitter](https://gitter.im/TheAlgorithms/community)! Community channels are a great way for you to ask questions and get help. Please join us!
## List of Algorithms ## List of Algorithms
See our [directory](DIRECTORY.md) for easier navigation and better overview of the project. See our [directory](DIRECTORY.md) for easier navigation and a better overview of the project.

View File

@ -0,0 +1,7 @@
# Arithmetic analysis
Arithmetic analysis is a branch of mathematics that deals with solving linear equations.
* <https://en.wikipedia.org/wiki/System_of_linear_equations>
* <https://en.wikipedia.org/wiki/Gaussian_elimination>
* <https://en.wikipedia.org/wiki/Root-finding_algorithms>

View File

@ -1,4 +1,4 @@
from typing import Callable from collections.abc import Callable
def bisection(function: Callable[[float], float], a: float, b: float) -> float: def bisection(function: Callable[[float], float], a: float, b: float) -> float:
@ -8,7 +8,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float:
1.0000000149011612 1.0000000149011612
>>> bisection(lambda x: x ** 3 - 1, 2, 1000) >>> bisection(lambda x: x ** 3 - 1, 2, 1000)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: could not find root in given interval. ValueError: could not find root in given interval.
>>> bisection(lambda x: x ** 2 - 4 * x + 3, 0, 2) >>> bisection(lambda x: x ** 2 - 4 * x + 3, 0, 2)
1.0 1.0
@ -16,7 +16,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float:
3.0 3.0
>>> bisection(lambda x: x ** 2 - 4 * x + 3, 4, 1000) >>> bisection(lambda x: x ** 2 - 4 * x + 3, 4, 1000)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: could not find root in given interval. ValueError: could not find root in given interval.
""" """
start: float = a start: float = a
@ -32,7 +32,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float:
raise ValueError("could not find root in given interval.") raise ValueError("could not find root in given interval.")
else: else:
mid: float = start + (end - start) / 2.0 mid: float = start + (end - start) / 2.0
while abs(start - mid) > 10 ** -7: # until precisely equals to 10^-7 while abs(start - mid) > 10**-7: # until precisely equals to 10^-7
if function(mid) == 0: if function(mid) == 0:
return mid return mid
elif function(mid) * function(start) < 0: elif function(mid) * function(start) < 0:
@ -44,7 +44,7 @@ def bisection(function: Callable[[float], float], a: float, b: float) -> float:
def f(x: float) -> float: def f(x: float) -> float:
return x ** 3 - 2 * x - 5 return x**3 - 2 * x - 5
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -5,9 +5,13 @@ Gaussian elimination - https://en.wikipedia.org/wiki/Gaussian_elimination
import numpy as np import numpy as np
from numpy import float64
from numpy.typing import NDArray
def retroactive_resolution(coefficients: np.matrix, vector: np.ndarray) -> np.ndarray: def retroactive_resolution(
coefficients: NDArray[float64], vector: NDArray[float64]
) -> NDArray[float64]:
""" """
This function performs a retroactive linear system resolution This function performs a retroactive linear system resolution
for triangular matrix for triangular matrix
@ -27,18 +31,20 @@ def retroactive_resolution(coefficients: np.matrix, vector: np.ndarray) -> np.nd
rows, columns = np.shape(coefficients) rows, columns = np.shape(coefficients)
x = np.zeros((rows, 1), dtype=float) x: NDArray[float64] = np.zeros((rows, 1), dtype=float)
for row in reversed(range(rows)): for row in reversed(range(rows)):
sum = 0 total = 0
for col in range(row + 1, columns): for col in range(row + 1, columns):
sum += coefficients[row, col] * x[col] total += coefficients[row, col] * x[col]
x[row, 0] = (vector[row] - sum) / coefficients[row, row] x[row, 0] = (vector[row] - total) / coefficients[row, row]
return x return x
def gaussian_elimination(coefficients: np.matrix, vector: np.ndarray) -> np.ndarray: def gaussian_elimination(
coefficients: NDArray[float64], vector: NDArray[float64]
) -> NDArray[float64]:
""" """
This function performs Gaussian elimination method This function performs Gaussian elimination method
@ -60,7 +66,7 @@ def gaussian_elimination(coefficients: np.matrix, vector: np.ndarray) -> np.ndar
return np.array((), dtype=float) return np.array((), dtype=float)
# augmented matrix # augmented matrix
augmented_mat = np.concatenate((coefficients, vector), axis=1) augmented_mat: NDArray[float64] = np.concatenate((coefficients, vector), axis=1)
augmented_mat = augmented_mat.astype("float64") augmented_mat = augmented_mat.astype("float64")
# scale the matrix leaving it triangular # scale the matrix leaving it triangular

View File

@ -3,7 +3,8 @@ Checks if a system of forces is in static equilibrium.
""" """
from __future__ import annotations from __future__ import annotations
from numpy import array, cos, cross, ndarray, radians, sin from numpy import array, cos, cross, float64, radians, sin
from numpy.typing import NDArray
def polar_force( def polar_force(
@ -12,10 +13,17 @@ def polar_force(
""" """
Resolves force along rectangular components. Resolves force along rectangular components.
(force, angle) => (force_x, force_y) (force, angle) => (force_x, force_y)
>>> polar_force(10, 45) >>> import math
[7.0710678118654755, 7.071067811865475] >>> force = polar_force(10, 45)
>>> polar_force(10, 3.14, radian_mode=True) >>> math.isclose(force[0], 7.071067811865477)
[-9.999987317275394, 0.01592652916486828] True
>>> math.isclose(force[1], 7.0710678118654755)
True
>>> force = polar_force(10, 3.14, radian_mode=True)
>>> math.isclose(force[0], -9.999987317275396)
True
>>> math.isclose(force[1], 0.01592652916486828)
True
""" """
if radian_mode: if radian_mode:
return [magnitude * cos(angle), magnitude * sin(angle)] return [magnitude * cos(angle), magnitude * sin(angle)]
@ -23,7 +31,7 @@ def polar_force(
def in_static_equilibrium( def in_static_equilibrium(
forces: ndarray, location: ndarray, eps: float = 10 ** -1 forces: NDArray[float64], location: NDArray[float64], eps: float = 10**-1
) -> bool: ) -> bool:
""" """
Check if a system is in equilibrium. Check if a system is in equilibrium.
@ -42,7 +50,7 @@ def in_static_equilibrium(
False False
""" """
# summation of moments is zero # summation of moments is zero
moments: ndarray = cross(location, forces) moments: NDArray[float64] = cross(location, forces)
sum_moments: float = sum(moments) sum_moments: float = sum(moments)
return abs(sum_moments) < eps return abs(sum_moments) < eps
@ -50,10 +58,14 @@ def in_static_equilibrium(
if __name__ == "__main__": if __name__ == "__main__":
# Test to check if it works # Test to check if it works
forces = array( forces = array(
[polar_force(718.4, 180 - 30), polar_force(879.54, 45), polar_force(100, -90)] [
polar_force(718.4, 180 - 30),
polar_force(879.54, 45),
polar_force(100, -90),
]
) )
location = array([[0, 0], [0, 0], [0, 0]]) location: NDArray[float64] = array([[0, 0], [0, 0], [0, 0]])
assert in_static_equilibrium(forces, location) assert in_static_equilibrium(forces, location)

View File

@ -1,5 +1,5 @@
import math import math
from typing import Callable from collections.abc import Callable
def intersection(function: Callable[[float], float], x0: float, x1: float) -> float: def intersection(function: Callable[[float], float], x0: float, x1: float) -> float:
@ -10,7 +10,7 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl
0.9999999999954654 0.9999999999954654
>>> intersection(lambda x: x ** 3 - 1, 5, 5) >>> intersection(lambda x: x ** 3 - 1, 5, 5)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ZeroDivisionError: float division by zero, could not find root ZeroDivisionError: float division by zero, could not find root
>>> intersection(lambda x: x ** 3 - 1, 100, 200) >>> intersection(lambda x: x ** 3 - 1, 100, 200)
1.0000000000003888 1.0000000000003888
@ -24,7 +24,7 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl
0.0 0.0
>>> intersection(math.cos, -math.pi, math.pi) >>> intersection(math.cos, -math.pi, math.pi)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ZeroDivisionError: float division by zero, could not find root ZeroDivisionError: float division by zero, could not find root
""" """
x_n: float = x0 x_n: float = x0
@ -35,7 +35,7 @@ def intersection(function: Callable[[float], float], x0: float, x1: float) -> fl
x_n2: float = x_n1 - ( x_n2: float = x_n1 - (
function(x_n1) / ((function(x_n1) - function(x_n)) / (x_n1 - x_n)) function(x_n1) / ((function(x_n1) - function(x_n)) / (x_n1 - x_n))
) )
if abs(x_n2 - x_n1) < 10 ** -5: if abs(x_n2 - x_n1) < 10**-5:
return x_n2 return x_n2
x_n = x_n1 x_n = x_n1
x_n1 = x_n2 x_n1 = x_n2

View File

@ -0,0 +1,173 @@
"""
Jacobi Iteration Method - https://en.wikipedia.org/wiki/Jacobi_method
"""
from __future__ import annotations
import numpy as np
from numpy import float64
from numpy.typing import NDArray
# Method to find solution of system of linear equations
def jacobi_iteration_method(
coefficient_matrix: NDArray[float64],
constant_matrix: NDArray[float64],
init_val: list[int],
iterations: int,
) -> list[float]:
"""
Jacobi Iteration Method:
An iterative algorithm to determine the solutions of strictly diagonally dominant
system of linear equations
4x1 + x2 + x3 = 2
x1 + 5x2 + 2x3 = -6
x1 + 2x2 + 4x3 = -4
x_init = [0.5, -0.5 , -0.5]
Examples:
>>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]])
>>> constant = np.array([[2], [-6], [-4]])
>>> init_val = [0.5, -0.5, -0.5]
>>> iterations = 3
>>> jacobi_iteration_method(coefficient, constant, init_val, iterations)
[0.909375, -1.14375, -0.7484375]
>>> coefficient = np.array([[4, 1, 1], [1, 5, 2]])
>>> constant = np.array([[2], [-6], [-4]])
>>> init_val = [0.5, -0.5, -0.5]
>>> iterations = 3
>>> jacobi_iteration_method(coefficient, constant, init_val, iterations)
Traceback (most recent call last):
...
ValueError: Coefficient matrix dimensions must be nxn but received 2x3
>>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]])
>>> constant = np.array([[2], [-6]])
>>> init_val = [0.5, -0.5, -0.5]
>>> iterations = 3
>>> jacobi_iteration_method(
... coefficient, constant, init_val, iterations
... ) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
ValueError: Coefficient and constant matrices dimensions must be nxn and nx1 but
received 3x3 and 2x1
>>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]])
>>> constant = np.array([[2], [-6], [-4]])
>>> init_val = [0.5, -0.5]
>>> iterations = 3
>>> jacobi_iteration_method(
... coefficient, constant, init_val, iterations
... ) # doctest: +NORMALIZE_WHITESPACE
Traceback (most recent call last):
...
ValueError: Number of initial values must be equal to number of rows in coefficient
matrix but received 2 and 3
>>> coefficient = np.array([[4, 1, 1], [1, 5, 2], [1, 2, 4]])
>>> constant = np.array([[2], [-6], [-4]])
>>> init_val = [0.5, -0.5, -0.5]
>>> iterations = 0
>>> jacobi_iteration_method(coefficient, constant, init_val, iterations)
Traceback (most recent call last):
...
ValueError: Iterations must be at least 1
"""
rows1, cols1 = coefficient_matrix.shape
rows2, cols2 = constant_matrix.shape
if rows1 != cols1:
msg = f"Coefficient matrix dimensions must be nxn but received {rows1}x{cols1}"
raise ValueError(msg)
if cols2 != 1:
msg = f"Constant matrix must be nx1 but received {rows2}x{cols2}"
raise ValueError(msg)
if rows1 != rows2:
msg = (
"Coefficient and constant matrices dimensions must be nxn and nx1 but "
f"received {rows1}x{cols1} and {rows2}x{cols2}"
)
raise ValueError(msg)
if len(init_val) != rows1:
msg = (
"Number of initial values must be equal to number of rows in coefficient "
f"matrix but received {len(init_val)} and {rows1}"
)
raise ValueError(msg)
if iterations <= 0:
raise ValueError("Iterations must be at least 1")
table: NDArray[float64] = np.concatenate(
(coefficient_matrix, constant_matrix), axis=1
)
rows, cols = table.shape
strictly_diagonally_dominant(table)
# Iterates the whole matrix for given number of times
for _ in range(iterations):
new_val = []
for row in range(rows):
temp = 0
for col in range(cols):
if col == row:
denom = table[row][col]
elif col == cols - 1:
val = table[row][col]
else:
temp += (-1) * table[row][col] * init_val[col]
temp = (temp + val) / denom
new_val.append(temp)
init_val = new_val
return [float(i) for i in new_val]
# Checks if the given matrix is strictly diagonally dominant
def strictly_diagonally_dominant(table: NDArray[float64]) -> bool:
"""
>>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 4, -4]])
>>> strictly_diagonally_dominant(table)
True
>>> table = np.array([[4, 1, 1, 2], [1, 5, 2, -6], [1, 2, 3, -4]])
>>> strictly_diagonally_dominant(table)
Traceback (most recent call last):
...
ValueError: Coefficient matrix is not strictly diagonally dominant
"""
rows, cols = table.shape
is_diagonally_dominant = True
for i in range(0, rows):
total = 0
for j in range(0, cols - 1):
if i == j:
continue
else:
total += table[i][j]
if table[i][i] <= total:
raise ValueError("Coefficient matrix is not strictly diagonally dominant")
return is_diagonally_dominant
# Test Cases
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -1,7 +1,19 @@
"""Lower-Upper (LU) Decomposition. """
Lowerupper (LU) decomposition factors a matrix as a product of a lower
triangular matrix and an upper triangular matrix. A square matrix has an LU
decomposition under the following conditions:
- If the matrix is invertible, then it has an LU decomposition if and only
if all of its leading principal minors are non-zero (see
https://en.wikipedia.org/wiki/Minor_(linear_algebra) for an explanation of
leading principal minors of a matrix).
- If the matrix is singular (i.e., not invertible) and it has a rank of k
(i.e., it has k linearly independent columns), then it has an LU
decomposition if its first k leading principal minors are non-zero.
Reference: This algorithm will simply attempt to perform LU decomposition on any square
- https://en.wikipedia.org/wiki/LU_decomposition matrix and raise an error if no such decomposition exists.
Reference: https://en.wikipedia.org/wiki/LU_decomposition
""" """
from __future__ import annotations from __future__ import annotations
@ -9,50 +21,82 @@ import numpy as np
def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]: def lower_upper_decomposition(table: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""Lower-Upper (LU) Decomposition """
Perform LU decomposition on a given matrix and raises an error if the matrix
Example: isn't square or if no such decomposition exists
>>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]]) >>> matrix = np.array([[2, -2, 1], [0, 1, 2], [5, 3, 1]])
>>> outcome = lower_upper_decomposition(matrix) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
>>> outcome[0] >>> lower_mat
array([[1. , 0. , 0. ], array([[1. , 0. , 0. ],
[0. , 1. , 0. ], [0. , 1. , 0. ],
[2.5, 8. , 1. ]]) [2.5, 8. , 1. ]])
>>> outcome[1] >>> upper_mat
array([[ 2. , -2. , 1. ], array([[ 2. , -2. , 1. ],
[ 0. , 1. , 2. ], [ 0. , 1. , 2. ],
[ 0. , 0. , -17.5]]) [ 0. , 0. , -17.5]])
>>> matrix = np.array([[4, 3], [6, 3]])
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
>>> lower_mat
array([[1. , 0. ],
[1.5, 1. ]])
>>> upper_mat
array([[ 4. , 3. ],
[ 0. , -1.5]])
# Matrix is not square
>>> matrix = np.array([[2, -2, 1], [0, 1, 2]]) >>> matrix = np.array([[2, -2, 1], [0, 1, 2]])
>>> lower_upper_decomposition(matrix) >>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: 'table' has to be of square shaped array but got a 2x3 array: ValueError: 'table' has to be of square shaped array but got a 2x3 array:
[[ 2 -2 1] [[ 2 -2 1]
[ 0 1 2]] [ 0 1 2]]
# Matrix is invertible, but its first leading principal minor is 0
>>> matrix = np.array([[0, 1], [1, 0]])
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
Traceback (most recent call last):
...
ArithmeticError: No LU decomposition exists
# Matrix is singular, but its first leading principal minor is 1
>>> matrix = np.array([[1, 0], [1, 0]])
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
>>> lower_mat
array([[1., 0.],
[1., 1.]])
>>> upper_mat
array([[1., 0.],
[0., 0.]])
# Matrix is singular, but its first leading principal minor is 0
>>> matrix = np.array([[0, 1], [0, 1]])
>>> lower_mat, upper_mat = lower_upper_decomposition(matrix)
Traceback (most recent call last):
...
ArithmeticError: No LU decomposition exists
""" """
# Table that contains our data # Ensure that table is a square array
# Table has to be a square array so we need to check first
rows, columns = np.shape(table) rows, columns = np.shape(table)
if rows != columns: if rows != columns:
raise ValueError( msg = (
f"'table' has to be of square shaped array but got a {rows}x{columns} " "'table' has to be of square shaped array but got a "
+ f"array:\n{table}" f"{rows}x{columns} array:\n{table}"
) )
raise ValueError(msg)
lower = np.zeros((rows, columns)) lower = np.zeros((rows, columns))
upper = np.zeros((rows, columns)) upper = np.zeros((rows, columns))
for i in range(columns): for i in range(columns):
for j in range(i): for j in range(i):
total = 0 total = sum(lower[i][k] * upper[k][j] for k in range(j))
for k in range(j): if upper[j][j] == 0:
total += lower[i][k] * upper[k][j] raise ArithmeticError("No LU decomposition exists")
lower[i][j] = (table[i][j] - total) / upper[j][j] lower[i][j] = (table[i][j] - total) / upper[j][j]
lower[i][i] = 1 lower[i][i] = 1
for j in range(i, columns): for j in range(i, columns):
total = 0 total = sum(lower[i][k] * upper[k][j] for k in range(j))
for k in range(i):
total += lower[i][k] * upper[k][j]
upper[i][j] = table[i][j] - total upper[i][j] = table[i][j] - total
return lower, upper return lower, upper

View File

@ -23,7 +23,7 @@ def ucal(u: float, p: int) -> float:
def main() -> None: def main() -> None:
n = int(input("enter the numbers of values: ")) n = int(input("enter the numbers of values: "))
y: list[list[float]] = [] y: list[list[float]] = []
for i in range(n): for _ in range(n):
y.append([]) y.append([])
for i in range(n): for i in range(n):
for j in range(n): for j in range(n):

View File

@ -1,7 +1,7 @@
"""Newton's Method.""" """Newton's Method."""
# Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method # Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method
from typing import Callable from collections.abc import Callable
RealFunc = Callable[[float], float] # type alias for a real -> real function RealFunc = Callable[[float], float] # type alias for a real -> real function
@ -28,7 +28,7 @@ def newton(
1.5707963267948966 1.5707963267948966
>>> newton(math.cos, lambda x: -math.sin(x), 0) >>> newton(math.cos, lambda x: -math.sin(x), 0)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ZeroDivisionError: Could not find root ZeroDivisionError: Could not find root
""" """
prev_guess = float(starting_int) prev_guess = float(starting_int)
@ -37,17 +37,17 @@ def newton(
next_guess = prev_guess - function(prev_guess) / derivative(prev_guess) next_guess = prev_guess - function(prev_guess) / derivative(prev_guess)
except ZeroDivisionError: except ZeroDivisionError:
raise ZeroDivisionError("Could not find root") from None raise ZeroDivisionError("Could not find root") from None
if abs(prev_guess - next_guess) < 10 ** -5: if abs(prev_guess - next_guess) < 10**-5:
return next_guess return next_guess
prev_guess = next_guess prev_guess = next_guess
def f(x: float) -> float: def f(x: float) -> float:
return (x ** 3) - (2 * x) - 5 return (x**3) - (2 * x) - 5
def f1(x: float) -> float: def f1(x: float) -> float:
return 3 * (x ** 2) - 2 return 3 * (x**2) - 2
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -5,13 +5,13 @@
from __future__ import annotations from __future__ import annotations
from decimal import Decimal from decimal import Decimal
from math import * # noqa: F401, F403 from math import * # noqa: F403
from sympy import diff from sympy import diff
def newton_raphson( def newton_raphson(
func: str, a: float | Decimal, precision: float = 10 ** -10 func: str, a: float | Decimal, precision: float = 10**-10
) -> float: ) -> float:
"""Finds root from the point 'a' onwards by Newton-Raphson method """Finds root from the point 'a' onwards by Newton-Raphson method
>>> newton_raphson("sin(x)", 2) >>> newton_raphson("sin(x)", 2)
@ -25,9 +25,11 @@ def newton_raphson(
""" """
x = a x = a
while True: while True:
x = Decimal(x) - (Decimal(eval(func)) / Decimal(eval(str(diff(func))))) x = Decimal(x) - (
Decimal(eval(func)) / Decimal(eval(str(diff(func)))) # noqa: S307
)
# This number dictates the accuracy of the answer # This number dictates the accuracy of the answer
if abs(eval(func)) < precision: if abs(eval(func)) < precision: # noqa: S307
return float(x) return float(x)

View File

@ -0,0 +1,83 @@
# Implementing Newton Raphson method in Python
# Author: Saksham Gupta
#
# The Newton-Raphson method (also known as Newton's method) is a way to
# quickly find a good approximation for the root of a functreal-valued ion
# The method can also be extended to complex functions
#
# Newton's Method - https://en.wikipedia.org/wiki/Newton's_method
from sympy import diff, lambdify, symbols
from sympy.functions import * # noqa: F403
def newton_raphson(
function: str,
starting_point: complex,
variable: str = "x",
precision: float = 10**-10,
multiplicity: int = 1,
) -> complex:
"""Finds root from the 'starting_point' onwards by Newton-Raphson method
Refer to https://docs.sympy.org/latest/modules/functions/index.html
for usable mathematical functions
>>> newton_raphson("sin(x)", 2)
3.141592653589793
>>> newton_raphson("x**4 -5", 0.4 + 5j)
(-7.52316384526264e-37+1.4953487812212207j)
>>> newton_raphson('log(y) - 1', 2, variable='y')
2.7182818284590455
>>> newton_raphson('exp(x) - 1', 10, precision=0.005)
1.2186556186174883e-10
>>> newton_raphson('cos(x)', 0)
Traceback (most recent call last):
...
ZeroDivisionError: Could not find root
"""
x = symbols(variable)
func = lambdify(x, function)
diff_function = lambdify(x, diff(function, x))
prev_guess = starting_point
while True:
if diff_function(prev_guess) != 0:
next_guess = prev_guess - multiplicity * func(prev_guess) / diff_function(
prev_guess
)
else:
raise ZeroDivisionError("Could not find root") from None
# Precision is checked by comparing the difference of consecutive guesses
if abs(next_guess - prev_guess) < precision:
return next_guess
prev_guess = next_guess
# Let's Execute
if __name__ == "__main__":
# Find root of trigonometric function
# Find value of pi
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
# Find root of polynomial
# Find fourth Root of 5
print(f"The root of x**4 - 5 = 0 is {newton_raphson('x**4 -5', 0.4 +5j)}")
# Find value of e
print(
"The root of log(y) - 1 = 0 is ",
f"{newton_raphson('log(y) - 1', 2, variable='y')}",
)
# Exponential Roots
print(
"The root of exp(x) - 1 = 0 is",
f"{newton_raphson('exp(x) - 1', 10, precision=0.005)}",
)
# Find root of cos(x)
print(f"The root of cos(x) = 0 is {newton_raphson('cos(x)', 0)}")

View File

@ -20,7 +20,7 @@ def secant_method(lower_bound: float, upper_bound: float, repeats: int) -> float
""" """
x0 = lower_bound x0 = lower_bound
x1 = upper_bound x1 = upper_bound
for i in range(0, repeats): for _ in range(0, repeats):
x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0)) x0, x1 = x1, x1 - (f(x1) * (x1 - x0)) / (f(x1) - f(x0))
return x1 return x1

9
audio_filters/README.md Normal file
View File

@ -0,0 +1,9 @@
# Audio Filter
Audio filters work on the frequency of an audio signal to attenuate unwanted frequency and amplify wanted ones.
They are used within anything related to sound, whether it is radio communication or a hi-fi system.
* <https://www.masteringbox.com/filter-types/>
* <http://ethanwiner.com/filters.html>
* <https://en.wikipedia.org/wiki/Audio_filter>
* <https://en.wikipedia.org/wiki/Electronic_filter>

View File

View File

@ -0,0 +1,226 @@
from math import cos, sin, sqrt, tau
from audio_filters.iir_filter import IIRFilter
"""
Create 2nd-order IIR filters with Butterworth design.
Code based on https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html
Alternatively you can use scipy.signal.butter, which should yield the same results.
"""
def make_lowpass(
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008
) -> IIRFilter:
"""
Creates a low-pass filter
>>> filter = make_lowpass(1000, 48000)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.004277569313094809,
0.008555138626189618, 0.004277569313094809]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
b0 = (1 - _cos) / 2
b1 = 1 - _cos
a0 = 1 + alpha
a1 = -2 * _cos
a2 = 1 - alpha
filt = IIRFilter(2)
filt.set_coefficients([a0, a1, a2], [b0, b1, b0])
return filt
def make_highpass(
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008
) -> IIRFilter:
"""
Creates a high-pass filter
>>> filter = make_highpass(1000, 48000)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.9957224306869052,
-1.9914448613738105, 0.9957224306869052]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
b0 = (1 + _cos) / 2
b1 = -1 - _cos
a0 = 1 + alpha
a1 = -2 * _cos
a2 = 1 - alpha
filt = IIRFilter(2)
filt.set_coefficients([a0, a1, a2], [b0, b1, b0])
return filt
def make_bandpass(
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008
) -> IIRFilter:
"""
Creates a band-pass filter
>>> filter = make_bandpass(1000, 48000)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.06526309611002579,
0, -0.06526309611002579]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
b0 = _sin / 2
b1 = 0
b2 = -b0
a0 = 1 + alpha
a1 = -2 * _cos
a2 = 1 - alpha
filt = IIRFilter(2)
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
return filt
def make_allpass(
frequency: int, samplerate: int, q_factor: float = 1 / sqrt(2) # noqa: B008
) -> IIRFilter:
"""
Creates an all-pass filter
>>> filter = make_allpass(1000, 48000)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[1.0922959556412573, -1.9828897227476208, 0.9077040443587427, 0.9077040443587427,
-1.9828897227476208, 1.0922959556412573]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
b0 = 1 - alpha
b1 = -2 * _cos
b2 = 1 + alpha
filt = IIRFilter(2)
filt.set_coefficients([b2, b1, b0], [b0, b1, b2])
return filt
def make_peak(
frequency: int,
samplerate: int,
gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008
) -> IIRFilter:
"""
Creates a peak filter
>>> filter = make_peak(1000, 48000, 6)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[1.0653405327119334, -1.9828897227476208, 0.9346594672880666, 1.1303715025601122,
-1.9828897227476208, 0.8696284974398878]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
big_a = 10 ** (gain_db / 40)
b0 = 1 + alpha * big_a
b1 = -2 * _cos
b2 = 1 - alpha * big_a
a0 = 1 + alpha / big_a
a1 = -2 * _cos
a2 = 1 - alpha / big_a
filt = IIRFilter(2)
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
return filt
def make_lowshelf(
frequency: int,
samplerate: int,
gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008
) -> IIRFilter:
"""
Creates a low-shelf filter
>>> filter = make_lowshelf(1000, 48000, 6)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[3.0409336710888786, -5.608870992220748, 2.602157875636628, 3.139954022810743,
-5.591841778072785, 2.5201667380627257]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
big_a = 10 ** (gain_db / 40)
pmc = (big_a + 1) - (big_a - 1) * _cos
ppmc = (big_a + 1) + (big_a - 1) * _cos
mpc = (big_a - 1) - (big_a + 1) * _cos
pmpc = (big_a - 1) + (big_a + 1) * _cos
aa2 = 2 * sqrt(big_a) * alpha
b0 = big_a * (pmc + aa2)
b1 = 2 * big_a * mpc
b2 = big_a * (pmc - aa2)
a0 = ppmc + aa2
a1 = -2 * pmpc
a2 = ppmc - aa2
filt = IIRFilter(2)
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
return filt
def make_highshelf(
frequency: int,
samplerate: int,
gain_db: float,
q_factor: float = 1 / sqrt(2), # noqa: B008
) -> IIRFilter:
"""
Creates a high-shelf filter
>>> filter = make_highshelf(1000, 48000, 6)
>>> filter.a_coeffs + filter.b_coeffs # doctest: +NORMALIZE_WHITESPACE
[2.2229172136088806, -3.9587208137297303, 1.7841414181566304, 4.295432981120543,
-7.922740859457287, 3.6756456963725253]
"""
w0 = tau * frequency / samplerate
_sin = sin(w0)
_cos = cos(w0)
alpha = _sin / (2 * q_factor)
big_a = 10 ** (gain_db / 40)
pmc = (big_a + 1) - (big_a - 1) * _cos
ppmc = (big_a + 1) + (big_a - 1) * _cos
mpc = (big_a - 1) - (big_a + 1) * _cos
pmpc = (big_a - 1) + (big_a + 1) * _cos
aa2 = 2 * sqrt(big_a) * alpha
b0 = big_a * (ppmc + aa2)
b1 = -2 * big_a * pmpc
b2 = big_a * (ppmc - aa2)
a0 = pmc + aa2
a1 = 2 * mpc
a2 = pmc - aa2
filt = IIRFilter(2)
filt.set_coefficients([a0, a1, a2], [b0, b1, b2])
return filt

View File

@ -0,0 +1,61 @@
from json import loads
from pathlib import Path
import numpy as np
from yulewalker import yulewalk
from audio_filters.butterworth_filter import make_highpass
from audio_filters.iir_filter import IIRFilter
data = loads((Path(__file__).resolve().parent / "loudness_curve.json").read_text())
class EqualLoudnessFilter:
r"""
An equal-loudness filter which compensates for the human ear's non-linear response
to sound.
This filter corrects this by cascading a yulewalk filter and a butterworth filter.
Designed for use with samplerate of 44.1kHz and above. If you're using a lower
samplerate, use with caution.
Code based on matlab implementation at https://bit.ly/3eqh2HU
(url shortened for ruff)
Target curve: https://i.imgur.com/3g2VfaM.png
Yulewalk response: https://i.imgur.com/J9LnJ4C.png
Butterworth and overall response: https://i.imgur.com/3g2VfaM.png
Images and original matlab implementation by David Robinson, 2001
"""
def __init__(self, samplerate: int = 44100) -> None:
self.yulewalk_filter = IIRFilter(10)
self.butterworth_filter = make_highpass(150, samplerate)
# pad the data to nyquist
curve_freqs = np.array(data["frequencies"] + [max(20000.0, samplerate / 2)])
curve_gains = np.array(data["gains"] + [140])
# Convert to angular frequency
freqs_normalized = curve_freqs / samplerate * 2
# Invert the curve and normalize to 0dB
gains_normalized = np.power(10, (np.min(curve_gains) - curve_gains) / 20)
# Scipy's `yulewalk` function is a stub, so we're using the
# `yulewalker` library instead.
# This function computes the coefficients using a least-squares
# fit to the specified curve.
ya, yb = yulewalk(10, freqs_normalized, gains_normalized)
self.yulewalk_filter.set_coefficients(ya, yb)
def process(self, sample: float) -> float:
"""
Process a single sample through both filters
>>> filt = EqualLoudnessFilter()
>>> filt.process(0.0)
0.0
"""
tmp = self.yulewalk_filter.process(sample)
return self.butterworth_filter.process(tmp)

View File

@ -0,0 +1,94 @@
from __future__ import annotations
class IIRFilter:
r"""
N-Order IIR filter
Assumes working with float samples normalized on [-1, 1]
---
Implementation details:
Based on the 2nd-order function from
https://en.wikipedia.org/wiki/Digital_biquad_filter,
this generalized N-order function was made.
Using the following transfer function
H(z)=\frac{b_{0}+b_{1}z^{-1}+b_{2}z^{-2}+...+b_{k}z^{-k}}{a_{0}+a_{1}z^{-1}+a_{2}z^{-2}+...+a_{k}z^{-k}}
we can rewrite this to
y[n]={\frac{1}{a_{0}}}\left(\left(b_{0}x[n]+b_{1}x[n-1]+b_{2}x[n-2]+...+b_{k}x[n-k]\right)-\left(a_{1}y[n-1]+a_{2}y[n-2]+...+a_{k}y[n-k]\right)\right)
"""
def __init__(self, order: int) -> None:
self.order = order
# a_{0} ... a_{k}
self.a_coeffs = [1.0] + [0.0] * order
# b_{0} ... b_{k}
self.b_coeffs = [1.0] + [0.0] * order
# x[n-1] ... x[n-k]
self.input_history = [0.0] * self.order
# y[n-1] ... y[n-k]
self.output_history = [0.0] * self.order
def set_coefficients(self, a_coeffs: list[float], b_coeffs: list[float]) -> None:
"""
Set the coefficients for the IIR filter. These should both be of size order + 1.
a_0 may be left out, and it will use 1.0 as default value.
This method works well with scipy's filter design functions
>>> # Make a 2nd-order 1000Hz butterworth lowpass filter
>>> import scipy.signal
>>> b_coeffs, a_coeffs = scipy.signal.butter(2, 1000,
... btype='lowpass',
... fs=48000)
>>> filt = IIRFilter(2)
>>> filt.set_coefficients(a_coeffs, b_coeffs)
"""
if len(a_coeffs) < self.order:
a_coeffs = [1.0, *a_coeffs]
if len(a_coeffs) != self.order + 1:
msg = (
f"Expected a_coeffs to have {self.order + 1} elements "
f"for {self.order}-order filter, got {len(a_coeffs)}"
)
raise ValueError(msg)
if len(b_coeffs) != self.order + 1:
msg = (
f"Expected b_coeffs to have {self.order + 1} elements "
f"for {self.order}-order filter, got {len(a_coeffs)}"
)
raise ValueError(msg)
self.a_coeffs = a_coeffs
self.b_coeffs = b_coeffs
def process(self, sample: float) -> float:
"""
Calculate y[n]
>>> filt = IIRFilter(2)
>>> filt.process(0)
0.0
"""
result = 0.0
# Start at index 1 and do index 0 at the end.
for i in range(1, self.order + 1):
result += (
self.b_coeffs[i] * self.input_history[i - 1]
- self.a_coeffs[i] * self.output_history[i - 1]
)
result = (result + self.b_coeffs[0] * sample) / self.a_coeffs[0]
self.input_history[1:] = self.input_history[:-1]
self.output_history[1:] = self.output_history[:-1]
self.input_history[0] = sample
self.output_history[0] = result
return result

View File

@ -0,0 +1,76 @@
{
"_comment": "The following is a representative average of the Equal Loudness Contours as measured by Robinson and Dadson, 1956",
"_doi": "10.1088/0508-3443/7/5/302",
"frequencies": [
0,
20,
30,
40,
50,
60,
70,
80,
90,
100,
200,
300,
400,
500,
600,
700,
800,
900,
1000,
1500,
2000,
2500,
3000,
3700,
4000,
5000,
6000,
7000,
8000,
9000,
10000,
12000,
15000,
20000
],
"gains": [
120,
113,
103,
97,
93,
91,
89,
87,
86,
85,
78,
76,
76,
76,
76,
77,
78,
79.5,
80,
79,
77,
74,
71.5,
70,
70.5,
74,
79,
84,
86,
86,
85,
95,
110,
125
]
}

View File

@ -0,0 +1,94 @@
from __future__ import annotations
from math import pi
from typing import Protocol
import matplotlib.pyplot as plt
import numpy as np
class FilterType(Protocol):
def process(self, sample: float) -> float:
"""
Calculate y[n]
>>> issubclass(FilterType, Protocol)
True
"""
return 0.0
def get_bounds(
fft_results: np.ndarray, samplerate: int
) -> tuple[int | float, int | float]:
"""
Get bounds for printing fft results
>>> import numpy
>>> array = numpy.linspace(-20.0, 20.0, 1000)
>>> get_bounds(array, 1000)
(-20, 20)
"""
lowest = min([-20, np.min(fft_results[1 : samplerate // 2 - 1])])
highest = max([20, np.max(fft_results[1 : samplerate // 2 - 1])])
return lowest, highest
def show_frequency_response(filter_type: FilterType, samplerate: int) -> None:
"""
Show frequency response of a filter
>>> from audio_filters.iir_filter import IIRFilter
>>> filt = IIRFilter(4)
>>> show_frequency_response(filt, 48000)
"""
size = 512
inputs = [1] + [0] * (size - 1)
outputs = [filter_type.process(item) for item in inputs]
filler = [0] * (samplerate - size) # zero-padding
outputs += filler
fft_out = np.abs(np.fft.fft(outputs))
fft_db = 20 * np.log10(fft_out)
# Frequencies on log scale from 24 to nyquist frequency
plt.xlim(24, samplerate / 2 - 1)
plt.xlabel("Frequency (Hz)")
plt.xscale("log")
# Display within reasonable bounds
bounds = get_bounds(fft_db, samplerate)
plt.ylim(max([-80, bounds[0]]), min([80, bounds[1]]))
plt.ylabel("Gain (dB)")
plt.plot(fft_db)
plt.show()
def show_phase_response(filter_type: FilterType, samplerate: int) -> None:
"""
Show phase response of a filter
>>> from audio_filters.iir_filter import IIRFilter
>>> filt = IIRFilter(4)
>>> show_phase_response(filt, 48000)
"""
size = 512
inputs = [1] + [0] * (size - 1)
outputs = [filter_type.process(item) for item in inputs]
filler = [0] * (samplerate - size) # zero-padding
outputs += filler
fft_out = np.angle(np.fft.fft(outputs))
# Frequencies on log scale from 24 to nyquist frequency
plt.xlim(24, samplerate / 2 - 1)
plt.xlabel("Frequency (Hz)")
plt.xscale("log")
plt.ylim(-2 * pi, 2 * pi)
plt.ylabel("Phase shift (Radians)")
plt.plot(np.unwrap(fft_out, -2 * pi))
plt.show()

8
backtracking/README.md Normal file
View File

@ -0,0 +1,8 @@
# Backtracking
Backtracking is a way to speed up the search process by removing candidates when they can't be the solution of a problem.
* <https://en.wikipedia.org/wiki/Backtracking>
* <https://en.wikipedia.org/wiki/Decision_tree_pruning>
* <https://medium.com/@priyankmistry1999/backtracking-sudoku-6e4439e4825c>
* <https://www.geeksforgeeks.org/sudoku-backtracking-7/>

View File

@ -0,0 +1,66 @@
"""
In the Combination Sum problem, we are given a list consisting of distinct integers.
We need to find all the combinations whose sum equals to target given.
We can use an element more than one.
Time complexity(Average Case): O(n!)
Constraints:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
All elements of candidates are distinct.
1 <= target <= 40
"""
def backtrack(
candidates: list, path: list, answer: list, target: int, previous_index: int
) -> None:
"""
A recursive function that searches for possible combinations. Backtracks in case
of a bigger current combination value than the target value.
Parameters
----------
previous_index: Last index from the previous search
target: The value we need to obtain by summing our integers in the path list.
answer: A list of possible combinations
path: Current combination
candidates: A list of integers we can use.
"""
if target == 0:
answer.append(path.copy())
else:
for index in range(previous_index, len(candidates)):
if target >= candidates[index]:
path.append(candidates[index])
backtrack(candidates, path, answer, target - candidates[index], index)
path.pop(len(path) - 1)
def combination_sum(candidates: list, target: int) -> list:
"""
>>> combination_sum([2, 3, 5], 8)
[[2, 2, 2, 2], [2, 3, 3], [3, 5]]
>>> combination_sum([2, 3, 6, 7], 7)
[[2, 2, 3], [7]]
>>> combination_sum([-8, 2.3, 0], 1)
Traceback (most recent call last):
...
RecursionError: maximum recursion depth exceeded in comparison
"""
path = [] # type: list[int]
answer = [] # type: list[int]
backtrack(candidates, path, answer, target, 0)
return answer
def main() -> None:
print(combination_sum([-8, 2.3, 0], 1))
if __name__ == "__main__":
import doctest
doctest.testmod()
main()

View File

@ -71,7 +71,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int)
>>> curr_ind = 1 >>> curr_ind = 1
>>> util_hamilton_cycle(graph, path, curr_ind) >>> util_hamilton_cycle(graph, path, curr_ind)
True True
>>> print(path) >>> path
[0, 1, 2, 4, 3, 0] [0, 1, 2, 4, 3, 0]
Case 2: Use exact graph as in previous case, but in the properties taken from Case 2: Use exact graph as in previous case, but in the properties taken from
@ -85,7 +85,7 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int)
>>> curr_ind = 3 >>> curr_ind = 3
>>> util_hamilton_cycle(graph, path, curr_ind) >>> util_hamilton_cycle(graph, path, curr_ind)
True True
>>> print(path) >>> path
[0, 1, 2, 4, 3, 0] [0, 1, 2, 4, 3, 0]
""" """
@ -95,10 +95,10 @@ def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int)
return graph[path[curr_ind - 1]][path[0]] == 1 return graph[path[curr_ind - 1]][path[0]] == 1
# Recursive Step # Recursive Step
for next in range(0, len(graph)): for next_ver in range(0, len(graph)):
if valid_connection(graph, next, curr_ind, path): if valid_connection(graph, next_ver, curr_ind, path):
# Insert current vertex into path as next transition # Insert current vertex into path as next transition
path[curr_ind] = next path[curr_ind] = next_ver
# Validate created path # Validate created path
if util_hamilton_cycle(graph, path, curr_ind + 1): if util_hamilton_cycle(graph, path, curr_ind + 1):
return True return True

View File

@ -78,7 +78,7 @@ def open_knight_tour(n: int) -> list[list[int]]:
>>> open_knight_tour(2) >>> open_knight_tour(2)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: Open Kight Tour cannot be performed on a board of size 2 ValueError: Open Kight Tour cannot be performed on a board of size 2
""" """
@ -91,7 +91,8 @@ def open_knight_tour(n: int) -> list[list[int]]:
return board return board
board[i][j] = 0 board[i][j] = 0
raise ValueError(f"Open Kight Tour cannot be performed on a board of size {n}") msg = f"Open Kight Tour cannot be performed on a board of size {n}"
raise ValueError(msg)
if __name__ == "__main__": if __name__ == "__main__":

69
backtracking/minmax.py Normal file
View File

@ -0,0 +1,69 @@
"""
Minimax helps to achieve maximum score in a game by checking all possible moves.
"""
from __future__ import annotations
import math
def minimax(
depth: int, node_index: int, is_max: bool, scores: list[int], height: float
) -> int:
"""
depth is current depth in game tree.
node_index is index of current node in scores[].
scores[] contains the leaves of game tree.
height is maximum height of game tree.
>>> scores = [90, 23, 6, 33, 21, 65, 123, 34423]
>>> height = math.log(len(scores), 2)
>>> minimax(0, 0, True, scores, height)
65
>>> minimax(-1, 0, True, scores, height)
Traceback (most recent call last):
...
ValueError: Depth cannot be less than 0
>>> minimax(0, 0, True, [], 2)
Traceback (most recent call last):
...
ValueError: Scores cannot be empty
>>> scores = [3, 5, 2, 9, 12, 5, 23, 23]
>>> height = math.log(len(scores), 2)
>>> minimax(0, 0, True, scores, height)
12
"""
if depth < 0:
raise ValueError("Depth cannot be less than 0")
if not scores:
raise ValueError("Scores cannot be empty")
if depth == height:
return scores[node_index]
return (
max(
minimax(depth + 1, node_index * 2, False, scores, height),
minimax(depth + 1, node_index * 2 + 1, False, scores, height),
)
if is_max
else min(
minimax(depth + 1, node_index * 2, True, scores, height),
minimax(depth + 1, node_index * 2 + 1, True, scores, height),
)
)
def main() -> None:
scores = [90, 23, 6, 33, 21, 65, 123, 34423]
height = math.log(len(scores), 2)
print(f"Optimal value : {minimax(0, 0, True, scores, height)}")
if __name__ == "__main__":
import doctest
doctest.testmod()
main()

View File

@ -12,7 +12,7 @@ from __future__ import annotations
solution = [] solution = []
def isSafe(board: list[list[int]], row: int, column: int) -> bool: def is_safe(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 This function returns a boolean value True if it is safe to place a queen there
considering the current state of the board. considering the current state of the board.
@ -63,7 +63,7 @@ def solve(board: list[list[int]], row: int) -> bool:
If all the combinations for that particular branch are successful the board is If all the combinations for that particular branch are successful the board is
reinitialized for the next possible combination. reinitialized for the next possible combination.
""" """
if isSafe(board, row, i): if is_safe(board, row, i):
board[row][i] = 1 board[row][i] = 1
solve(board, row + 1) solve(board, row + 1)
board[row][i] = 0 board[row][i] = 0

View File

@ -107,7 +107,6 @@ def depth_first_search(
# 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): for col in range(n):
# We apply that we learned previously. First we check that in the current board # 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 # (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 # that there are a collision in vertical. Then we apply the two formulas we
@ -130,9 +129,9 @@ def depth_first_search(
# 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( depth_first_search(
possible_board + [col], [*possible_board, col],
diagonal_right_collisions + [row - col], [*diagonal_right_collisions, row - col],
diagonal_left_collisions + [row + col], [*diagonal_left_collisions, row + col],
boards, boards,
n, n,
) )

93
backtracking/power_sum.py Normal file
View File

@ -0,0 +1,93 @@
"""
Problem source: https://www.hackerrank.com/challenges/the-power-sum/problem
Find the number of ways that a given integer X, can be expressed as the sum
of the Nth powers of unique, natural numbers. For example, if X=13 and N=2.
We have to find all combinations of unique squares adding up to 13.
The only solution is 2^2+3^2. Constraints: 1<=X<=1000, 2<=N<=10.
"""
from math import pow
def backtrack(
needed_sum: int,
power: int,
current_number: int,
current_sum: int,
solutions_count: int,
) -> tuple[int, int]:
"""
>>> backtrack(13, 2, 1, 0, 0)
(0, 1)
>>> backtrack(100, 2, 1, 0, 0)
(0, 3)
>>> backtrack(100, 3, 1, 0, 0)
(0, 1)
>>> backtrack(800, 2, 1, 0, 0)
(0, 561)
>>> backtrack(1000, 10, 1, 0, 0)
(0, 0)
>>> backtrack(400, 2, 1, 0, 0)
(0, 55)
>>> backtrack(50, 1, 1, 0, 0)
(0, 3658)
"""
if current_sum == needed_sum:
# If the sum of the powers is equal to needed_sum, then we have a solution.
solutions_count += 1
return current_sum, solutions_count
i_to_n = int(pow(current_number, power))
if current_sum + i_to_n <= needed_sum:
# If the sum of the powers is less than needed_sum, then continue adding powers.
current_sum += i_to_n
current_sum, solutions_count = backtrack(
needed_sum, power, current_number + 1, current_sum, solutions_count
)
current_sum -= i_to_n
if i_to_n < needed_sum:
# If the power of i is less than needed_sum, then try with the next power.
current_sum, solutions_count = backtrack(
needed_sum, power, current_number + 1, current_sum, solutions_count
)
return current_sum, solutions_count
def solve(needed_sum: int, power: int) -> int:
"""
>>> solve(13, 2)
1
>>> solve(100, 2)
3
>>> solve(100, 3)
1
>>> solve(800, 2)
561
>>> solve(1000, 10)
0
>>> solve(400, 2)
55
>>> solve(50, 1)
Traceback (most recent call last):
...
ValueError: Invalid input
needed_sum must be between 1 and 1000, power between 2 and 10.
>>> solve(-10, 5)
Traceback (most recent call last):
...
ValueError: Invalid input
needed_sum must be between 1 and 1000, power between 2 and 10.
"""
if not (1 <= needed_sum <= 1000 and 2 <= power <= 10):
raise ValueError(
"Invalid input\n"
"needed_sum must be between 1 and 1000, power between 2 and 10."
)
return backtrack(needed_sum, power, 1, 0, 0)[1] # Return the solutions_count
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -88,12 +88,12 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]])
solutions[i][j] = 1 solutions[i][j] = 1
return True return True
lower_flag = (not (i < 0)) and (not (j < 0)) # Check lower bounds lower_flag = (not i < 0) and (not j < 0) # Check lower bounds
upper_flag = (i < size) and (j < size) # Check upper bounds upper_flag = (i < size) and (j < size) # Check upper bounds
if lower_flag and upper_flag: if lower_flag and upper_flag:
# check for already visited and block points. # check for already visited and block points.
block_flag = (not (solutions[i][j])) and (not (maze[i][j])) block_flag = (not solutions[i][j]) and (not maze[i][j])
if block_flag: if block_flag:
# check visited # check visited
solutions[i][j] = 1 solutions[i][j] = 1

View File

@ -39,14 +39,14 @@ def create_state_space_tree(
if sum(path) == max_sum: if sum(path) == max_sum:
result.append(path) result.append(path)
return return
for num_index in range(num_index, len(nums)): for index in range(num_index, len(nums)):
create_state_space_tree( create_state_space_tree(
nums, nums,
max_sum, max_sum,
num_index + 1, index + 1,
path + [nums[num_index]], [*path, nums[index]],
result, result,
remaining_nums_sum - nums[num_index], remaining_nums_sum - nums[index],
) )

168
backtracking/word_search.py Normal file
View File

@ -0,0 +1,168 @@
"""
Author : Alexander Pantyukhin
Date : November 24, 2022
Task:
Given an m x n grid of characters board and a string word,
return true if word exists in the grid.
The word can be constructed from letters of sequentially adjacent cells,
where adjacent cells are horizontally or vertically neighboring.
The same letter cell may not be used more than once.
Example:
Matrix:
---------
|A|B|C|E|
|S|F|C|S|
|A|D|E|E|
---------
Word:
"ABCCED"
Result:
True
Implementation notes: Use backtracking approach.
At each point, check all neighbors to try to find the next letter of the word.
leetcode: https://leetcode.com/problems/word-search/
"""
def get_point_key(len_board: int, len_board_column: int, row: int, column: int) -> int:
"""
Returns the hash key of matrix indexes.
>>> get_point_key(10, 20, 1, 0)
200
"""
return len_board * len_board_column * row + column
def exits_word(
board: list[list[str]],
word: str,
row: int,
column: int,
word_index: int,
visited_points_set: set[int],
) -> bool:
"""
Return True if it's possible to search the word suffix
starting from the word_index.
>>> exits_word([["A"]], "B", 0, 0, 0, set())
False
"""
if board[row][column] != word[word_index]:
return False
if word_index == len(word) - 1:
return True
traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)]
len_board = len(board)
len_board_column = len(board[0])
for direction in traverts_directions:
next_i = row + direction[0]
next_j = column + direction[1]
if not (0 <= next_i < len_board and 0 <= next_j < len_board_column):
continue
key = get_point_key(len_board, len_board_column, next_i, next_j)
if key in visited_points_set:
continue
visited_points_set.add(key)
if exits_word(board, word, next_i, next_j, word_index + 1, visited_points_set):
return True
visited_points_set.remove(key)
return False
def word_exists(board: list[list[str]], word: str) -> bool:
"""
>>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCCED")
True
>>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "SEE")
True
>>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCB")
False
>>> word_exists([["A"]], "A")
True
>>> word_exists([["A","A","A","A","A","A"],
... ["A","A","A","A","A","A"],
... ["A","A","A","A","A","A"],
... ["A","A","A","A","A","A"],
... ["A","A","A","A","A","B"],
... ["A","A","A","A","B","A"]],
... "AAAAAAAAAAAAABB")
False
>>> word_exists([["A"]], 123)
Traceback (most recent call last):
...
ValueError: The word parameter should be a string of length greater than 0.
>>> word_exists([["A"]], "")
Traceback (most recent call last):
...
ValueError: The word parameter should be a string of length greater than 0.
>>> word_exists([[]], "AB")
Traceback (most recent call last):
...
ValueError: The board should be a non empty matrix of single chars strings.
>>> word_exists([], "AB")
Traceback (most recent call last):
...
ValueError: The board should be a non empty matrix of single chars strings.
>>> word_exists([["A"], [21]], "AB")
Traceback (most recent call last):
...
ValueError: The board should be a non empty matrix of single chars strings.
"""
# Validate board
board_error_message = (
"The board should be a non empty matrix of single chars strings."
)
len_board = len(board)
if not isinstance(board, list) or len(board) == 0:
raise ValueError(board_error_message)
for row in board:
if not isinstance(row, list) or len(row) == 0:
raise ValueError(board_error_message)
for item in row:
if not isinstance(item, str) or len(item) != 1:
raise ValueError(board_error_message)
# Validate word
if not isinstance(word, str) or len(word) == 0:
raise ValueError(
"The word parameter should be a string of length greater than 0."
)
len_board_column = len(board[0])
for i in range(len_board):
for j in range(len_board_column):
if exits_word(
board, word, i, j, 0, {get_point_key(len_board, len_board_column, i, j)}
):
return True
return False
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -1,7 +1,11 @@
https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations # Bit manipulation
https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations
https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types
https://wiki.python.org/moin/BitManipulation Bit manipulation is the act of manipulating bits to detect errors (hamming code), encrypts and decrypts messages (more on that in the 'ciphers' folder) or just do anything at the lowest level of your computer.
https://wiki.python.org/moin/BitwiseOperators
https://www.tutorialspoint.com/python3/bitwise_operators_example.htm * <https://en.wikipedia.org/wiki/Bit_manipulation>
* <https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations>
* <https://docs.python.org/3/reference/expressions.html#unary-arithmetic-and-bitwise-operations>
* <https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types>
* <https://wiki.python.org/moin/BitManipulation>
* <https://wiki.python.org/moin/BitwiseOperators>
* <https://www.tutorialspoint.com/python3/bitwise_operators_example.htm>

View File

@ -22,7 +22,7 @@ def binary_and(a: int, b: int) -> str:
>>> binary_and(0, -1) >>> binary_and(0, -1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: the value of both input must be positive ValueError: the value of both inputs must be positive
>>> binary_and(0, 1.1) >>> binary_and(0, 1.1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -33,7 +33,7 @@ def binary_and(a: int, b: int) -> str:
TypeError: '<' not supported between instances of 'str' and 'int' TypeError: '<' not supported between instances of 'str' and 'int'
""" """
if a < 0 or b < 0: if a < 0 or b < 0:
raise ValueError("the value of both input must be positive") raise ValueError("the value of both inputs must be positive")
a_binary = str(bin(a))[2:] # remove the leading "0b" a_binary = str(bin(a))[2:] # remove the leading "0b"
b_binary = str(bin(b))[2:] # remove the leading "0b" b_binary = str(bin(b))[2:] # remove the leading "0b"

View File

@ -21,7 +21,7 @@ def binary_or(a: int, b: int) -> str:
>>> binary_or(0, -1) >>> binary_or(0, -1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: the value of both input must be positive ValueError: the value of both inputs must be positive
>>> binary_or(0, 1.1) >>> binary_or(0, 1.1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -32,7 +32,7 @@ def binary_or(a: int, b: int) -> str:
TypeError: '<' not supported between instances of 'str' and 'int' TypeError: '<' not supported between instances of 'str' and 'int'
""" """
if a < 0 or b < 0: if a < 0 or b < 0:
raise ValueError("the value of both input must be positive") raise ValueError("the value of both inputs must be positive")
a_binary = str(bin(a))[2:] # remove the leading "0b" a_binary = str(bin(a))[2:] # remove the leading "0b"
b_binary = str(bin(b))[2:] b_binary = str(bin(b))[2:]
max_len = max(len(a_binary), len(b_binary)) max_len = max(len(a_binary), len(b_binary))

View File

@ -22,7 +22,7 @@ def binary_xor(a: int, b: int) -> str:
>>> binary_xor(0, -1) >>> binary_xor(0, -1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: the value of both input must be positive ValueError: the value of both inputs must be positive
>>> binary_xor(0, 1.1) >>> binary_xor(0, 1.1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
@ -33,7 +33,7 @@ def binary_xor(a: int, b: int) -> str:
TypeError: '<' not supported between instances of 'str' and 'int' TypeError: '<' not supported between instances of 'str' and 'int'
""" """
if a < 0 or b < 0: if a < 0 or b < 0:
raise ValueError("the value of both input must be positive") raise ValueError("the value of both inputs must be positive")
a_binary = str(bin(a))[2:] # remove the leading "0b" a_binary = str(bin(a))[2:] # remove the leading "0b"
b_binary = str(bin(b))[2:] # remove the leading "0b" b_binary = str(bin(b))[2:] # remove the leading "0b"

View File

@ -0,0 +1,46 @@
def get_1s_count(number: int) -> int:
"""
Count the number of set bits in a 32 bit integer using Brian Kernighan's way.
Ref - https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
>>> get_1s_count(25)
3
>>> get_1s_count(37)
3
>>> get_1s_count(21)
3
>>> get_1s_count(58)
4
>>> get_1s_count(0)
0
>>> get_1s_count(256)
1
>>> get_1s_count(-1)
Traceback (most recent call last):
...
ValueError: Input must be a non-negative integer
>>> get_1s_count(0.8)
Traceback (most recent call last):
...
ValueError: Input must be a non-negative integer
>>> get_1s_count("25")
Traceback (most recent call last):
...
ValueError: Input must be a non-negative integer
"""
if not isinstance(number, int) or number < 0:
raise ValueError("Input must be a non-negative integer")
count = 0
while number:
# This way we arrive at next set bit (next 1) instead of looping
# through each bit and checking for 1s hence the
# loop won't run 32 times it will only run the number of `1` times
number &= number - 1
count += 1
return count
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -1,34 +1,91 @@
def get_set_bits_count(number: int) -> int: from timeit import timeit
def get_set_bits_count_using_brian_kernighans_algorithm(number: int) -> int:
""" """
Count the number of set bits in a 32 bit integer Count the number of set bits in a 32 bit integer
>>> get_set_bits_count(25) >>> get_set_bits_count_using_brian_kernighans_algorithm(25)
3 3
>>> get_set_bits_count(37) >>> get_set_bits_count_using_brian_kernighans_algorithm(37)
3 3
>>> get_set_bits_count(21) >>> get_set_bits_count_using_brian_kernighans_algorithm(21)
3 3
>>> get_set_bits_count(58) >>> get_set_bits_count_using_brian_kernighans_algorithm(58)
4 4
>>> get_set_bits_count(0) >>> get_set_bits_count_using_brian_kernighans_algorithm(0)
0 0
>>> get_set_bits_count(256) >>> get_set_bits_count_using_brian_kernighans_algorithm(256)
1 1
>>> get_set_bits_count(-1) >>> get_set_bits_count_using_brian_kernighans_algorithm(-1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
ValueError: the value of input must be positive ValueError: the value of input must not be negative
""" """
if number < 0: if number < 0:
raise ValueError("the value of input must be positive") raise ValueError("the value of input must not be negative")
result = 0
while number:
number &= number - 1
result += 1
return result
def get_set_bits_count_using_modulo_operator(number: int) -> int:
"""
Count the number of set bits in a 32 bit integer
>>> get_set_bits_count_using_modulo_operator(25)
3
>>> get_set_bits_count_using_modulo_operator(37)
3
>>> get_set_bits_count_using_modulo_operator(21)
3
>>> get_set_bits_count_using_modulo_operator(58)
4
>>> get_set_bits_count_using_modulo_operator(0)
0
>>> get_set_bits_count_using_modulo_operator(256)
1
>>> get_set_bits_count_using_modulo_operator(-1)
Traceback (most recent call last):
...
ValueError: the value of input must not be negative
"""
if number < 0:
raise ValueError("the value of input must not be negative")
result = 0 result = 0
while number: while number:
if number % 2 == 1: if number % 2 == 1:
result += 1 result += 1
number = number >> 1 number >>= 1
return result return result
def benchmark() -> None:
"""
Benchmark code for comparing 2 functions, with different length int values.
Brian Kernighan's algorithm is consistently faster than using modulo_operator.
"""
def do_benchmark(number: int) -> None:
setup = "import __main__ as z"
print(f"Benchmark when {number = }:")
print(f"{get_set_bits_count_using_modulo_operator(number) = }")
timing = timeit("z.get_set_bits_count_using_modulo_operator(25)", setup=setup)
print(f"timeit() runs in {timing} seconds")
print(f"{get_set_bits_count_using_brian_kernighans_algorithm(number) = }")
timing = timeit(
"z.get_set_bits_count_using_brian_kernighans_algorithm(25)",
setup=setup,
)
print(f"timeit() runs in {timing} seconds")
for number in (25, 37, 58, 0):
do_benchmark(number)
print()
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest
doctest.testmod() doctest.testmod()
benchmark()

View File

@ -0,0 +1,94 @@
def gray_code(bit_count: int) -> list:
"""
Takes in an integer n and returns a n-bit
gray code sequence
An n-bit gray code sequence is a sequence of 2^n
integers where:
a) Every integer is between [0,2^n -1] inclusive
b) The sequence begins with 0
c) An integer appears at most one times in the sequence
d)The binary representation of every pair of integers differ
by exactly one bit
e) The binary representation of first and last bit also
differ by exactly one bit
>>> gray_code(2)
[0, 1, 3, 2]
>>> gray_code(1)
[0, 1]
>>> gray_code(3)
[0, 1, 3, 2, 6, 7, 5, 4]
>>> gray_code(-1)
Traceback (most recent call last):
...
ValueError: The given input must be positive
>>> gray_code(10.6)
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for <<: 'int' and 'float'
"""
# bit count represents no. of bits in the gray code
if bit_count < 0:
raise ValueError("The given input must be positive")
# get the generated string sequence
sequence = gray_code_sequence_string(bit_count)
#
# convert them to integers
for i in range(len(sequence)):
sequence[i] = int(sequence[i], 2)
return sequence
def gray_code_sequence_string(bit_count: int) -> list:
"""
Will output the n-bit grey sequence as a
string of bits
>>> gray_code_sequence_string(2)
['00', '01', '11', '10']
>>> gray_code_sequence_string(1)
['0', '1']
"""
# The approach is a recursive one
# Base case achieved when either n = 0 or n=1
if bit_count == 0:
return ["0"]
if bit_count == 1:
return ["0", "1"]
seq_len = 1 << bit_count # defines the length of the sequence
# 1<< n is equivalent to 2^n
# recursive answer will generate answer for n-1 bits
smaller_sequence = gray_code_sequence_string(bit_count - 1)
sequence = []
# append 0 to first half of the smaller sequence generated
for i in range(seq_len // 2):
generated_no = "0" + smaller_sequence[i]
sequence.append(generated_no)
# append 1 to second half ... start from the end of the list
for i in reversed(range(seq_len // 2)):
generated_no = "1" + smaller_sequence[i]
sequence.append(generated_no)
return sequence
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,34 @@
def get_highest_set_bit_position(number: int) -> int:
"""
Returns position of the highest set bit of a number.
Ref - https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
>>> get_highest_set_bit_position(25)
5
>>> get_highest_set_bit_position(37)
6
>>> get_highest_set_bit_position(1)
1
>>> get_highest_set_bit_position(4)
3
>>> get_highest_set_bit_position(0)
0
>>> get_highest_set_bit_position(0.8)
Traceback (most recent call last):
...
TypeError: Input value must be an 'int' type
"""
if not isinstance(number, int):
raise TypeError("Input value must be an 'int' type")
position = 0
while number:
position += 1
number >>= 1
return position
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,51 @@
# Reference: https://www.geeksforgeeks.org/position-of-rightmost-set-bit/
def get_index_of_rightmost_set_bit(number: int) -> int:
"""
Take in a positive integer 'number'.
Returns the zero-based index of first set bit in that 'number' from right.
Returns -1, If no set bit found.
>>> get_index_of_rightmost_set_bit(0)
-1
>>> get_index_of_rightmost_set_bit(5)
0
>>> get_index_of_rightmost_set_bit(36)
2
>>> get_index_of_rightmost_set_bit(8)
3
>>> get_index_of_rightmost_set_bit(-18)
Traceback (most recent call last):
...
ValueError: Input must be a non-negative integer
>>> get_index_of_rightmost_set_bit('test')
Traceback (most recent call last):
...
ValueError: Input must be a non-negative integer
>>> get_index_of_rightmost_set_bit(1.25)
Traceback (most recent call last):
...
ValueError: Input must be a non-negative integer
"""
if not isinstance(number, int) or number < 0:
raise ValueError("Input must be a non-negative integer")
intermediate = number & ~(number - 1)
index = 0
while intermediate:
intermediate >>= 1
index += 1
return index - 1
if __name__ == "__main__":
"""
Finding the index of rightmost set bit has some very peculiar use-cases,
especially in finding missing or/and repeating numbers in a list of
positive integers.
"""
import doctest
doctest.testmod(verbose=True)

View File

@ -0,0 +1,37 @@
def is_even(number: int) -> bool:
"""
return true if the input integer is even
Explanation: Lets take a look at the following deicmal to binary conversions
2 => 10
14 => 1110
100 => 1100100
3 => 11
13 => 1101
101 => 1100101
from the above examples we can observe that
for all the odd integers there is always 1 set bit at the end
also, 1 in binary can be represented as 001, 00001, or 0000001
so for any odd integer n => n&1 is always equals 1 else the integer is even
>>> is_even(1)
False
>>> is_even(4)
True
>>> is_even(9)
False
>>> is_even(15)
False
>>> is_even(40)
True
>>> is_even(100)
True
>>> is_even(101)
False
"""
return number & 1 == 0
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,57 @@
"""
Author : Alexander Pantyukhin
Date : November 1, 2022
Task:
Given a positive int number. Return True if this number is power of 2
or False otherwise.
Implementation notes: Use bit manipulation.
For example if the number is the power of two it's bits representation:
n = 0..100..00
n - 1 = 0..011..11
n & (n - 1) - no intersections = 0
"""
def is_power_of_two(number: int) -> bool:
"""
Return True if this number is power of 2 or False otherwise.
>>> is_power_of_two(0)
True
>>> is_power_of_two(1)
True
>>> is_power_of_two(2)
True
>>> is_power_of_two(4)
True
>>> is_power_of_two(6)
False
>>> is_power_of_two(8)
True
>>> is_power_of_two(17)
False
>>> is_power_of_two(-1)
Traceback (most recent call last):
...
ValueError: number must not be negative
>>> is_power_of_two(1.2)
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for &: 'float' and 'float'
# Test all powers of 2 from 0 to 10,000
>>> all(is_power_of_two(int(2 ** i)) for i in range(10000))
True
"""
if number < 0:
raise ValueError("number must not be negative")
return number & (number - 1) == 0
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,39 @@
"""
Author : Alexander Pantyukhin
Date : November 30, 2022
Task:
Given two int numbers. Return True these numbers have opposite signs
or False otherwise.
Implementation notes: Use bit manipulation.
Use XOR for two numbers.
"""
def different_signs(num1: int, num2: int) -> bool:
"""
Return True if numbers have opposite signs False otherwise.
>>> different_signs(1, -1)
True
>>> different_signs(1, 1)
False
>>> different_signs(1000000000000000000000000000, -1000000000000000000000000000)
True
>>> different_signs(-1000000000000000000000000000, 1000000000000000000000000000)
True
>>> different_signs(50, 278)
False
>>> different_signs(0, 2)
False
>>> different_signs(2, 0)
False
"""
return num1 ^ num2 < 0
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -14,10 +14,11 @@ def get_reverse_bit_string(number: int) -> str:
TypeError: operation can not be conducted on a object of type str TypeError: operation can not be conducted on a object of type str
""" """
if not isinstance(number, int): if not isinstance(number, int):
raise TypeError( msg = (
"operation can not be conducted on a object of type " "operation can not be conducted on a object of type "
f"{type(number).__name__}" f"{type(number).__name__}"
) )
raise TypeError(msg)
bit_string = "" bit_string = ""
for _ in range(0, 32): for _ in range(0, 32):
bit_string += str(number % 2) bit_string += str(number % 2)

45
blockchain/README.md Normal file
View File

@ -0,0 +1,45 @@
# Blockchain
A Blockchain is a type of **distributed ledger** technology (DLT) that consists of growing list of records, called **blocks**, that are securely linked together using **cryptography**.
Let's breakdown the terminologies in the above definition. We find below terminologies,
- Digital Ledger Technology (DLT)
- Blocks
- Cryptography
## Digital Ledger Technology
It is otherwise called as distributed ledger technology. It is simply the opposite of centralized database. Firstly, what is a **ledger**? A ledger is a book or collection of accounts that records account transactions.
*Why is Blockchain addressed as digital ledger if it can record more than account transactions? What other transaction details and information can it hold?*
Digital Ledger Technology is just a ledger which is shared among multiple nodes. This way there exist no need for central authority to hold the info. Okay, how is it differentiated from central database and what are their benefits?
There is an organization which has 4 branches whose data are stored in a centralized database. So even if one branch needs any data from ledger they need an approval from database in charge. And if one hacks the central database he gets to tamper and control all the data.
Now lets assume every branch has a copy of the ledger and then once anything is added to the ledger by anyone branch it is gonna automatically reflect in all other ledgers available in other branch. This is done using Peer-to-peer network.
So this means even if information is tampered in one branch we can find out. If one branch is hacked we can be alerted ,so we can safeguard other branches. Now, assume these branches as computers or nodes and the ledger is a transaction record or digital receipt. If one ledger is hacked in a node we can detect since there will be a mismatch in comparison with other node information. So this is the concept of Digital Ledger Technology.
*Is it required for all nodes to have access to all information in other nodes? Wouldn't this require enormous storage space in each node?*
## Blocks
In short a block is nothing but collections of records with a labelled header. These are connected cryptographically. Once a new block is added to a chain, the previous block is connected, more precisely said as locked and hence, will remain unaltered. We can understand this concept once we get a clear understanding of working mechanism of blockchain.
## Cryptography
It is the practice and study of secure communication techniques in the midst of adversarial behavior. More broadly, cryptography is the creation and analysis of protocols that prevent third parties or the general public from accessing private messages.
*Which cryptography technology is most widely used in blockchain and why?*
So, in general, blockchain technology is a distributed record holder which records the information about ownership of an asset. To define precisely,
> Blockchain is a distributed, immutable ledger that makes it easier to record transactions and track assets in a corporate network.
An asset could be tangible (such as a house, car, cash, or land) or intangible (such as a business) (intellectual property, patents, copyrights, branding). A blockchain network can track and sell almost anything of value, lowering risk and costs for everyone involved.
So this is all about introduction to blockchain technology. To learn more about the topic refer below links....
* <https://en.wikipedia.org/wiki/Blockchain>
* <https://en.wikipedia.org/wiki/Chinese_remainder_theorem>
* <https://en.wikipedia.org/wiki/Diophantine_equation>
* <https://www.geeksforgeeks.org/modular-division/>

View File

@ -53,6 +53,7 @@ def chinese_remainder_theorem(n1: int, r1: int, n2: int, r2: int) -> int:
# ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid---------------- # ----------SAME SOLUTION USING InvertModulo instead ExtendedEuclid----------------
# This function find the inverses of a i.e., a^(-1) # This function find the inverses of a i.e., a^(-1)
def invert_modulo(a: int, n: int) -> int: def invert_modulo(a: int, n: int) -> int:
""" """

View File

@ -0,0 +1,7 @@
# Boolean Algebra
Boolean algebra is used to do arithmetic with bits of values True (1) or False (0).
There are three basic operations: 'and', 'or' and 'not'.
* <https://en.wikipedia.org/wiki/Boolean_algebra>
* <https://plato.stanford.edu/entries/boolalg-math/>

View File

@ -0,0 +1,50 @@
"""
An AND Gate is a logic gate in boolean algebra which results to 1 (True) if both the
inputs are 1, and 0 (False) otherwise.
Following is the truth table of an AND Gate:
------------------------------
| Input 1 | Input 2 | Output |
------------------------------
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
------------------------------
Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
"""
def and_gate(input_1: int, input_2: int) -> int:
"""
Calculate AND of the input values
>>> and_gate(0, 0)
0
>>> and_gate(0, 1)
0
>>> and_gate(1, 0)
0
>>> and_gate(1, 1)
1
"""
return int((input_1, input_2).count(0) == 0)
def test_and_gate() -> None:
"""
Tests the and_gate function
"""
assert and_gate(0, 0) == 0
assert and_gate(0, 1) == 0
assert and_gate(1, 0) == 0
assert and_gate(1, 1) == 1
if __name__ == "__main__":
test_and_gate()
print(and_gate(1, 0))
print(and_gate(0, 0))
print(and_gate(0, 1))
print(and_gate(1, 1))

View File

@ -0,0 +1,47 @@
"""
A NAND Gate is a logic gate in boolean algebra which results to 0 (False) if both
the inputs are 1, and 1 (True) otherwise. It's similar to adding
a NOT gate along with an AND gate.
Following is the truth table of a NAND Gate:
------------------------------
| Input 1 | Input 2 | Output |
------------------------------
| 0 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
------------------------------
Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
"""
def nand_gate(input_1: int, input_2: int) -> int:
"""
Calculate NAND of the input values
>>> nand_gate(0, 0)
1
>>> nand_gate(0, 1)
1
>>> nand_gate(1, 0)
1
>>> nand_gate(1, 1)
0
"""
return int((input_1, input_2).count(0) != 0)
def test_nand_gate() -> None:
"""
Tests the nand_gate function
"""
assert nand_gate(0, 0) == 1
assert nand_gate(0, 1) == 1
assert nand_gate(1, 0) == 1
assert nand_gate(1, 1) == 0
if __name__ == "__main__":
print(nand_gate(0, 0))
print(nand_gate(0, 1))
print(nand_gate(1, 0))
print(nand_gate(1, 1))

View File

@ -0,0 +1,48 @@
"""
A NOR Gate is a logic gate in boolean algebra which results to false(0)
if any of the input is 1, and True(1) if both the inputs are 0.
Following is the truth table of a NOR Gate:
| Input 1 | Input 2 | Output |
| 0 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 0 |
Following is the code implementation of the NOR Gate
"""
def nor_gate(input_1: int, input_2: int) -> int:
"""
>>> nor_gate(0, 0)
1
>>> nor_gate(0, 1)
0
>>> nor_gate(1, 0)
0
>>> nor_gate(1, 1)
0
>>> nor_gate(0.0, 0.0)
1
>>> nor_gate(0, -7)
0
"""
return int(input_1 == input_2 == 0)
def main() -> None:
print("Truth Table of NOR Gate:")
print("| Input 1 | Input 2 | Output |")
print(f"| 0 | 0 | {nor_gate(0, 0)} |")
print(f"| 0 | 1 | {nor_gate(0, 1)} |")
print(f"| 1 | 0 | {nor_gate(1, 0)} |")
print(f"| 1 | 1 | {nor_gate(1, 1)} |")
if __name__ == "__main__":
import doctest
doctest.testmod()
main()
"""Code provided by Akshaj Vishwanathan"""
"""Reference: https://www.geeksforgeeks.org/logic-gates-in-python/"""

View File

@ -0,0 +1,37 @@
"""
A NOT Gate is a logic gate in boolean algebra which results to 0 (False) if the
input is high, and 1 (True) if the input is low.
Following is the truth table of a XOR Gate:
------------------------------
| Input | Output |
------------------------------
| 0 | 1 |
| 1 | 0 |
------------------------------
Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
"""
def not_gate(input_1: int) -> int:
"""
Calculate NOT of the input values
>>> not_gate(0)
1
>>> not_gate(1)
0
"""
return 1 if input_1 == 0 else 0
def test_not_gate() -> None:
"""
Tests the not_gate function
"""
assert not_gate(0) == 1
assert not_gate(1) == 0
if __name__ == "__main__":
print(not_gate(0))
print(not_gate(1))

View File

@ -0,0 +1,46 @@
"""
An OR Gate is a logic gate in boolean algebra which results to 0 (False) if both the
inputs are 0, and 1 (True) otherwise.
Following is the truth table of an AND Gate:
------------------------------
| Input 1 | Input 2 | Output |
------------------------------
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
------------------------------
Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
"""
def or_gate(input_1: int, input_2: int) -> int:
"""
Calculate OR of the input values
>>> or_gate(0, 0)
0
>>> or_gate(0, 1)
1
>>> or_gate(1, 0)
1
>>> or_gate(1, 1)
1
"""
return int((input_1, input_2).count(1) != 0)
def test_or_gate() -> None:
"""
Tests the or_gate function
"""
assert or_gate(0, 0) == 0
assert or_gate(0, 1) == 1
assert or_gate(1, 0) == 1
assert or_gate(1, 1) == 1
if __name__ == "__main__":
print(or_gate(0, 1))
print(or_gate(1, 0))
print(or_gate(0, 0))
print(or_gate(1, 1))

View File

@ -1,25 +1,28 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Sequence
from typing import Literal
def compare_string(string1: str, string2: str) -> str:
def compare_string(string1: str, string2: str) -> str | Literal[False]:
""" """
>>> compare_string('0010','0110') >>> compare_string('0010','0110')
'0_10' '0_10'
>>> compare_string('0110','1101') >>> compare_string('0110','1101')
'X' False
""" """
l1 = list(string1) list1 = list(string1)
l2 = list(string2) list2 = list(string2)
count = 0 count = 0
for i in range(len(l1)): for i in range(len(list1)):
if l1[i] != l2[i]: if list1[i] != list2[i]:
count += 1 count += 1
l1[i] = "_" list1[i] = "_"
if count > 1: if count > 1:
return "X" return False
else: else:
return "".join(l1) return "".join(list1)
def check(binary: list[str]) -> list[str]: def check(binary: list[str]) -> list[str]:
@ -28,16 +31,16 @@ def check(binary: list[str]) -> list[str]:
['0.00.01.5'] ['0.00.01.5']
""" """
pi = [] pi = []
while 1: while True:
check1 = ["$"] * len(binary) check1 = ["$"] * len(binary)
temp = [] temp = []
for i in range(len(binary)): for i in range(len(binary)):
for j in range(i + 1, len(binary)): for j in range(i + 1, len(binary)):
k = compare_string(binary[i], binary[j]) k = compare_string(binary[i], binary[j])
if k != "X": if k is False:
check1[i] = "*" check1[i] = "*"
check1[j] = "*" check1[j] = "*"
temp.append(k) temp.append("X")
for i in range(len(binary)): for i in range(len(binary)):
if check1[i] == "$": if check1[i] == "$":
pi.append(binary[i]) pi.append(binary[i])
@ -46,19 +49,18 @@ def check(binary: list[str]) -> list[str]:
binary = list(set(temp)) binary = list(set(temp))
def decimal_to_binary(no_of_variable: int, minterms: list[float]) -> list[str]: def decimal_to_binary(no_of_variable: int, minterms: Sequence[float]) -> list[str]:
""" """
>>> decimal_to_binary(3,[1.5]) >>> decimal_to_binary(3,[1.5])
['0.00.01.5'] ['0.00.01.5']
""" """
temp = [] temp = []
s = "" for minterm in minterms:
for m in minterms: string = ""
for i in range(no_of_variable): for _ in range(no_of_variable):
s = str(m % 2) + s string = str(minterm % 2) + string
m //= 2 minterm //= 2
temp.append(s) temp.append(string)
s = ""
return temp return temp
@ -70,16 +72,13 @@ def is_for_table(string1: str, string2: str, count: int) -> bool:
>>> is_for_table('01_','001',1) >>> is_for_table('01_','001',1)
False False
""" """
l1 = list(string1) list1 = list(string1)
l2 = list(string2) list2 = list(string2)
count_n = 0 count_n = 0
for i in range(len(l1)): for i in range(len(list1)):
if l1[i] != l2[i]: if list1[i] != list2[i]:
count_n += 1 count_n += 1
if count_n == count: return count_n == count
return True
else:
return False
def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]: def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]:
@ -108,7 +107,7 @@ def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]:
for k in range(len(chart)): for k in range(len(chart)):
chart[k][j] = 0 chart[k][j] = 0
temp.append(prime_implicants[i]) temp.append(prime_implicants[i])
while 1: while True:
max_n = 0 max_n = 0
rem = -1 rem = -1
count_n = 0 count_n = 0
@ -146,10 +145,10 @@ def prime_implicant_chart(
return chart return chart
def main(): def main() -> None:
no_of_variable = int(input("Enter the no. of variables\n")) no_of_variable = int(input("Enter the no. of variables\n"))
minterms = [ minterms = [
int(x) float(x)
for x in input( for x in input(
"Enter the decimal representation of Minterms 'Spaces Separated'\n" "Enter the decimal representation of Minterms 'Spaces Separated'\n"
).split() ).split()

View File

@ -0,0 +1,48 @@
"""
A XNOR Gate is a logic gate in boolean algebra which results to 0 (False) if both the
inputs are different, and 1 (True), if the inputs are same.
It's similar to adding a NOT gate to an XOR gate
Following is the truth table of a XNOR Gate:
------------------------------
| Input 1 | Input 2 | Output |
------------------------------
| 0 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
------------------------------
Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
"""
def xnor_gate(input_1: int, input_2: int) -> int:
"""
Calculate XOR of the input values
>>> xnor_gate(0, 0)
1
>>> xnor_gate(0, 1)
0
>>> xnor_gate(1, 0)
0
>>> xnor_gate(1, 1)
1
"""
return 1 if input_1 == input_2 else 0
def test_xnor_gate() -> None:
"""
Tests the xnor_gate function
"""
assert xnor_gate(0, 0) == 1
assert xnor_gate(0, 1) == 0
assert xnor_gate(1, 0) == 0
assert xnor_gate(1, 1) == 1
if __name__ == "__main__":
print(xnor_gate(0, 0))
print(xnor_gate(0, 1))
print(xnor_gate(1, 0))
print(xnor_gate(1, 1))

View File

@ -0,0 +1,46 @@
"""
A XOR Gate is a logic gate in boolean algebra which results to 1 (True) if only one of
the two inputs is 1, and 0 (False) if an even number of inputs are 1.
Following is the truth table of a XOR Gate:
------------------------------
| Input 1 | Input 2 | Output |
------------------------------
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
------------------------------
Refer - https://www.geeksforgeeks.org/logic-gates-in-python/
"""
def xor_gate(input_1: int, input_2: int) -> int:
"""
calculate xor of the input values
>>> xor_gate(0, 0)
0
>>> xor_gate(0, 1)
1
>>> xor_gate(1, 0)
1
>>> xor_gate(1, 1)
0
"""
return (input_1, input_2).count(0) % 2
def test_xor_gate() -> None:
"""
Tests the xor_gate function
"""
assert xor_gate(0, 0) == 0
assert xor_gate(0, 1) == 1
assert xor_gate(1, 0) == 1
assert xor_gate(1, 1) == 0
if __name__ == "__main__":
print(xor_gate(0, 0))
print(xor_gate(0, 1))

View File

@ -1,4 +1,8 @@
# Cellular Automata # Cellular Automata
* https://en.wikipedia.org/wiki/Cellular_automaton Cellular automata are a way to simulate the behavior of "life", no matter if it is a robot or cell.
* https://mathworld.wolfram.com/ElementaryCellularAutomaton.html They usually follow simple rules but can lead to the creation of complex forms.
The most popular cellular automaton is Conway's [Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life).
* <https://en.wikipedia.org/wiki/Cellular_automaton>
* <https://mathworld.wolfram.com/ElementaryCellularAutomaton.html>

View File

@ -70,7 +70,7 @@ def new_generation(cells: list[list[int]]) -> list[list[int]]:
return next_generation return next_generation
def generate_images(cells: list[list[int]], frames) -> list[Image.Image]: def generate_images(cells: list[list[int]], frames: int) -> list[Image.Image]:
""" """
Generates a list of images of subsequent Game of Life states. Generates a list of images of subsequent Game of Life states.
""" """

View File

@ -10,7 +10,7 @@ Python:
- 3.5 - 3.5
Usage: Usage:
- $python3 game_o_life <canvas_size:int> - $python3 game_of_life <canvas_size:int>
Game-Of-Life Rules: Game-Of-Life Rules:
@ -34,25 +34,27 @@ import numpy as np
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from matplotlib.colors import ListedColormap from matplotlib.colors import ListedColormap
usage_doc = "Usage of script: script_nama <size_of_canvas:int>" usage_doc = "Usage of script: script_name <size_of_canvas:int>"
choice = [0] * 100 + [1] * 10 choice = [0] * 100 + [1] * 10
random.shuffle(choice) random.shuffle(choice)
def create_canvas(size): def create_canvas(size: int) -> list[list[bool]]:
canvas = [[False for i in range(size)] for j in range(size)] canvas = [[False for i in range(size)] for j in range(size)]
return canvas return canvas
def seed(canvas): def seed(canvas: list[list[bool]]) -> None:
for i, row in enumerate(canvas): for i, row in enumerate(canvas):
for j, _ in enumerate(row): for j, _ in enumerate(row):
canvas[i][j] = bool(random.getrandbits(1)) canvas[i][j] = bool(random.getrandbits(1))
def run(canvas):
"""This function runs the rules of game through all points, and changes their def run(canvas: list[list[bool]]) -> list[list[bool]]:
"""
This function runs the rules of game through all points, and changes their
status accordingly.(in the same canvas) status accordingly.(in the same canvas)
@Args: @Args:
-- --
@ -62,19 +64,18 @@ def run(canvas):
-- --
canvas of population after one step canvas of population after one step
""" """
canvas = np.array(canvas) current_canvas = np.array(canvas)
next_gen_canvas = np.array(create_canvas(canvas.shape[0])) next_gen_canvas = np.array(create_canvas(current_canvas.shape[0]))
for r, row in enumerate(canvas): for r, row in enumerate(current_canvas):
for c, pt in enumerate(row): for c, pt in enumerate(row):
# print(r-1,r+2,c-1,c+2)
next_gen_canvas[r][c] = __judge_point( next_gen_canvas[r][c] = __judge_point(
pt, canvas[r - 1 : r + 2, c - 1 : c + 2] pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2]
) )
return next_gen_canvas.tolist() return next_gen_canvas.tolist()
def __judge_point(pt, neighbours): def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool:
dead = 0 dead = 0
alive = 0 alive = 0
# finding dead or alive neighbours count. # finding dead or alive neighbours count.
@ -96,7 +97,7 @@ def __judge_point(pt, neighbours):
if pt: if pt:
if alive < 2: if alive < 2:
state = False state = False
elif alive == 2 or alive == 3: elif alive in {2, 3}:
state = True state = True
elif alive > 3: elif alive > 3:
state = False state = False

View File

@ -0,0 +1,139 @@
"""
Simulate the evolution of a highway with only one road that is a loop.
The highway is divided in cells, each cell can have at most one car in it.
The highway is a loop so when a car comes to one end, it will come out on the other.
Each car is represented by its speed (from 0 to 5).
Some information about speed:
-1 means that the cell on the highway is empty
0 to 5 are the speed of the cars with 0 being the lowest and 5 the highest
highway: list[int] Where every position and speed of every car will be stored
probability The probability that a driver will slow down
initial_speed The speed of the cars a the start
frequency How many cells there are between two cars at the start
max_speed The maximum speed a car can go to
number_of_cells How many cell are there in the highway
number_of_update How many times will the position be updated
More information here: https://en.wikipedia.org/wiki/Nagel%E2%80%93Schreckenberg_model
Examples for doctest:
>>> simulate(construct_highway(6, 3, 0), 2, 0, 2)
[[0, -1, -1, 0, -1, -1], [-1, 1, -1, -1, 1, -1], [-1, -1, 1, -1, -1, 1]]
>>> simulate(construct_highway(5, 2, -2), 3, 0, 2)
[[0, -1, 0, -1, 0], [0, -1, 0, -1, -1], [0, -1, -1, 1, -1], [-1, 1, -1, 0, -1]]
"""
from random import randint, random
def construct_highway(
number_of_cells: int,
frequency: int,
initial_speed: int,
random_frequency: bool = False,
random_speed: bool = False,
max_speed: int = 5,
) -> list:
"""
Build the highway following the parameters given
>>> construct_highway(10, 2, 6)
[[6, -1, 6, -1, 6, -1, 6, -1, 6, -1]]
>>> construct_highway(10, 10, 2)
[[2, -1, -1, -1, -1, -1, -1, -1, -1, -1]]
"""
highway = [[-1] * number_of_cells] # Create a highway without any car
i = 0
initial_speed = max(initial_speed, 0)
while i < number_of_cells:
highway[0][i] = (
randint(0, max_speed) if random_speed else initial_speed
) # Place the cars
i += (
randint(1, max_speed * 2) if random_frequency else frequency
) # Arbitrary number, may need tuning
return highway
def get_distance(highway_now: list, car_index: int) -> int:
"""
Get the distance between a car (at index car_index) and the next car
>>> get_distance([6, -1, 6, -1, 6], 2)
1
>>> get_distance([2, -1, -1, -1, 3, 1, 0, 1, 3, 2], 0)
3
>>> get_distance([-1, -1, -1, -1, 2, -1, -1, -1, 3], -1)
4
"""
distance = 0
cells = highway_now[car_index + 1 :]
for cell in range(len(cells)): # May need a better name for this
if cells[cell] != -1: # If the cell is not empty then
return distance # we have the distance we wanted
distance += 1
# Here if the car is near the end of the highway
return distance + get_distance(highway_now, -1)
def update(highway_now: list, probability: float, max_speed: int) -> list:
"""
Update the speed of the cars
>>> update([-1, -1, -1, -1, -1, 2, -1, -1, -1, -1, 3], 0.0, 5)
[-1, -1, -1, -1, -1, 3, -1, -1, -1, -1, 4]
>>> update([-1, -1, 2, -1, -1, -1, -1, 3], 0.0, 5)
[-1, -1, 3, -1, -1, -1, -1, 1]
"""
number_of_cells = len(highway_now)
# Beforce calculations, the highway is empty
next_highway = [-1] * number_of_cells
for car_index in range(number_of_cells):
if highway_now[car_index] != -1:
# Add 1 to the current speed of the car and cap the speed
next_highway[car_index] = min(highway_now[car_index] + 1, max_speed)
# Number of empty cell before the next car
dn = get_distance(highway_now, car_index) - 1
# We can't have the car causing an accident
next_highway[car_index] = min(next_highway[car_index], dn)
if random() < probability:
# Randomly, a driver will slow down
next_highway[car_index] = max(next_highway[car_index] - 1, 0)
return next_highway
def simulate(
highway: list, number_of_update: int, probability: float, max_speed: int
) -> list:
"""
The main function, it will simulate the evolution of the highway
>>> simulate([[-1, 2, -1, -1, -1, 3]], 2, 0.0, 3)
[[-1, 2, -1, -1, -1, 3], [-1, -1, -1, 2, -1, 0], [1, -1, -1, 0, -1, -1]]
>>> simulate([[-1, 2, -1, 3]], 4, 0.0, 3)
[[-1, 2, -1, 3], [-1, 0, -1, 0], [-1, 0, -1, 0], [-1, 0, -1, 0], [-1, 0, -1, 0]]
"""
number_of_cells = len(highway[0])
for i in range(number_of_update):
next_speeds_calculated = update(highway[i], probability, max_speed)
real_next_speeds = [-1] * number_of_cells
for car_index in range(number_of_cells):
speed = next_speeds_calculated[car_index]
if speed != -1:
# Change the position based on the speed (with % to create the loop)
index = (car_index + speed) % number_of_cells
# Commit the change of position
real_next_speeds[index] = speed
highway.append(real_next_speeds)
return highway
if __name__ == "__main__":
import doctest
doctest.testmod()

7
ciphers/README.md Normal file
View File

@ -0,0 +1,7 @@
# Ciphers
Ciphers are used to protect data from people that are not allowed to have it. They are everywhere on the internet to protect your connections.
* <https://en.wikipedia.org/wiki/Cipher>
* <http://practicalcryptography.com/ciphers/>
* <https://practicalcryptography.com/ciphers/classical-era/>

View File

@ -9,26 +9,26 @@ SYMBOLS = (
) )
def check_keys(keyA: int, keyB: int, mode: str) -> None: def check_keys(key_a: int, key_b: int, mode: str) -> None:
if mode == "encrypt": if mode == "encrypt":
if keyA == 1: if key_a == 1:
sys.exit( sys.exit(
"The affine cipher becomes weak when key " "The affine cipher becomes weak when key "
"A is set to 1. Choose different key" "A is set to 1. Choose different key"
) )
if keyB == 0: if key_b == 0:
sys.exit( sys.exit(
"The affine cipher becomes weak when key " "The affine cipher becomes weak when key "
"B is set to 0. Choose different key" "B is set to 0. Choose different key"
) )
if keyA < 0 or keyB < 0 or keyB > len(SYMBOLS) - 1: if key_a < 0 or key_b < 0 or key_b > len(SYMBOLS) - 1:
sys.exit( sys.exit(
"Key A must be greater than 0 and key B must " "Key A must be greater than 0 and key B must "
f"be between 0 and {len(SYMBOLS) - 1}." f"be between 0 and {len(SYMBOLS) - 1}."
) )
if cryptomath.gcd(keyA, len(SYMBOLS)) != 1: if cryptomath.gcd(key_a, len(SYMBOLS)) != 1:
sys.exit( sys.exit(
f"Key A {keyA} and the symbol set size {len(SYMBOLS)} " f"Key A {key_a} and the symbol set size {len(SYMBOLS)} "
"are not relatively prime. Choose a different key." "are not relatively prime. Choose a different key."
) )
@ -39,16 +39,16 @@ def encrypt_message(key: int, message: str) -> str:
... 'substitution cipher.') ... 'substitution cipher.')
'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi' 'VL}p MM{I}p~{HL}Gp{vp pFsH}pxMpyxIx JHL O}F{~pvuOvF{FuF{xIp~{HL}Gi'
""" """
keyA, keyB = divmod(key, len(SYMBOLS)) key_a, key_b = divmod(key, len(SYMBOLS))
check_keys(keyA, keyB, "encrypt") check_keys(key_a, key_b, "encrypt")
cipherText = "" cipher_text = ""
for symbol in message: for symbol in message:
if symbol in SYMBOLS: if symbol in SYMBOLS:
symIndex = SYMBOLS.find(symbol) sym_index = SYMBOLS.find(symbol)
cipherText += SYMBOLS[(symIndex * keyA + keyB) % len(SYMBOLS)] cipher_text += SYMBOLS[(sym_index * key_a + key_b) % len(SYMBOLS)]
else: else:
cipherText += symbol cipher_text += symbol
return cipherText return cipher_text
def decrypt_message(key: int, message: str) -> str: def decrypt_message(key: int, message: str) -> str:
@ -57,25 +57,27 @@ def decrypt_message(key: int, message: str) -> str:
... '{xIp~{HL}Gi') ... '{xIp~{HL}Gi')
'The affine cipher is a type of monoalphabetic substitution cipher.' 'The affine cipher is a type of monoalphabetic substitution cipher.'
""" """
keyA, keyB = divmod(key, len(SYMBOLS)) key_a, key_b = divmod(key, len(SYMBOLS))
check_keys(keyA, keyB, "decrypt") check_keys(key_a, key_b, "decrypt")
plainText = "" plain_text = ""
modInverseOfkeyA = cryptomath.find_mod_inverse(keyA, len(SYMBOLS)) mod_inverse_of_key_a = cryptomath.find_mod_inverse(key_a, len(SYMBOLS))
for symbol in message: for symbol in message:
if symbol in SYMBOLS: if symbol in SYMBOLS:
symIndex = SYMBOLS.find(symbol) sym_index = SYMBOLS.find(symbol)
plainText += SYMBOLS[(symIndex - keyB) * modInverseOfkeyA % len(SYMBOLS)] plain_text += SYMBOLS[
(sym_index - key_b) * mod_inverse_of_key_a % len(SYMBOLS)
]
else: else:
plainText += symbol plain_text += symbol
return plainText return plain_text
def get_random_key() -> int: def get_random_key() -> int:
while True: while True:
keyA = random.randint(2, len(SYMBOLS)) key_b = random.randint(2, len(SYMBOLS))
keyB = random.randint(2, len(SYMBOLS)) key_b = random.randint(2, len(SYMBOLS))
if cryptomath.gcd(keyA, len(SYMBOLS)) == 1 and keyB % len(SYMBOLS) != 0: if cryptomath.gcd(key_b, len(SYMBOLS)) == 1 and key_b % len(SYMBOLS) != 0:
return keyA * len(SYMBOLS) + keyB return key_b * len(SYMBOLS) + key_b
def main() -> None: def main() -> None:

View File

@ -38,26 +38,13 @@ def atbash(sequence: str) -> str:
def benchmark() -> None: def benchmark() -> None:
"""Let's benchmark them side-by-side...""" """Let's benchmark our functions side-by-side..."""
from timeit import timeit from timeit import timeit
print("Running performance benchmarks...") print("Running performance benchmarks...")
print( setup = "from string import printable ; from __main__ import atbash, atbash_slow"
"> atbash_slow()", print(f"> atbash_slow(): {timeit('atbash_slow(printable)', setup=setup)} seconds")
timeit( print(f"> atbash(): {timeit('atbash(printable)', setup=setup)} seconds")
"atbash_slow(printable)",
setup="from string import printable ; from __main__ import atbash_slow",
),
"seconds",
)
print(
"> atbash()",
timeit(
"atbash(printable)",
setup="from string import printable ; from __main__ import atbash",
),
"seconds",
)
if __name__ == "__main__": if __name__ == "__main__":

131
ciphers/autokey.py Normal file
View File

@ -0,0 +1,131 @@
"""
https://en.wikipedia.org/wiki/Autokey_cipher
An autokey cipher (also known as the autoclave cipher) is a cipher that
incorporates the message (the plaintext) into the key.
The key is generated from the message in some automated fashion,
sometimes by selecting certain letters from the text or, more commonly,
by adding a short primer key to the front of the message.
"""
def encrypt(plaintext: str, key: str) -> str:
"""
Encrypt a given plaintext (string) and key (string), returning the
encrypted ciphertext.
>>> encrypt("hello world", "coffee")
'jsqqs avvwo'
>>> encrypt("coffee is good as python", "TheAlgorithms")
'vvjfpk wj ohvp su ddylsv'
>>> encrypt("coffee is good as python", 2)
Traceback (most recent call last):
...
TypeError: key must be a string
>>> encrypt("", "TheAlgorithms")
Traceback (most recent call last):
...
ValueError: plaintext is empty
"""
if not isinstance(plaintext, str):
raise TypeError("plaintext must be a string")
if not isinstance(key, str):
raise TypeError("key must be a string")
if not plaintext:
raise ValueError("plaintext is empty")
if not key:
raise ValueError("key is empty")
key += plaintext
plaintext = plaintext.lower()
key = key.lower()
plaintext_iterator = 0
key_iterator = 0
ciphertext = ""
while plaintext_iterator < len(plaintext):
if (
ord(plaintext[plaintext_iterator]) < 97
or ord(plaintext[plaintext_iterator]) > 122
):
ciphertext += plaintext[plaintext_iterator]
plaintext_iterator += 1
elif ord(key[key_iterator]) < 97 or ord(key[key_iterator]) > 122:
key_iterator += 1
else:
ciphertext += chr(
(
(ord(plaintext[plaintext_iterator]) - 97 + ord(key[key_iterator]))
- 97
)
% 26
+ 97
)
key_iterator += 1
plaintext_iterator += 1
return ciphertext
def decrypt(ciphertext: str, key: str) -> str:
"""
Decrypt a given ciphertext (string) and key (string), returning the decrypted
ciphertext.
>>> decrypt("jsqqs avvwo", "coffee")
'hello world'
>>> decrypt("vvjfpk wj ohvp su ddylsv", "TheAlgorithms")
'coffee is good as python'
>>> decrypt("vvjfpk wj ohvp su ddylsv", "")
Traceback (most recent call last):
...
ValueError: key is empty
>>> decrypt(527.26, "TheAlgorithms")
Traceback (most recent call last):
...
TypeError: ciphertext must be a string
"""
if not isinstance(ciphertext, str):
raise TypeError("ciphertext must be a string")
if not isinstance(key, str):
raise TypeError("key must be a string")
if not ciphertext:
raise ValueError("ciphertext is empty")
if not key:
raise ValueError("key is empty")
key = key.lower()
ciphertext_iterator = 0
key_iterator = 0
plaintext = ""
while ciphertext_iterator < len(ciphertext):
if (
ord(ciphertext[ciphertext_iterator]) < 97
or ord(ciphertext[ciphertext_iterator]) > 122
):
plaintext += ciphertext[ciphertext_iterator]
else:
plaintext += chr(
(ord(ciphertext[ciphertext_iterator]) - ord(key[key_iterator])) % 26
+ 97
)
key += chr(
(ord(ciphertext[ciphertext_iterator]) - ord(key[key_iterator])) % 26
+ 97
)
key_iterator += 1
ciphertext_iterator += 1
return plaintext
if __name__ == "__main__":
import doctest
doctest.testmod()
operation = int(input("Type 1 to encrypt or 2 to decrypt:"))
if operation == 1:
plaintext = input("Typeplaintext to be encrypted:\n")
key = input("Type the key:\n")
print(encrypt(plaintext, key))
elif operation == 2:
ciphertext = input("Type the ciphertext to be decrypted:\n")
key = input("Type the key:\n")
print(decrypt(ciphertext, key))
decrypt("jsqqs avvwo", "coffee")

View File

@ -0,0 +1,89 @@
"""
Program to encode and decode Baconian or Bacon's Cipher
Wikipedia reference : https://en.wikipedia.org/wiki/Bacon%27s_cipher
"""
encode_dict = {
"a": "AAAAA",
"b": "AAAAB",
"c": "AAABA",
"d": "AAABB",
"e": "AABAA",
"f": "AABAB",
"g": "AABBA",
"h": "AABBB",
"i": "ABAAA",
"j": "BBBAA",
"k": "ABAAB",
"l": "ABABA",
"m": "ABABB",
"n": "ABBAA",
"o": "ABBAB",
"p": "ABBBA",
"q": "ABBBB",
"r": "BAAAA",
"s": "BAAAB",
"t": "BAABA",
"u": "BAABB",
"v": "BBBAB",
"w": "BABAA",
"x": "BABAB",
"y": "BABBA",
"z": "BABBB",
" ": " ",
}
decode_dict = {value: key for key, value in encode_dict.items()}
def encode(word: str) -> str:
"""
Encodes to Baconian cipher
>>> encode("hello")
'AABBBAABAAABABAABABAABBAB'
>>> encode("hello world")
'AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB'
>>> encode("hello world!")
Traceback (most recent call last):
...
Exception: encode() accepts only letters of the alphabet and spaces
"""
encoded = ""
for letter in word.lower():
if letter.isalpha() or letter == " ":
encoded += encode_dict[letter]
else:
raise Exception("encode() accepts only letters of the alphabet and spaces")
return encoded
def decode(coded: str) -> str:
"""
Decodes from Baconian cipher
>>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB")
'hello world'
>>> decode("AABBBAABAAABABAABABAABBAB")
'hello'
>>> decode("AABBBAABAAABABAABABAABBAB BABAAABBABBAAAAABABAAAABB!")
Traceback (most recent call last):
...
Exception: decode() accepts only 'A', 'B' and spaces
"""
if set(coded) - {"A", "B", " "} != set():
raise Exception("decode() accepts only 'A', 'B' and spaces")
decoded = ""
for word in coded.split():
while len(word) != 0:
decoded += decode_dict[word[:5]]
word = word[5:]
decoded += " "
return decoded.strip()
if __name__ == "__main__":
from doctest import testmod
testmod()

View File

@ -1,19 +1,63 @@
import base64 def base16_encode(data: bytes) -> str:
def encode_to_b16(inp: str) -> bytes:
""" """
Encodes a given utf-8 string into base-16. Encodes the given bytes into base16.
>>> encode_to_b16('Hello World!')
b'48656C6C6F20576F726C6421' >>> base16_encode(b'Hello World!')
>>> encode_to_b16('HELLO WORLD!') '48656C6C6F20576F726C6421'
b'48454C4C4F20574F524C4421' >>> base16_encode(b'HELLO WORLD!')
>>> encode_to_b16('') '48454C4C4F20574F524C4421'
>>> base16_encode(b'')
''
"""
# Turn the data into a list of integers (where each integer is a byte),
# Then turn each byte into its hexadecimal representation, make sure
# it is uppercase, and then join everything together and return it.
return "".join([hex(byte)[2:].zfill(2).upper() for byte in list(data)])
def base16_decode(data: str) -> bytes:
"""
Decodes the given base16 encoded data into bytes.
>>> base16_decode('48656C6C6F20576F726C6421')
b'Hello World!'
>>> base16_decode('48454C4C4F20574F524C4421')
b'HELLO WORLD!'
>>> base16_decode('')
b'' b''
>>> base16_decode('486')
Traceback (most recent call last):
...
ValueError: Base16 encoded data is invalid:
Data does not have an even number of hex digits.
>>> base16_decode('48656c6c6f20576f726c6421')
Traceback (most recent call last):
...
ValueError: Base16 encoded data is invalid:
Data is not uppercase hex or it contains invalid characters.
>>> base16_decode('This is not base64 encoded data.')
Traceback (most recent call last):
...
ValueError: Base16 encoded data is invalid:
Data is not uppercase hex or it contains invalid characters.
""" """
encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) # Check data validity, following RFC3548
b16encoded = base64.b16encode(encoded) # b16encoded the encoded string # https://www.ietf.org/rfc/rfc3548.txt
return b16encoded if (len(data) % 2) != 0:
raise ValueError(
"""Base16 encoded data is invalid:
Data does not have an even number of hex digits."""
)
# Check the character set - the standard base16 alphabet
# is uppercase according to RFC3548 section 6
if not set(data) <= set("0123456789ABCDEF"):
raise ValueError(
"""Base16 encoded data is invalid:
Data is not uppercase hex or it contains invalid characters."""
)
# For every two hexadecimal digits (= a byte), turn it into an integer.
# Then, string the result together into bytes, and return it.
return bytes(int(data[i] + data[i + 1], 16) for i in range(0, len(data), 2))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,13 +1,42 @@
import base64 import base64
def main() -> None: def base32_encode(string: str) -> bytes:
inp = input("->") """
encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) Encodes a given string to base32, returning a bytes-like object
b32encoded = base64.b32encode(encoded) # b32encoded the encoded string >>> base32_encode("Hello World!")
print(b32encoded) b'JBSWY3DPEBLW64TMMQQQ===='
print(base64.b32decode(b32encoded).decode("utf-8")) # decoded it >>> base32_encode("123456")
b'GEZDGNBVGY======'
>>> base32_encode("some long complex string")
b'ONXW2ZJANRXW4ZZAMNXW24DMMV4CA43UOJUW4ZY='
"""
# encoded the input (we need a bytes like object)
# then, b32encoded the bytes-like object
return base64.b32encode(string.encode("utf-8"))
def base32_decode(encoded_bytes: bytes) -> str:
"""
Decodes a given bytes-like object to a string, returning a string
>>> base32_decode(b'JBSWY3DPEBLW64TMMQQQ====')
'Hello World!'
>>> base32_decode(b'GEZDGNBVGY======')
'123456'
>>> base32_decode(b'ONXW2ZJANRXW4ZZAMNXW24DMMV4CA43UOJUW4ZY=')
'some long complex string'
"""
# decode the bytes from base32
# then, decode the bytes-like object to return as a string
return base64.b32decode(encoded_bytes).decode("utf-8")
if __name__ == "__main__": if __name__ == "__main__":
main() test = "Hello World!"
encoded = base32_encode(test)
print(encoded)
decoded = base32_decode(encoded)
print(decoded)

View File

@ -7,7 +7,7 @@ def base64_encode(data: bytes) -> bytes:
The data is first transformed to binary and appended with binary digits so that its 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 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 the B64_CHARSET string. The number of appended binary digits would later determine
how many "=" sign should be added, the padding. how many "=" signs should be added, the padding.
For every 2 binary digits added, a "=" sign is added in the output. 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 We can add any binary digits to make it a multiple of 6, for instance, consider the
following example: following example:
@ -34,9 +34,8 @@ def base64_encode(data: bytes) -> bytes:
""" """
# Make sure the supplied data is a bytes-like object # Make sure the supplied data is a bytes-like object
if not isinstance(data, bytes): if not isinstance(data, bytes):
raise TypeError( msg = f"a bytes-like object is required, not '{data.__class__.__name__}'"
f"a bytes-like object is required, not '{data.__class__.__name__}'" raise TypeError(msg)
)
binary_stream = "".join(bin(byte)[2:].zfill(8) for byte in data) binary_stream = "".join(bin(byte)[2:].zfill(8) for byte in data)
@ -88,10 +87,11 @@ def base64_decode(encoded_data: str) -> bytes:
""" """
# Make sure encoded_data is either a string or a bytes-like object # 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): if not isinstance(encoded_data, bytes) and not isinstance(encoded_data, str):
raise TypeError( msg = (
"argument should be a bytes-like object or ASCII string, not " "argument should be a bytes-like object or ASCII string, "
f"'{encoded_data.__class__.__name__}'" f"not '{encoded_data.__class__.__name__}'"
) )
raise TypeError(msg)
# In case encoded_data is a bytes-like object, make sure it contains only # In case encoded_data is a bytes-like object, make sure it contains only
# ASCII characters so we convert it to a string object # ASCII characters so we convert it to a string object

View File

@ -1,13 +1,33 @@
import base64 import base64
def main() -> None: def base85_encode(string: str) -> bytes:
inp = input("->") """
encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) >>> base85_encode("")
a85encoded = base64.a85encode(encoded) # a85encoded the encoded string b''
print(a85encoded) >>> base85_encode("12345")
print(base64.a85decode(a85encoded).decode("utf-8")) # decoded it b'0etOA2#'
>>> base85_encode("base 85")
b'@UX=h+?24'
"""
# encoded the input to a bytes-like object and then a85encode that
return base64.a85encode(string.encode("utf-8"))
def base85_decode(a85encoded: bytes) -> str:
"""
>>> base85_decode(b"")
''
>>> base85_decode(b"0etOA2#")
'12345'
>>> base85_decode(b"@UX=h+?24")
'base 85'
"""
# a85decode the input into bytes and decode that into a human readable string
return base64.a85decode(a85encoded).decode("utf-8")
if __name__ == "__main__": if __name__ == "__main__":
main() import doctest
doctest.testmod()

View File

@ -5,7 +5,7 @@ Author: Mohit Radadiya
from string import ascii_uppercase from string import ascii_uppercase
dict1 = {char: i for i, char in enumerate(ascii_uppercase)} dict1 = {char: i for i, char in enumerate(ascii_uppercase)}
dict2 = {i: char for i, char in enumerate(ascii_uppercase)} dict2 = dict(enumerate(ascii_uppercase))
# This function generates the key in # This function generates the key in

111
ciphers/bifid.py Normal file
View File

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""
The Bifid Cipher uses a Polybius Square to encipher a message in a way that
makes it fairly difficult to decipher without knowing the secret.
https://www.braingle.com/brainteasers/codes/bifid.php
"""
import numpy as np
SQUARE = [
["a", "b", "c", "d", "e"],
["f", "g", "h", "i", "k"],
["l", "m", "n", "o", "p"],
["q", "r", "s", "t", "u"],
["v", "w", "x", "y", "z"],
]
class BifidCipher:
def __init__(self) -> None:
self.SQUARE = np.array(SQUARE)
def letter_to_numbers(self, letter: str) -> np.ndarray:
"""
Return the pair of numbers that represents the given letter in the
polybius square
>>> np.array_equal(BifidCipher().letter_to_numbers('a'), [1,1])
True
>>> np.array_equal(BifidCipher().letter_to_numbers('u'), [4,5])
True
"""
index1, index2 = np.where(letter == self.SQUARE)
indexes = np.concatenate([index1 + 1, index2 + 1])
return indexes
def numbers_to_letter(self, index1: int, index2: int) -> str:
"""
Return the letter corresponding to the position [index1, index2] in
the polybius square
>>> BifidCipher().numbers_to_letter(4, 5) == "u"
True
>>> BifidCipher().numbers_to_letter(1, 1) == "a"
True
"""
letter = self.SQUARE[index1 - 1, index2 - 1]
return letter
def encode(self, message: str) -> str:
"""
Return the encoded version of message according to the polybius cipher
>>> BifidCipher().encode('testmessage') == 'qtltbdxrxlk'
True
>>> BifidCipher().encode('Test Message') == 'qtltbdxrxlk'
True
>>> BifidCipher().encode('test j') == BifidCipher().encode('test i')
True
"""
message = message.lower()
message = message.replace(" ", "")
message = message.replace("j", "i")
first_step = np.empty((2, len(message)))
for letter_index in range(len(message)):
numbers = self.letter_to_numbers(message[letter_index])
first_step[0, letter_index] = numbers[0]
first_step[1, letter_index] = numbers[1]
second_step = first_step.reshape(2 * len(message))
encoded_message = ""
for numbers_index in range(len(message)):
index1 = int(second_step[numbers_index * 2])
index2 = int(second_step[(numbers_index * 2) + 1])
letter = self.numbers_to_letter(index1, index2)
encoded_message = encoded_message + letter
return encoded_message
def decode(self, message: str) -> str:
"""
Return the decoded version of message according to the polybius cipher
>>> BifidCipher().decode('qtltbdxrxlk') == 'testmessage'
True
"""
message = message.lower()
message.replace(" ", "")
first_step = np.empty(2 * len(message))
for letter_index in range(len(message)):
numbers = self.letter_to_numbers(message[letter_index])
first_step[letter_index * 2] = numbers[0]
first_step[letter_index * 2 + 1] = numbers[1]
second_step = first_step.reshape((2, len(message)))
decoded_message = ""
for numbers_index in range(len(message)):
index1 = int(second_step[0, numbers_index])
index2 = int(second_step[1, numbers_index])
letter = self.numbers_to_letter(index1, index2)
decoded_message = decoded_message + letter
return decoded_message

View File

@ -1,3 +1,6 @@
import string
def decrypt(message: str) -> None: def decrypt(message: str) -> None:
""" """
>>> decrypt('TMDETUX PMDVU') >>> decrypt('TMDETUX PMDVU')
@ -28,16 +31,15 @@ def decrypt(message: str) -> None:
Decryption using Key #24: VOFGVWZ ROFXW Decryption using Key #24: VOFGVWZ ROFXW
Decryption using Key #25: UNEFUVY QNEWV Decryption using Key #25: UNEFUVY QNEWV
""" """
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" for key in range(len(string.ascii_uppercase)):
for key in range(len(LETTERS)):
translated = "" translated = ""
for symbol in message: for symbol in message:
if symbol in LETTERS: if symbol in string.ascii_uppercase:
num = LETTERS.find(symbol) num = string.ascii_uppercase.find(symbol)
num = num - key num = num - key
if num < 0: if num < 0:
num = num + len(LETTERS) num = num + len(string.ascii_uppercase)
translated = translated + LETTERS[num] translated = translated + string.ascii_uppercase[num]
else: else:
translated = translated + symbol translated = translated + symbol
print(f"Decryption using Key #{key}: {translated}") print(f"Decryption using Key #{key}: {translated}")

View File

@ -27,7 +27,7 @@ def encrypt(input_string: str, key: int, alphabet: str | None = None) -> str:
========================= =========================
The caesar cipher is named after Julius Caesar who used it when sending The caesar cipher is named after Julius Caesar who used it when sending
secret military messages to his troops. This is a simple substitution cipher secret military messages to his troops. This is a simple substitution cipher
where very character in the plain-text is shifted by a certain number known where every character in the plain-text is shifted by a certain number known
as the "key" or "shift". as the "key" or "shift".
Example: Example:

View File

@ -6,7 +6,8 @@ def gcd(a: int, b: int) -> int:
def find_mod_inverse(a: int, m: int) -> int: def find_mod_inverse(a: int, m: int) -> int:
if gcd(a, m) != 1: if gcd(a, m) != 1:
raise ValueError(f"mod inverse of {a!r} and {m!r} does not exist") msg = f"mod inverse of {a!r} and {m!r} does not exist"
raise ValueError(msg)
u1, u2, u3 = 1, 0, a u1, u2, u3 = 1, 0, a
v1, v2, v3 = 0, 1, m v1, v2, v3 = 0, 1, m
while v3 != 0: while v3 != 0:

View File

@ -6,7 +6,7 @@ def decrypt_caesar_with_chi_squared(
ciphertext: str, ciphertext: str,
cipher_alphabet: list[str] | None = None, cipher_alphabet: list[str] | None = None,
frequencies_dict: dict[str, float] | None = None, frequencies_dict: dict[str, float] | None = None,
case_sensetive: bool = False, case_sensitive: bool = False,
) -> tuple[int, float, str]: ) -> tuple[int, float, str]:
""" """
Basic Usage Basic Usage
@ -20,7 +20,7 @@ def decrypt_caesar_with_chi_squared(
* frequencies_dict (dict): a dictionary of word frequencies where keys are * frequencies_dict (dict): a dictionary of word frequencies where keys are
the letters and values are a percentage representation of the frequency as the letters and values are a percentage representation of the frequency as
a decimal/float a decimal/float
* case_sensetive (bool): a boolean value: True if the case matters during * case_sensitive (bool): a boolean value: True if the case matters during
decryption, False if it doesn't decryption, False if it doesn't
Returns: Returns:
@ -117,6 +117,9 @@ def decrypt_caesar_with_chi_squared(
>>> decrypt_caesar_with_chi_squared('crybd cdbsxq') >>> decrypt_caesar_with_chi_squared('crybd cdbsxq')
(10, 233.35343938980898, 'short string') (10, 233.35343938980898, 'short string')
>>> decrypt_caesar_with_chi_squared('Crybd Cdbsxq', case_sensitive=True)
(10, 233.35343938980898, 'Short String')
>>> decrypt_caesar_with_chi_squared(12) >>> decrypt_caesar_with_chi_squared(12)
Traceback (most recent call last): Traceback (most recent call last):
AttributeError: 'int' object has no attribute 'lower' AttributeError: 'int' object has no attribute 'lower'
@ -158,7 +161,7 @@ def decrypt_caesar_with_chi_squared(
# Custom frequencies dictionary # Custom frequencies dictionary
frequencies = frequencies_dict frequencies = frequencies_dict
if not case_sensetive: if not case_sensitive:
ciphertext = ciphertext.lower() ciphertext = ciphertext.lower()
# Chi squared statistic values # Chi squared statistic values
@ -172,10 +175,14 @@ def decrypt_caesar_with_chi_squared(
for letter in ciphertext: for letter in ciphertext:
try: try:
# Try to index the letter in the alphabet # Try to index the letter in the alphabet
new_key = (alphabet_letters.index(letter) - shift) % len( new_key = (alphabet_letters.index(letter.lower()) - shift) % len(
alphabet_letters alphabet_letters
) )
decrypted_with_shift += alphabet_letters[new_key] decrypted_with_shift += (
alphabet_letters[new_key].upper()
if case_sensitive and letter.isupper()
else alphabet_letters[new_key]
)
except ValueError: except ValueError:
# Append the character if it isn't in the alphabet # Append the character if it isn't in the alphabet
decrypted_with_shift += letter decrypted_with_shift += letter
@ -184,10 +191,11 @@ def decrypt_caesar_with_chi_squared(
# Loop through each letter in the decoded message with the shift # Loop through each letter in the decoded message with the shift
for letter in decrypted_with_shift: for letter in decrypted_with_shift:
if case_sensetive: if case_sensitive:
letter = letter.lower()
if letter in frequencies: if letter in frequencies:
# Get the amount of times the letter occurs in the message # Get the amount of times the letter occurs in the message
occurrences = decrypted_with_shift.count(letter) occurrences = decrypted_with_shift.lower().count(letter)
# Get the excepcted amount of times the letter should appear based # Get the excepcted amount of times the letter should appear based
# on letter frequencies # on letter frequencies
@ -221,10 +229,13 @@ def decrypt_caesar_with_chi_squared(
# Get the most likely cipher by finding the cipher with the smallest chi squared # Get the most likely cipher by finding the cipher with the smallest chi squared
# statistic # statistic
most_likely_cipher: int = min( # type: ignore def chi_squared_statistic_values_sorting_key(key: int) -> tuple[float, str]:
chi_squared_statistic_values, # type: ignore return chi_squared_statistic_values[key]
key=chi_squared_statistic_values.get, # type: ignore
) # type: ignore most_likely_cipher: int = min(
chi_squared_statistic_values,
key=chi_squared_statistic_values_sorting_key,
)
# Get all the data from the most likely cipher (key, decoded message) # Get all the data from the most likely cipher (key, decoded message)
( (

View File

@ -73,7 +73,7 @@ def miller_rabin(n: int, allow_probable: bool = False) -> bool:
for prime in plist: for prime in plist:
pr = False pr = False
for r in range(s): for r in range(s):
m = pow(prime, d * 2 ** r, n) m = pow(prime, d * 2**r, n)
# see article for analysis explanation for m # see article for analysis explanation for m
if (r == 0 and m == 1) or ((m + 1) % n == 0): if (r == 0 and m == 1) or ((m + 1) % n == 0):
pr = True pr = True

View File

@ -10,13 +10,13 @@ primes = {
5: { 5: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -25,16 +25,16 @@ primes = {
14: { 14: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF", "15728E5A8AACAA68FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -43,21 +43,21 @@ primes = {
15: { 15: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -66,27 +66,27 @@ primes = {
16: { 16: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6"
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+ "FFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -95,33 +95,33 @@ primes = {
17: { 17: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+ "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+ "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+ "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+ "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+ "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+ "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+ "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+ "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+ "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+ "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+ "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+ "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+ "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+ "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+ "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+ "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+ "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+ "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+ "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+ "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+ "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+ "6DCC4024FFFFFFFFFFFFFFFF", "6DCC4024FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -130,48 +130,48 @@ primes = {
18: { 18: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6"
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD"
+ "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831"
+ "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B"
+ "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF"
+ "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6"
+ "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3"
+ "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328"
+ "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C"
+ "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE"
+ "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4"
+ "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300"
+ "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568"
+ "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+ "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B"
+ "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A"
+ "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36"
+ "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
+ "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92"
+ "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
+ "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71"
+ "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", "60C980DD98EDD3DFFFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -228,10 +228,10 @@ class DiffieHellman:
def is_valid_public_key(self, key: int) -> bool: def is_valid_public_key(self, key: int) -> bool:
# check if the other public key is valid based on NIST SP800-56 # check if the other public key is valid based on NIST SP800-56
if 2 <= key and key <= self.prime - 2: return (
if pow(key, (self.prime - 1) // 2, self.prime) == 1: 2 <= key <= self.prime - 2
return True and pow(key, (self.prime - 1) // 2, self.prime) == 1
return False )
def generate_shared_key(self, other_key_str: str) -> str: def generate_shared_key(self, other_key_str: str) -> str:
other_key = int(other_key_str, base=16) other_key = int(other_key_str, base=16)
@ -243,10 +243,10 @@ class DiffieHellman:
@staticmethod @staticmethod
def is_valid_public_key_static(remote_public_key_str: int, prime: int) -> bool: def is_valid_public_key_static(remote_public_key_str: int, prime: int) -> bool:
# check if the other public key is valid based on NIST SP800-56 # 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: return (
if pow(remote_public_key_str, (prime - 1) // 2, prime) == 1: 2 <= remote_public_key_str <= prime - 2
return True and pow(remote_public_key_str, (prime - 1) // 2, prime) == 1
return False )
@staticmethod @staticmethod
def generate_shared_key_static( def generate_shared_key_static(

View File

@ -26,7 +26,7 @@ def primitive_root(p_val: int) -> int:
def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, int]]: def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, int]]:
print("Generating prime p...") print("Generating prime p...")
p = rabin_miller.generateLargePrime(key_size) # select large prime number. p = rabin_miller.generate_large_prime(key_size) # select large prime number.
e_1 = primitive_root(p) # one primitive root on modulo p. 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. d = random.randrange(3, p) # private_key -> have to be greater than 2 for safety.
e_2 = cryptomath.find_mod_inverse(pow(e_1, d, p), p) e_2 = cryptomath.find_mod_inverse(pow(e_1, d, p), p)
@ -37,28 +37,23 @@ def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, i
return public_key, private_key return public_key, private_key
def make_key_files(name: str, keySize: int) -> None: def make_key_files(name: str, key_size: int) -> None:
if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( if os.path.exists(f"{name}_pubkey.txt") or os.path.exists(f"{name}_privkey.txt"):
"%s_privkey.txt" % name
):
print("\nWARNING:") print("\nWARNING:")
print( print(
'"%s_pubkey.txt" or "%s_privkey.txt" already exists. \n' f'"{name}_pubkey.txt" or "{name}_privkey.txt" already exists. \n'
"Use a different name or delete these files and re-run this program." "Use a different name or delete these files and re-run this program."
% (name, name)
) )
sys.exit() sys.exit()
publicKey, privateKey = generate_key(keySize) public_key, private_key = generate_key(key_size)
print("\nWriting public key to file %s_pubkey.txt..." % name) print(f"\nWriting public key to file {name}_pubkey.txt...")
with open("%s_pubkey.txt" % name, "w") as fo: with open(f"{name}_pubkey.txt", "w") as fo:
fo.write( fo.write(f"{public_key[0]},{public_key[1]},{public_key[2]},{public_key[3]}")
"%d,%d,%d,%d" % (publicKey[0], publicKey[1], publicKey[2], publicKey[3])
)
print("Writing private key to file %s_privkey.txt..." % name) print(f"Writing private key to file {name}_privkey.txt...")
with open("%s_privkey.txt" % name, "w") as fo: with open(f"{name}_privkey.txt", "w") as fo:
fo.write("%d,%d" % (privateKey[0], privateKey[1])) fo.write(f"{private_key[0]},{private_key[1]}")
def main() -> None: def main() -> None:

View File

@ -8,7 +8,7 @@ the famous Enigma machine from WWII.
Module includes: Module includes:
- enigma function - enigma function
- showcase of function usage - showcase of function usage
- 9 randnomly generated rotors - 9 randomly generated rotors
- reflector (aka static rotor) - reflector (aka static rotor)
- original alphabet - original alphabet
@ -86,24 +86,21 @@ def _validator(
""" """
# Checks if there are 3 unique rotors # Checks if there are 3 unique rotors
unique_rotsel = len(set(rotsel)) if (unique_rotsel := len(set(rotsel))) < 3:
if unique_rotsel < 3: msg = f"Please use 3 unique rotors (not {unique_rotsel})"
raise Exception(f"Please use 3 unique rotors (not {unique_rotsel})") raise Exception(msg)
# Checks if rotor positions are valid # Checks if rotor positions are valid
rotorpos1, rotorpos2, rotorpos3 = rotpos rotorpos1, rotorpos2, rotorpos3 = rotpos
if not 0 < rotorpos1 <= len(abc): if not 0 < rotorpos1 <= len(abc):
raise ValueError( msg = f"First rotor position is not within range of 1..26 ({rotorpos1}"
f"First rotor position is not within range of 1..26 (" f"{rotorpos1}" raise ValueError(msg)
)
if not 0 < rotorpos2 <= len(abc): if not 0 < rotorpos2 <= len(abc):
raise ValueError( msg = f"Second rotor position is not within range of 1..26 ({rotorpos2})"
f"Second rotor position is not within range of 1..26 (" f"{rotorpos2})" raise ValueError(msg)
)
if not 0 < rotorpos3 <= len(abc): if not 0 < rotorpos3 <= len(abc):
raise ValueError( msg = f"Third rotor position is not within range of 1..26 ({rotorpos3})"
f"Third rotor position is not within range of 1..26 (" f"{rotorpos3})" raise ValueError(msg)
)
# Validates string and returns dict # Validates string and returns dict
pbdict = _plugboard(pb) pbdict = _plugboard(pb)
@ -131,9 +128,11 @@ def _plugboard(pbstring: str) -> dict[str, str]:
# a) is type string # a) is type string
# b) has even length (so pairs can be made) # b) has even length (so pairs can be made)
if not isinstance(pbstring, str): if not isinstance(pbstring, str):
raise TypeError(f"Plugboard setting isn't type string ({type(pbstring)})") msg = f"Plugboard setting isn't type string ({type(pbstring)})"
raise TypeError(msg)
elif len(pbstring) % 2 != 0: elif len(pbstring) % 2 != 0:
raise Exception(f"Odd number of symbols ({len(pbstring)})") msg = f"Odd number of symbols ({len(pbstring)})"
raise Exception(msg)
elif pbstring == "": elif pbstring == "":
return {} return {}
@ -143,9 +142,11 @@ def _plugboard(pbstring: str) -> dict[str, str]:
tmppbl = set() tmppbl = set()
for i in pbstring: for i in pbstring:
if i not in abc: if i not in abc:
raise Exception(f"'{i}' not in list of symbols") msg = f"'{i}' not in list of symbols"
raise Exception(msg)
elif i in tmppbl: elif i in tmppbl:
raise Exception(f"Duplicate symbol ({i})") msg = f"Duplicate symbol ({i})"
raise Exception(msg)
else: else:
tmppbl.add(i) tmppbl.add(i)
del tmppbl del tmppbl
@ -231,7 +232,6 @@ def enigma(
# encryption/decryption process -------------------------- # encryption/decryption process --------------------------
for symbol in text: for symbol in text:
if symbol in abc: if symbol in abc:
# 1st plugboard -------------------------- # 1st plugboard --------------------------
if symbol in plugboard: if symbol in plugboard:
symbol = plugboard[symbol] symbol = plugboard[symbol]

View File

@ -62,7 +62,7 @@ class HillCipher:
# take x and return x % len(key_string) # take x and return x % len(key_string)
modulus = numpy.vectorize(lambda x: x % 36) modulus = numpy.vectorize(lambda x: x % 36)
to_int = numpy.vectorize(lambda x: round(x)) to_int = numpy.vectorize(round)
def __init__(self, encrypt_key: numpy.ndarray) -> None: def __init__(self, encrypt_key: numpy.ndarray) -> None:
""" """
@ -104,10 +104,11 @@ class HillCipher:
req_l = len(self.key_string) req_l = len(self.key_string)
if greatest_common_divisor(det, len(self.key_string)) != 1: if greatest_common_divisor(det, len(self.key_string)) != 1:
raise ValueError( msg = (
f"determinant modular {req_l} of encryption key({det}) is not co prime " f"determinant modular {req_l} of encryption key({det}) "
f"w.r.t {req_l}.\nTry another key." f"is not co prime w.r.t {req_l}.\nTry another key."
) )
raise ValueError(msg)
def process_text(self, text: str) -> str: def process_text(self, text: str) -> str:
""" """
@ -201,11 +202,11 @@ class HillCipher:
def main() -> None: def main() -> None:
N = int(input("Enter the order of the encryption key: ")) n = int(input("Enter the order of the encryption key: "))
hill_matrix = [] hill_matrix = []
print("Enter each row of the encryption key with space separated integers") print("Enter each row of the encryption key with space separated integers")
for _ in range(N): for _ in range(n):
row = [int(x) for x in input().split()] row = [int(x) for x in input().split()]
hill_matrix.append(row) hill_matrix.append(row)

Some files were not shown because too many files have changed in this diff Show More