mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-01-18 16:27:02 +00:00
Clear gh-pages branch
This commit is contained in:
parent
260e3d8b35
commit
2c90add821
|
@ -1,8 +0,0 @@
|
||||||
# https://github.com/microsoft/vscode-dev-containers/blob/main/containers/python-3/README.md
|
|
||||||
ARG VARIANT=3.12-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
|
|
|
@ -1 +0,0 @@
|
||||||
https://code.visualstudio.com/docs/devcontainers/tutorial
|
|
|
@ -1,42 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Python 3",
|
|
||||||
"build": {
|
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"context": "..",
|
|
||||||
"args": {
|
|
||||||
// Update 'VARIANT' to pick a Python version: 3, 3.11, 3.10, 3.9, 3.8
|
|
||||||
// Append -bullseye or -buster to pin to an OS version.
|
|
||||||
// Use -bullseye variants on local on arm64/Apple Silicon.
|
|
||||||
"VARIANT": "3.12-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"
|
|
||||||
}
|
|
88
.github/CODEOWNERS
vendored
88
.github/CODEOWNERS
vendored
|
@ -1,88 +0,0 @@
|
||||||
# This is a comment.
|
|
||||||
# Each line is a file pattern followed by one or more owners.
|
|
||||||
|
|
||||||
# More details are here: https://help.github.com/articles/about-codeowners/
|
|
||||||
|
|
||||||
# The '*' pattern is global owners.
|
|
||||||
|
|
||||||
# Order is important. The last matching pattern has the most precedence.
|
|
||||||
|
|
||||||
/.* @cclauss
|
|
||||||
|
|
||||||
# /arithmetic_analysis/
|
|
||||||
|
|
||||||
# /backtracking/
|
|
||||||
|
|
||||||
# /bit_manipulation/
|
|
||||||
|
|
||||||
# /blockchain/
|
|
||||||
|
|
||||||
# /boolean_algebra/
|
|
||||||
|
|
||||||
# /cellular_automata/
|
|
||||||
|
|
||||||
# /ciphers/
|
|
||||||
|
|
||||||
# /compression/
|
|
||||||
|
|
||||||
# /computer_vision/
|
|
||||||
|
|
||||||
# /conversions/
|
|
||||||
|
|
||||||
# /data_structures/
|
|
||||||
|
|
||||||
# /digital_image_processing/
|
|
||||||
|
|
||||||
# /divide_and_conquer/
|
|
||||||
|
|
||||||
# /dynamic_programming/
|
|
||||||
|
|
||||||
# /file_transfer/
|
|
||||||
|
|
||||||
# /fuzzy_logic/
|
|
||||||
|
|
||||||
# /genetic_algorithm/
|
|
||||||
|
|
||||||
# /geodesy/
|
|
||||||
|
|
||||||
# /graphics/
|
|
||||||
|
|
||||||
# /graphs/
|
|
||||||
|
|
||||||
# /greedy_method/
|
|
||||||
|
|
||||||
# /hashes/
|
|
||||||
|
|
||||||
# /images/
|
|
||||||
|
|
||||||
# /linear_algebra/
|
|
||||||
|
|
||||||
# /machine_learning/
|
|
||||||
|
|
||||||
# /maths/
|
|
||||||
|
|
||||||
# /matrix/
|
|
||||||
|
|
||||||
# /networking_flow/
|
|
||||||
|
|
||||||
# /neural_network/
|
|
||||||
|
|
||||||
# /other/
|
|
||||||
|
|
||||||
# /project_euler/
|
|
||||||
|
|
||||||
# /quantum/
|
|
||||||
|
|
||||||
# /scheduling/
|
|
||||||
|
|
||||||
# /scripts/
|
|
||||||
|
|
||||||
# /searches/
|
|
||||||
|
|
||||||
# /sorts/
|
|
||||||
|
|
||||||
# /strings/
|
|
||||||
|
|
||||||
# /traversals/
|
|
||||||
|
|
||||||
/web_programming/ @cclauss
|
|
54
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
54
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,54 +0,0 @@
|
||||||
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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +0,0 @@
|
||||||
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
|
|
19
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
19
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -1,19 +0,0 @@
|
||||||
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).
|
|
||||||
Do not create issues to implement new algorithms as these will be closed.
|
|
||||||
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 include new topics or improving any existing implementations.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
19
.github/ISSUE_TEMPLATE/other.yml
vendored
19
.github/ISSUE_TEMPLATE/other.yml
vendored
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -1,8 +0,0 @@
|
||||||
# Keep GitHub Actions up to date with Dependabot...
|
|
||||||
# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "daily"
|
|
21
.github/pull_request_template.md
vendored
21
.github/pull_request_template.md
vendored
|
@ -1,21 +0,0 @@
|
||||||
### Describe your change:
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* [ ] Add an algorithm?
|
|
||||||
* [ ] Fix a bug or typo in an existing algorithm?
|
|
||||||
* [ ] Add or change doctests? -- Note: Please avoid changing both code and tests in a single pull request.
|
|
||||||
* [ ] Documentation change?
|
|
||||||
|
|
||||||
### Checklist:
|
|
||||||
* [ ] 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.
|
|
||||||
* [ ] I know that pull requests will not be merged if they fail the automated tests.
|
|
||||||
* [ ] This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
|
|
||||||
* [ ] All new Python files are placed inside an existing directory.
|
|
||||||
* [ ] All filenames are in all lowercase characters with no spaces or dashes.
|
|
||||||
* [ ] 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 functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing.
|
|
||||||
* [ ] 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 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".
|
|
63
.github/stale.yml
vendored
63
.github/stale.yml
vendored
|
@ -1,63 +0,0 @@
|
||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
|
||||||
daysUntilStale: 30
|
|
||||||
|
|
||||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
|
||||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
|
||||||
daysUntilClose: 7
|
|
||||||
|
|
||||||
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
|
|
||||||
onlyLabels: []
|
|
||||||
|
|
||||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
|
||||||
exemptLabels:
|
|
||||||
- "Status: on hold"
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
|
||||||
exemptProjects: false
|
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
|
||||||
exemptMilestones: false
|
|
||||||
|
|
||||||
# Set to true to ignore issues with an assignee (defaults to false)
|
|
||||||
exemptAssignees: false
|
|
||||||
|
|
||||||
# Label to use when marking as stale
|
|
||||||
staleLabel: stale
|
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
|
||||||
limitPerRun: 5
|
|
||||||
|
|
||||||
# Comment to post when removing the stale label.
|
|
||||||
# unmarkComment: >
|
|
||||||
# Your comment here.
|
|
||||||
|
|
||||||
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
|
|
||||||
pulls:
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This pull request has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
for your contributions.
|
|
||||||
|
|
||||||
# Comment to post when closing a stale Pull Request.
|
|
||||||
closeComment: >
|
|
||||||
Please reopen this pull request once you commit the changes requested
|
|
||||||
or make improvements on the code. If this is not the case and you need
|
|
||||||
some help, feel free to seek help from our [Gitter](https://gitter.im/TheAlgorithms/community)
|
|
||||||
or ping one of the reviewers. Thank you for your contributions!
|
|
||||||
|
|
||||||
issues:
|
|
||||||
# Comment to post when marking as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
for your contributions.
|
|
||||||
|
|
||||||
# Comment to post when closing a stale Issue.
|
|
||||||
closeComment: >
|
|
||||||
Please reopen this issue once you add more information and updates here.
|
|
||||||
If this is not the case and you need some help, feel free to seek help
|
|
||||||
from our [Gitter](https://gitter.im/TheAlgorithms/community) or ping one of the
|
|
||||||
reviewers. Thank you for your contributions!
|
|
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
|
@ -1,38 +0,0 @@
|
||||||
name: "build"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *" # Run everyday
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.13
|
|
||||||
allow-prereleases: true
|
|
||||||
- uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ~/.cache/pip
|
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip setuptools wheel
|
|
||||||
python -m pip install pytest-cov -r requirements.txt
|
|
||||||
- name: Run tests
|
|
||||||
# TODO: #8818 Re-enable quantum tests
|
|
||||||
run: pytest
|
|
||||||
--ignore=quantum/q_fourier_transform.py
|
|
||||||
--ignore=computer_vision/cnn_classification.py
|
|
||||||
--ignore=dynamic_programming/k_means_clustering_tensorflow.py
|
|
||||||
--ignore=machine_learning/lstm/lstm_prediction.py
|
|
||||||
--ignore=neural_network/input_data.py
|
|
||||||
--ignore=project_euler/
|
|
||||||
--ignore=scripts/validate_solutions.py
|
|
||||||
--cov-report=term-missing:skip-covered
|
|
||||||
--cov=. .
|
|
||||||
- if: ${{ success() }}
|
|
||||||
run: scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md
|
|
25
.github/workflows/directory_writer.yml
vendored
25
.github/workflows/directory_writer.yml
vendored
|
@ -1,25 +0,0 @@
|
||||||
# The objective of this GitHub Action is to update the DIRECTORY.md file (if needed)
|
|
||||||
# when doing a git push
|
|
||||||
name: directory_writer
|
|
||||||
on: [push]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.x
|
|
||||||
- name: Write DIRECTORY.md
|
|
||||||
run: |
|
|
||||||
scripts/build_directory_md.py 2>&1 | tee DIRECTORY.md
|
|
||||||
git config --global user.name "$GITHUB_ACTOR"
|
|
||||||
git config --global user.email "$GITHUB_ACTOR@users.noreply.github.com"
|
|
||||||
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/$GITHUB_REPOSITORY
|
|
||||||
- name: Update DIRECTORY.md
|
|
||||||
run: |
|
|
||||||
git add DIRECTORY.md
|
|
||||||
git commit -am "updating DIRECTORY.md" || true
|
|
||||||
git push --force origin HEAD:$GITHUB_REF || true
|
|
39
.github/workflows/project_euler.yml
vendored
39
.github/workflows/project_euler.yml
vendored
|
@ -1,39 +0,0 @@
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
# Run only if a file is changed within the project_euler directory and related files
|
|
||||||
paths:
|
|
||||||
- "project_euler/**"
|
|
||||||
- ".github/workflows/project_euler.yml"
|
|
||||||
- "scripts/validate_solutions.py"
|
|
||||||
schedule:
|
|
||||||
- cron: "0 0 * * *" # Run everyday
|
|
||||||
|
|
||||||
name: "Project Euler"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
project-euler:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.x
|
|
||||||
- name: Install pytest and pytest-cov
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install --upgrade numpy pytest pytest-cov
|
|
||||||
- run: pytest --doctest-modules --cov-report=term-missing:skip-covered --cov=project_euler/ project_euler/
|
|
||||||
validate-solutions:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: 3.x
|
|
||||||
- name: Install pytest and requests
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install --upgrade numpy pytest requests
|
|
||||||
- run: pytest scripts/validate_solutions.py
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
16
.github/workflows/ruff.yml
vendored
16
.github/workflows/ruff.yml
vendored
|
@ -1,16 +0,0 @@
|
||||||
# https://beta.ruff.rs
|
|
||||||
name: ruff
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
jobs:
|
|
||||||
ruff:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- run: pip install --user ruff
|
|
||||||
- run: ruff check --output-format=github .
|
|
110
.gitignore
vendored
110
.gitignore
vendored
|
@ -1,110 +0,0 @@
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a Python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
.python-version
|
|
||||||
|
|
||||||
# celery beat schedule file
|
|
||||||
celerybeat-schedule
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
.idea
|
|
||||||
.try
|
|
||||||
.vscode/
|
|
||||||
.vs/
|
|
|
@ -1,2 +0,0 @@
|
||||||
tasks:
|
|
||||||
- init: pip3 install -r ./requirements.txt
|
|
|
@ -1,64 +0,0 @@
|
||||||
repos:
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v5.0.0
|
|
||||||
hooks:
|
|
||||||
- id: check-executables-have-shebangs
|
|
||||||
- id: check-toml
|
|
||||||
- id: check-yaml
|
|
||||||
- id: end-of-file-fixer
|
|
||||||
types: [python]
|
|
||||||
- id: trailing-whitespace
|
|
||||||
- id: requirements-txt-fixer
|
|
||||||
|
|
||||||
- repo: https://github.com/MarcoGorelli/auto-walrus
|
|
||||||
rev: 0.3.4
|
|
||||||
hooks:
|
|
||||||
- id: auto-walrus
|
|
||||||
|
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
||||||
rev: v0.6.9
|
|
||||||
hooks:
|
|
||||||
- id: ruff
|
|
||||||
- id: ruff-format
|
|
||||||
|
|
||||||
- repo: https://github.com/codespell-project/codespell
|
|
||||||
rev: v2.3.0
|
|
||||||
hooks:
|
|
||||||
- id: codespell
|
|
||||||
additional_dependencies:
|
|
||||||
- tomli
|
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
|
||||||
rev: "2.2.4"
|
|
||||||
hooks:
|
|
||||||
- id: pyproject-fmt
|
|
||||||
|
|
||||||
- repo: local
|
|
||||||
hooks:
|
|
||||||
- id: validate-filenames
|
|
||||||
name: Validate filenames
|
|
||||||
entry: ./scripts/validate_filenames.py
|
|
||||||
language: script
|
|
||||||
pass_filenames: false
|
|
||||||
|
|
||||||
- repo: https://github.com/abravalheri/validate-pyproject
|
|
||||||
rev: v0.20.2
|
|
||||||
hooks:
|
|
||||||
- id: validate-pyproject
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
||||||
rev: v1.11.2
|
|
||||||
hooks:
|
|
||||||
- id: mypy
|
|
||||||
args:
|
|
||||||
- --explicit-package-bases
|
|
||||||
- --ignore-missing-imports
|
|
||||||
- --install-types # See mirrors-mypy README.md
|
|
||||||
- --non-interactive
|
|
||||||
additional_dependencies: [types-requests]
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
|
||||||
rev: "v4.0.0-alpha.8"
|
|
||||||
hooks:
|
|
||||||
- id: prettier
|
|
||||||
types_or: [toml, yaml]
|
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"githubPullRequests.ignoredPullRequestBranches": [
|
|
||||||
"master"
|
|
||||||
]
|
|
||||||
}
|
|
193
CONTRIBUTING.md
193
CONTRIBUTING.md
|
@ -1,193 +0,0 @@
|
||||||
# Contributing guidelines
|
|
||||||
|
|
||||||
## Before contributing
|
|
||||||
|
|
||||||
Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before submitting your pull requests, please ensure that you __read the whole guidelines__. If you have any doubts about the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community on [Gitter](https://gitter.im/TheAlgorithms/community).
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
### Contributor
|
|
||||||
|
|
||||||
We are delighted that you are considering implementing algorithms and data structures for others! This repository is referenced and used by learners from all over the globe. By being one of our contributors, you agree and confirm that:
|
|
||||||
|
|
||||||
- You did your work - no plagiarism allowed.
|
|
||||||
- Any plagiarized work will not be merged.
|
|
||||||
- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged.
|
|
||||||
- Your submitted work fulfills or mostly fulfills 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.
|
|
||||||
|
|
||||||
__Improving comments__ and __writing proper tests__ are also highly welcome.
|
|
||||||
|
|
||||||
### Contribution
|
|
||||||
|
|
||||||
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 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 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.
|
|
||||||
|
|
||||||
#### Issues
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
__Do not__ create an issue to contribute an algorithm. Please submit a pull request instead.
|
|
||||||
|
|
||||||
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?
|
|
||||||
|
|
||||||
An Algorithm is one or more functions (or classes) that:
|
|
||||||
* take one or more inputs,
|
|
||||||
* perform some internal calculations or data manipulations,
|
|
||||||
* return one or more outputs,
|
|
||||||
* have minimal side effects (Ex. `print()`, `plot()`, `read()`, `write()`).
|
|
||||||
|
|
||||||
Algorithms should be packaged in a way that would make it easy for readers to put them into larger programs.
|
|
||||||
|
|
||||||
Algorithms should:
|
|
||||||
* have intuitive class and function names that make their purpose clear to readers
|
|
||||||
* use Python naming conventions and intuitive variable names to ease comprehension
|
|
||||||
* be flexible to take different input values
|
|
||||||
* have Python type hints for their input parameters and return values
|
|
||||||
* raise Python exceptions (`ValueError`, etc.) on erroneous input values
|
|
||||||
* have docstrings with clear explanations and/or URLs to source materials
|
|
||||||
* contain doctests that test both valid and erroneous input values
|
|
||||||
* return all calculation results instead of printing or plotting them
|
|
||||||
|
|
||||||
Algorithms in this repo should not be how-to examples for existing Python packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing Python packages but each algorithm in this repo should add unique value.
|
|
||||||
|
|
||||||
#### Pre-commit plugin
|
|
||||||
Use [pre-commit](https://pre-commit.com/#installation) to automatically format your code to match our coding style:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m pip install pre-commit # only required the first time
|
|
||||||
pre-commit install
|
|
||||||
```
|
|
||||||
That's it! The plugin will run every time you commit any changes. If there are any errors found during the run, fix them and commit those changes. You can even run the plugin manually on all files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pre-commit run --all-files --show-diff-on-failure
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Coding Style
|
|
||||||
|
|
||||||
We want your work to be readable by others; therefore, we encourage you to note the following:
|
|
||||||
|
|
||||||
- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
|
|
||||||
- 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.
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
- We encourage the use of Python [f-strings](https://realpython.com/python-f-strings/#f-strings-a-new-and-improved-way-to-format-strings-in-python) where they make the code easier to read.
|
|
||||||
|
|
||||||
- Please consider running [__psf/black__](https://github.com/python/black) on your Python file(s) before submitting your pull request. This is not yet a requirement but it does make your code more readable and automatically aligns it with much of [PEP 8](https://www.python.org/dev/peps/pep-0008/). There are other code formatters (autopep8, yapf) but the __black__ formatter is now hosted by the Python Software Foundation. To use it,
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m pip install black # only required the first time
|
|
||||||
black .
|
|
||||||
```
|
|
||||||
|
|
||||||
- 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
|
|
||||||
python3 -m pip install ruff # only required the first time
|
|
||||||
ruff check
|
|
||||||
```
|
|
||||||
|
|
||||||
- Original code submission require docstrings or comments to describe your work.
|
|
||||||
|
|
||||||
- More on docstrings and comments:
|
|
||||||
|
|
||||||
If you used a Wikipedia article or some other source material to create your algorithm, please add the URL in a docstring or comment to help your reader.
|
|
||||||
|
|
||||||
The following are considered to be bad and may be requested to be improved:
|
|
||||||
|
|
||||||
```python
|
|
||||||
x = x + 2 # increased by 2
|
|
||||||
```
|
|
||||||
|
|
||||||
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 the indentation of docstrings. The following is a good example:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def sum_ab(a, b):
|
|
||||||
"""
|
|
||||||
Return the sum of two integers a and b.
|
|
||||||
"""
|
|
||||||
return a + b
|
|
||||||
```
|
|
||||||
|
|
||||||
- Write tests (especially [__doctests__](https://docs.python.org/3/library/doctest.html)) to illustrate and verify your work. We highly encourage the use of _doctests on all functions_.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def sum_ab(a, b):
|
|
||||||
"""
|
|
||||||
Return the sum of two integers a and b
|
|
||||||
>>> sum_ab(2, 2)
|
|
||||||
4
|
|
||||||
>>> sum_ab(-2, 3)
|
|
||||||
1
|
|
||||||
>>> sum_ab(4.9, 5.1)
|
|
||||||
10.0
|
|
||||||
"""
|
|
||||||
return a + b
|
|
||||||
```
|
|
||||||
|
|
||||||
These doctests will be run by pytest as part of our automated testing so please try to run your doctests locally and make sure that they are found and pass:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 -m doctest -v my_submission.py
|
|
||||||
```
|
|
||||||
|
|
||||||
The use of the Python built-in `input()` function is __not__ encouraged:
|
|
||||||
|
|
||||||
```python
|
|
||||||
input('Enter your input:')
|
|
||||||
# Or even worse...
|
|
||||||
input = eval(input("Enter your input: "))
|
|
||||||
```
|
|
||||||
|
|
||||||
However, if your code uses `input()` then we encourage you to gracefully deal with leading and trailing whitespace in user input by adding `.strip()` as in:
|
|
||||||
|
|
||||||
```python
|
|
||||||
starting_value = int(input("Please enter a starting value: ").strip())
|
|
||||||
```
|
|
||||||
|
|
||||||
The use of [Python type hints](https://docs.python.org/3/library/typing.html) is encouraged for function parameters and return values. Our automated testing will run [mypy](http://mypy-lang.org) so run that locally before making your submission.
|
|
||||||
|
|
||||||
```python
|
|
||||||
def sum_ab(a: int, b: int) -> int:
|
|
||||||
return a + b
|
|
||||||
```
|
|
||||||
|
|
||||||
Instructions on how to install mypy can be found [here](https://github.com/python/mypy). Please use the command `mypy --ignore-missing-imports .` to test all files or `mypy --ignore-missing-imports path/to/file.py` to test a specific file.
|
|
||||||
|
|
||||||
- [__List comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain.
|
|
||||||
|
|
||||||
- Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms.
|
|
||||||
- If you need a third-party module that is not in the file __requirements.txt__, please add it to that file as part of your submission.
|
|
||||||
|
|
||||||
#### Other Requirements for Submissions
|
|
||||||
- If you are submitting code in the `project_euler/` directory, please also read [the dedicated Guideline](https://github.com/TheAlgorithms/Python/blob/master/project_euler/README.md) before contributing to our Project Euler library.
|
|
||||||
- The file extension for code files should be `.py`. Jupyter Notebooks should be submitted to [TheAlgorithms/Jupyter](https://github.com/TheAlgorithms/Jupyter).
|
|
||||||
- Strictly use snake_case (underscore_separated) in your file_name, as it will be easy to parse in future using scripts.
|
|
||||||
- Please avoid creating new directories if at all possible. Try to fit your work into the existing directory structure.
|
|
||||||
- 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 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 GitHub Actions processes.
|
|
||||||
- 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 you to add [__Python type hints__](https://docs.python.org/3/library/typing.html) where it makes sense to do so.
|
|
||||||
|
|
||||||
- Most importantly,
|
|
||||||
- __Be consistent in the use of these guidelines when submitting.__
|
|
||||||
- __Join__ us on [Discord](https://discord.com/invite/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms/community) __now!__
|
|
||||||
- Happy coding!
|
|
||||||
|
|
||||||
Writer [@poyea](https://github.com/poyea), Jun 2019.
|
|
1364
DIRECTORY.md
1364
DIRECTORY.md
File diff suppressed because it is too large
Load Diff
21
LICENSE.md
21
LICENSE.md
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016-2022 TheAlgorithms and contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
49
README.md
49
README.md
|
@ -1,49 +0,0 @@
|
||||||
<div align="center">
|
|
||||||
<!-- Title: -->
|
|
||||||
<a href="https://github.com/TheAlgorithms/">
|
|
||||||
<img src="https://raw.githubusercontent.com/TheAlgorithms/website/1cd824df116b27029f17c2d1b42d81731f28a920/public/logo.svg" height="100">
|
|
||||||
</a>
|
|
||||||
<h1><a href="https://github.com/TheAlgorithms/">The Algorithms</a> - Python</h1>
|
|
||||||
<!-- Labels: -->
|
|
||||||
<!-- First row: -->
|
|
||||||
<a href="https://gitpod.io/#https://github.com/TheAlgorithms/Python">
|
|
||||||
<img src="https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod&style=flat-square" height="20" alt="Gitpod Ready-to-Code">
|
|
||||||
</a>
|
|
||||||
<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">
|
|
||||||
</a>
|
|
||||||
<img src="https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square" height="20">
|
|
||||||
<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">
|
|
||||||
</a>
|
|
||||||
<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">
|
|
||||||
</a>
|
|
||||||
<!-- Second row: -->
|
|
||||||
<br>
|
|
||||||
<a href="https://github.com/TheAlgorithms/Python/actions">
|
|
||||||
<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://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">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/psf/black">
|
|
||||||
<img src="https://img.shields.io/static/v1?label=code%20style&message=black&color=black&style=flat-square" height="20" alt="code style: black">
|
|
||||||
</a>
|
|
||||||
<!-- Short description: -->
|
|
||||||
<h3>All algorithms implemented in Python - for education</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.
|
|
||||||
|
|
||||||
## Community Channels
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
See our [directory](DIRECTORY.md) for easier navigation and a better overview of the project.
|
|
|
@ -1,9 +0,0 @@
|
||||||
# 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>
|
|
|
@ -1,234 +0,0 @@
|
||||||
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),
|
|
||||||
) -> 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),
|
|
||||||
) -> 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),
|
|
||||||
) -> 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),
|
|
||||||
) -> 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),
|
|
||||||
) -> 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),
|
|
||||||
) -> 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),
|
|
||||||
) -> 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
|
|
|
@ -1,61 +0,0 @@
|
||||||
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)
|
|
|
@ -1,94 +0,0 @@
|
||||||
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
|
|
|
@ -1,76 +0,0 @@
|
||||||
{
|
|
||||||
"_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
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from abc import abstractmethod
|
|
||||||
from math import pi
|
|
||||||
from typing import Protocol
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
class FilterType(Protocol):
|
|
||||||
@abstractmethod
|
|
||||||
def process(self, sample: float) -> float:
|
|
||||||
"""
|
|
||||||
Calculate y[n]
|
|
||||||
|
|
||||||
>>> issubclass(FilterType, Protocol)
|
|
||||||
True
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
|
@ -1,8 +0,0 @@
|
||||||
# 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/>
|
|
|
@ -1,82 +0,0 @@
|
||||||
"""
|
|
||||||
In this problem, we want to determine all possible combinations of k
|
|
||||||
numbers out of 1 ... n. We use backtracking to solve this problem.
|
|
||||||
|
|
||||||
Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))),
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from itertools import combinations
|
|
||||||
|
|
||||||
|
|
||||||
def combination_lists(n: int, k: int) -> list[list[int]]:
|
|
||||||
"""
|
|
||||||
>>> combination_lists(n=4, k=2)
|
|
||||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
|
||||||
"""
|
|
||||||
return [list(x) for x in combinations(range(1, n + 1), k)]
|
|
||||||
|
|
||||||
|
|
||||||
def generate_all_combinations(n: int, k: int) -> list[list[int]]:
|
|
||||||
"""
|
|
||||||
>>> generate_all_combinations(n=4, k=2)
|
|
||||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
|
||||||
>>> generate_all_combinations(n=0, k=0)
|
|
||||||
[[]]
|
|
||||||
>>> generate_all_combinations(n=10, k=-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: k must not be negative
|
|
||||||
>>> generate_all_combinations(n=-1, k=10)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: n must not be negative
|
|
||||||
>>> generate_all_combinations(n=5, k=4)
|
|
||||||
[[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
|
|
||||||
>>> from itertools import combinations
|
|
||||||
>>> all(generate_all_combinations(n, k) == combination_lists(n, k)
|
|
||||||
... for n in range(1, 6) for k in range(1, 6))
|
|
||||||
True
|
|
||||||
"""
|
|
||||||
if k < 0:
|
|
||||||
raise ValueError("k must not be negative")
|
|
||||||
if n < 0:
|
|
||||||
raise ValueError("n must not be negative")
|
|
||||||
|
|
||||||
result: list[list[int]] = []
|
|
||||||
create_all_state(1, n, k, [], result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def create_all_state(
|
|
||||||
increment: int,
|
|
||||||
total_number: int,
|
|
||||||
level: int,
|
|
||||||
current_list: list[int],
|
|
||||||
total_list: list[list[int]],
|
|
||||||
) -> None:
|
|
||||||
if level == 0:
|
|
||||||
total_list.append(current_list[:])
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in range(increment, total_number - level + 2):
|
|
||||||
current_list.append(i)
|
|
||||||
create_all_state(i + 1, total_number, level - 1, current_list, total_list)
|
|
||||||
current_list.pop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from doctest import testmod
|
|
||||||
|
|
||||||
testmod()
|
|
||||||
print(generate_all_combinations(n=4, k=2))
|
|
||||||
tests = ((n, k) for n in range(1, 5) for k in range(1, 5))
|
|
||||||
for n, k in tests:
|
|
||||||
print(n, k, generate_all_combinations(n, k) == combination_lists(n, k))
|
|
||||||
|
|
||||||
print("Benchmark:")
|
|
||||||
from timeit import timeit
|
|
||||||
|
|
||||||
for func in ("combination_lists", "generate_all_combinations"):
|
|
||||||
print(f"{func:>25}(): {timeit(f'{func}(n=4, k = 2)', globals=globals())}")
|
|
|
@ -1,88 +0,0 @@
|
||||||
"""
|
|
||||||
In this problem, we want to determine all possible permutations
|
|
||||||
of the given sequence. We use backtracking to solve this problem.
|
|
||||||
|
|
||||||
Time complexity: O(n! * n),
|
|
||||||
where n denotes the length of the given sequence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def generate_all_permutations(sequence: list[int | str]) -> None:
|
|
||||||
create_state_space_tree(sequence, [], 0, [0 for i in range(len(sequence))])
|
|
||||||
|
|
||||||
|
|
||||||
def create_state_space_tree(
|
|
||||||
sequence: list[int | str],
|
|
||||||
current_sequence: list[int | str],
|
|
||||||
index: int,
|
|
||||||
index_used: list[int],
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Creates a state space tree to iterate through each branch using DFS.
|
|
||||||
We know that each state has exactly len(sequence) - index children.
|
|
||||||
It terminates when it reaches the end of the given sequence.
|
|
||||||
|
|
||||||
:param sequence: The input sequence for which permutations are generated.
|
|
||||||
:param current_sequence: The current permutation being built.
|
|
||||||
:param index: The current index in the sequence.
|
|
||||||
:param index_used: list to track which elements are used in permutation.
|
|
||||||
|
|
||||||
Example 1:
|
|
||||||
>>> sequence = [1, 2, 3]
|
|
||||||
>>> current_sequence = []
|
|
||||||
>>> index_used = [False, False, False]
|
|
||||||
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
|
|
||||||
[1, 2, 3]
|
|
||||||
[1, 3, 2]
|
|
||||||
[2, 1, 3]
|
|
||||||
[2, 3, 1]
|
|
||||||
[3, 1, 2]
|
|
||||||
[3, 2, 1]
|
|
||||||
|
|
||||||
Example 2:
|
|
||||||
>>> sequence = ["A", "B", "C"]
|
|
||||||
>>> current_sequence = []
|
|
||||||
>>> index_used = [False, False, False]
|
|
||||||
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
|
|
||||||
['A', 'B', 'C']
|
|
||||||
['A', 'C', 'B']
|
|
||||||
['B', 'A', 'C']
|
|
||||||
['B', 'C', 'A']
|
|
||||||
['C', 'A', 'B']
|
|
||||||
['C', 'B', 'A']
|
|
||||||
|
|
||||||
Example 3:
|
|
||||||
>>> sequence = [1]
|
|
||||||
>>> current_sequence = []
|
|
||||||
>>> index_used = [False]
|
|
||||||
>>> create_state_space_tree(sequence, current_sequence, 0, index_used)
|
|
||||||
[1]
|
|
||||||
"""
|
|
||||||
|
|
||||||
if index == len(sequence):
|
|
||||||
print(current_sequence)
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in range(len(sequence)):
|
|
||||||
if not index_used[i]:
|
|
||||||
current_sequence.append(sequence[i])
|
|
||||||
index_used[i] = True
|
|
||||||
create_state_space_tree(sequence, current_sequence, index + 1, index_used)
|
|
||||||
current_sequence.pop()
|
|
||||||
index_used[i] = False
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
remove the comment to take an input from the user
|
|
||||||
|
|
||||||
print("Enter the elements")
|
|
||||||
sequence = list(map(int, input().split()))
|
|
||||||
"""
|
|
||||||
|
|
||||||
sequence: list[int | str] = [3, 1, 2, 4]
|
|
||||||
generate_all_permutations(sequence)
|
|
||||||
|
|
||||||
sequence_2: list[int | str] = ["A", "B", "C"]
|
|
||||||
generate_all_permutations(sequence_2)
|
|
|
@ -1,93 +0,0 @@
|
||||||
"""
|
|
||||||
In this problem, we want to determine all possible subsequences
|
|
||||||
of the given sequence. We use backtracking to solve this problem.
|
|
||||||
|
|
||||||
Time complexity: O(2^n),
|
|
||||||
where n denotes the length of the given sequence.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
|
|
||||||
def generate_all_subsequences(sequence: list[Any]) -> None:
|
|
||||||
create_state_space_tree(sequence, [], 0)
|
|
||||||
|
|
||||||
|
|
||||||
def create_state_space_tree(
|
|
||||||
sequence: list[Any], current_subsequence: list[Any], index: int
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Creates a state space tree to iterate through each branch using DFS.
|
|
||||||
We know that each state has exactly two children.
|
|
||||||
It terminates when it reaches the end of the given sequence.
|
|
||||||
|
|
||||||
:param sequence: The input sequence for which subsequences are generated.
|
|
||||||
:param current_subsequence: The current subsequence being built.
|
|
||||||
:param index: The current index in the sequence.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> sequence = [3, 2, 1]
|
|
||||||
>>> current_subsequence = []
|
|
||||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
|
||||||
[]
|
|
||||||
[1]
|
|
||||||
[2]
|
|
||||||
[2, 1]
|
|
||||||
[3]
|
|
||||||
[3, 1]
|
|
||||||
[3, 2]
|
|
||||||
[3, 2, 1]
|
|
||||||
|
|
||||||
>>> sequence = ["A", "B"]
|
|
||||||
>>> current_subsequence = []
|
|
||||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
|
||||||
[]
|
|
||||||
['B']
|
|
||||||
['A']
|
|
||||||
['A', 'B']
|
|
||||||
|
|
||||||
>>> sequence = []
|
|
||||||
>>> current_subsequence = []
|
|
||||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
|
||||||
[]
|
|
||||||
|
|
||||||
>>> sequence = [1, 2, 3, 4]
|
|
||||||
>>> current_subsequence = []
|
|
||||||
>>> create_state_space_tree(sequence, current_subsequence, 0)
|
|
||||||
[]
|
|
||||||
[4]
|
|
||||||
[3]
|
|
||||||
[3, 4]
|
|
||||||
[2]
|
|
||||||
[2, 4]
|
|
||||||
[2, 3]
|
|
||||||
[2, 3, 4]
|
|
||||||
[1]
|
|
||||||
[1, 4]
|
|
||||||
[1, 3]
|
|
||||||
[1, 3, 4]
|
|
||||||
[1, 2]
|
|
||||||
[1, 2, 4]
|
|
||||||
[1, 2, 3]
|
|
||||||
[1, 2, 3, 4]
|
|
||||||
"""
|
|
||||||
|
|
||||||
if index == len(sequence):
|
|
||||||
print(current_subsequence)
|
|
||||||
return
|
|
||||||
|
|
||||||
create_state_space_tree(sequence, current_subsequence, index + 1)
|
|
||||||
current_subsequence.append(sequence[index])
|
|
||||||
create_state_space_tree(sequence, current_subsequence, index + 1)
|
|
||||||
current_subsequence.pop()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
seq: list[Any] = [1, 2, 3]
|
|
||||||
generate_all_subsequences(seq)
|
|
||||||
|
|
||||||
seq.clear()
|
|
||||||
seq.extend(["A", "B", "C"])
|
|
||||||
generate_all_subsequences(seq)
|
|
|
@ -1,113 +0,0 @@
|
||||||
"""
|
|
||||||
Graph Coloring also called "m coloring problem"
|
|
||||||
consists of coloring a given graph with at most m colors
|
|
||||||
such that no adjacent vertices are assigned the same color
|
|
||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Graph_coloring
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def valid_coloring(
|
|
||||||
neighbours: list[int], colored_vertices: list[int], color: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
For each neighbour check if the coloring constraint is satisfied
|
|
||||||
If any of the neighbours fail the constraint return False
|
|
||||||
If all neighbours validate the constraint return True
|
|
||||||
|
|
||||||
>>> neighbours = [0,1,0,1,0]
|
|
||||||
>>> colored_vertices = [0, 2, 1, 2, 0]
|
|
||||||
|
|
||||||
>>> color = 1
|
|
||||||
>>> valid_coloring(neighbours, colored_vertices, color)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> color = 2
|
|
||||||
>>> valid_coloring(neighbours, colored_vertices, color)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
# Does any neighbour not satisfy the constraints
|
|
||||||
return not any(
|
|
||||||
neighbour == 1 and colored_vertices[i] == color
|
|
||||||
for i, neighbour in enumerate(neighbours)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def util_color(
|
|
||||||
graph: list[list[int]], max_colors: int, colored_vertices: list[int], index: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Pseudo-Code
|
|
||||||
|
|
||||||
Base Case:
|
|
||||||
1. Check if coloring is complete
|
|
||||||
1.1 If complete return True (meaning that we successfully colored the graph)
|
|
||||||
|
|
||||||
Recursive Step:
|
|
||||||
2. Iterates over each color:
|
|
||||||
Check if the current coloring is valid:
|
|
||||||
2.1. Color given vertex
|
|
||||||
2.2. Do recursive call, check if this coloring leads to a solution
|
|
||||||
2.4. if current coloring leads to a solution return
|
|
||||||
2.5. Uncolor given vertex
|
|
||||||
|
|
||||||
>>> graph = [[0, 1, 0, 0, 0],
|
|
||||||
... [1, 0, 1, 0, 1],
|
|
||||||
... [0, 1, 0, 1, 0],
|
|
||||||
... [0, 1, 1, 0, 0],
|
|
||||||
... [0, 1, 0, 0, 0]]
|
|
||||||
>>> max_colors = 3
|
|
||||||
>>> colored_vertices = [0, 1, 0, 0, 0]
|
|
||||||
>>> index = 3
|
|
||||||
|
|
||||||
>>> util_color(graph, max_colors, colored_vertices, index)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> max_colors = 2
|
|
||||||
>>> util_color(graph, max_colors, colored_vertices, index)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Base Case
|
|
||||||
if index == len(graph):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Recursive Step
|
|
||||||
for i in range(max_colors):
|
|
||||||
if valid_coloring(graph[index], colored_vertices, i):
|
|
||||||
# Color current vertex
|
|
||||||
colored_vertices[index] = i
|
|
||||||
# Validate coloring
|
|
||||||
if util_color(graph, max_colors, colored_vertices, index + 1):
|
|
||||||
return True
|
|
||||||
# Backtrack
|
|
||||||
colored_vertices[index] = -1
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def color(graph: list[list[int]], max_colors: int) -> list[int]:
|
|
||||||
"""
|
|
||||||
Wrapper function to call subroutine called util_color
|
|
||||||
which will either return True or False.
|
|
||||||
If True is returned colored_vertices list is filled with correct colorings
|
|
||||||
|
|
||||||
>>> graph = [[0, 1, 0, 0, 0],
|
|
||||||
... [1, 0, 1, 0, 1],
|
|
||||||
... [0, 1, 0, 1, 0],
|
|
||||||
... [0, 1, 1, 0, 0],
|
|
||||||
... [0, 1, 0, 0, 0]]
|
|
||||||
|
|
||||||
>>> max_colors = 3
|
|
||||||
>>> color(graph, max_colors)
|
|
||||||
[0, 1, 0, 2, 0]
|
|
||||||
|
|
||||||
>>> max_colors = 2
|
|
||||||
>>> color(graph, max_colors)
|
|
||||||
[]
|
|
||||||
"""
|
|
||||||
colored_vertices = [-1] * len(graph)
|
|
||||||
|
|
||||||
if util_color(graph, max_colors, colored_vertices, 0):
|
|
||||||
return colored_vertices
|
|
||||||
|
|
||||||
return []
|
|
|
@ -1,66 +0,0 @@
|
||||||
"""
|
|
||||||
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
|
|
||||||
"""
|
|
||||||
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()
|
|
|
@ -1,131 +0,0 @@
|
||||||
# https://www.geeksforgeeks.org/solve-crossword-puzzle/
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid(
|
|
||||||
puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Check if a word can be placed at the given position.
|
|
||||||
|
|
||||||
>>> puzzle = [
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', '']
|
|
||||||
... ]
|
|
||||||
>>> is_valid(puzzle, 'word', 0, 0, True)
|
|
||||||
True
|
|
||||||
>>> puzzle = [
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', '']
|
|
||||||
... ]
|
|
||||||
>>> is_valid(puzzle, 'word', 0, 0, False)
|
|
||||||
True
|
|
||||||
"""
|
|
||||||
for i in range(len(word)):
|
|
||||||
if vertical:
|
|
||||||
if row + i >= len(puzzle) or puzzle[row + i][col] != "":
|
|
||||||
return False
|
|
||||||
elif col + i >= len(puzzle[0]) or puzzle[row][col + i] != "":
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def place_word(
|
|
||||||
puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Place a word at the given position.
|
|
||||||
|
|
||||||
>>> puzzle = [
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', '']
|
|
||||||
... ]
|
|
||||||
>>> place_word(puzzle, 'word', 0, 0, True)
|
|
||||||
>>> puzzle
|
|
||||||
[['w', '', '', ''], ['o', '', '', ''], ['r', '', '', ''], ['d', '', '', '']]
|
|
||||||
"""
|
|
||||||
for i, char in enumerate(word):
|
|
||||||
if vertical:
|
|
||||||
puzzle[row + i][col] = char
|
|
||||||
else:
|
|
||||||
puzzle[row][col + i] = char
|
|
||||||
|
|
||||||
|
|
||||||
def remove_word(
|
|
||||||
puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Remove a word from the given position.
|
|
||||||
|
|
||||||
>>> puzzle = [
|
|
||||||
... ['w', '', '', ''],
|
|
||||||
... ['o', '', '', ''],
|
|
||||||
... ['r', '', '', ''],
|
|
||||||
... ['d', '', '', '']
|
|
||||||
... ]
|
|
||||||
>>> remove_word(puzzle, 'word', 0, 0, True)
|
|
||||||
>>> puzzle
|
|
||||||
[['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']]
|
|
||||||
"""
|
|
||||||
for i in range(len(word)):
|
|
||||||
if vertical:
|
|
||||||
puzzle[row + i][col] = ""
|
|
||||||
else:
|
|
||||||
puzzle[row][col + i] = ""
|
|
||||||
|
|
||||||
|
|
||||||
def solve_crossword(puzzle: list[list[str]], words: list[str]) -> bool:
|
|
||||||
"""
|
|
||||||
Solve the crossword puzzle using backtracking.
|
|
||||||
|
|
||||||
>>> puzzle = [
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', '']
|
|
||||||
... ]
|
|
||||||
|
|
||||||
>>> words = ['word', 'four', 'more', 'last']
|
|
||||||
>>> solve_crossword(puzzle, words)
|
|
||||||
True
|
|
||||||
>>> puzzle = [
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', ''],
|
|
||||||
... ['', '', '', '']
|
|
||||||
... ]
|
|
||||||
>>> words = ['word', 'four', 'more', 'paragraphs']
|
|
||||||
>>> solve_crossword(puzzle, words)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
for row in range(len(puzzle)):
|
|
||||||
for col in range(len(puzzle[0])):
|
|
||||||
if puzzle[row][col] == "":
|
|
||||||
for word in words:
|
|
||||||
for vertical in [True, False]:
|
|
||||||
if is_valid(puzzle, word, row, col, vertical):
|
|
||||||
place_word(puzzle, word, row, col, vertical)
|
|
||||||
words.remove(word)
|
|
||||||
if solve_crossword(puzzle, words):
|
|
||||||
return True
|
|
||||||
words.append(word)
|
|
||||||
remove_word(puzzle, word, row, col, vertical)
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
PUZZLE = [[""] * 3 for _ in range(3)]
|
|
||||||
WORDS = ["cat", "dog", "car"]
|
|
||||||
|
|
||||||
if solve_crossword(PUZZLE, WORDS):
|
|
||||||
print("Solution found:")
|
|
||||||
for row in PUZZLE:
|
|
||||||
print(" ".join(row))
|
|
||||||
else:
|
|
||||||
print("No solution found:")
|
|
|
@ -1,77 +0,0 @@
|
||||||
"""
|
|
||||||
author: Aayush Soni
|
|
||||||
Given n pairs of parentheses, write a function to generate all
|
|
||||||
combinations of well-formed parentheses.
|
|
||||||
Input: n = 2
|
|
||||||
Output: ["(())","()()"]
|
|
||||||
Leetcode link: https://leetcode.com/problems/generate-parentheses/description/
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def backtrack(
|
|
||||||
partial: str, open_count: int, close_count: int, n: int, result: list[str]
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Generate valid combinations of balanced parentheses using recursion.
|
|
||||||
|
|
||||||
:param partial: A string representing the current combination.
|
|
||||||
:param open_count: An integer representing the count of open parentheses.
|
|
||||||
:param close_count: An integer representing the count of close parentheses.
|
|
||||||
:param n: An integer representing the total number of pairs.
|
|
||||||
:param result: A list to store valid combinations.
|
|
||||||
:return: None
|
|
||||||
|
|
||||||
This function uses recursion to explore all possible combinations,
|
|
||||||
ensuring that at each step, the parentheses remain balanced.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> result = []
|
|
||||||
>>> backtrack("", 0, 0, 2, result)
|
|
||||||
>>> result
|
|
||||||
['(())', '()()']
|
|
||||||
"""
|
|
||||||
if len(partial) == 2 * n:
|
|
||||||
# When the combination is complete, add it to the result.
|
|
||||||
result.append(partial)
|
|
||||||
return
|
|
||||||
|
|
||||||
if open_count < n:
|
|
||||||
# If we can add an open parenthesis, do so, and recurse.
|
|
||||||
backtrack(partial + "(", open_count + 1, close_count, n, result)
|
|
||||||
|
|
||||||
if close_count < open_count:
|
|
||||||
# If we can add a close parenthesis (it won't make the combination invalid),
|
|
||||||
# do so, and recurse.
|
|
||||||
backtrack(partial + ")", open_count, close_count + 1, n, result)
|
|
||||||
|
|
||||||
|
|
||||||
def generate_parenthesis(n: int) -> list[str]:
|
|
||||||
"""
|
|
||||||
Generate valid combinations of balanced parentheses for a given n.
|
|
||||||
|
|
||||||
:param n: An integer representing the number of pairs of parentheses.
|
|
||||||
:return: A list of strings with valid combinations.
|
|
||||||
|
|
||||||
This function uses a recursive approach to generate the combinations.
|
|
||||||
|
|
||||||
Time Complexity: O(2^(2n)) - In the worst case, we have 2^(2n) combinations.
|
|
||||||
Space Complexity: O(n) - where 'n' is the number of pairs.
|
|
||||||
|
|
||||||
Example 1:
|
|
||||||
>>> generate_parenthesis(3)
|
|
||||||
['((()))', '(()())', '(())()', '()(())', '()()()']
|
|
||||||
|
|
||||||
Example 2:
|
|
||||||
>>> generate_parenthesis(1)
|
|
||||||
['()']
|
|
||||||
"""
|
|
||||||
|
|
||||||
result: list[str] = []
|
|
||||||
backtrack("", 0, 0, n, result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,176 +0,0 @@
|
||||||
"""
|
|
||||||
A Hamiltonian cycle (Hamiltonian circuit) is a graph cycle
|
|
||||||
through a graph that visits each node exactly once.
|
|
||||||
Determining whether such paths and cycles exist in graphs
|
|
||||||
is the 'Hamiltonian path problem', which is NP-complete.
|
|
||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Hamiltonian_path
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def valid_connection(
|
|
||||||
graph: list[list[int]], next_ver: int, curr_ind: int, path: list[int]
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Checks whether it is possible to add next into path by validating 2 statements
|
|
||||||
1. There should be path between current and next vertex
|
|
||||||
2. Next vertex should not be in path
|
|
||||||
If both validations succeed we return True, saying that it is possible to connect
|
|
||||||
this vertices, otherwise we return False
|
|
||||||
|
|
||||||
Case 1:Use exact graph as in main function, with initialized values
|
|
||||||
>>> graph = [[0, 1, 0, 1, 0],
|
|
||||||
... [1, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 0, 1],
|
|
||||||
... [1, 1, 0, 0, 1],
|
|
||||||
... [0, 1, 1, 1, 0]]
|
|
||||||
>>> path = [0, -1, -1, -1, -1, 0]
|
|
||||||
>>> curr_ind = 1
|
|
||||||
>>> next_ver = 1
|
|
||||||
>>> valid_connection(graph, next_ver, curr_ind, path)
|
|
||||||
True
|
|
||||||
|
|
||||||
Case 2: Same graph, but trying to connect to node that is already in path
|
|
||||||
>>> path = [0, 1, 2, 4, -1, 0]
|
|
||||||
>>> curr_ind = 4
|
|
||||||
>>> next_ver = 1
|
|
||||||
>>> valid_connection(graph, next_ver, curr_ind, path)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 1. Validate that path exists between current and next vertices
|
|
||||||
if graph[path[curr_ind - 1]][next_ver] == 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 2. Validate that next vertex is not already in path
|
|
||||||
return not any(vertex == next_ver for vertex in path)
|
|
||||||
|
|
||||||
|
|
||||||
def util_hamilton_cycle(graph: list[list[int]], path: list[int], curr_ind: int) -> bool:
|
|
||||||
"""
|
|
||||||
Pseudo-Code
|
|
||||||
Base Case:
|
|
||||||
1. Check if we visited all of vertices
|
|
||||||
1.1 If last visited vertex has path to starting vertex return True either
|
|
||||||
return False
|
|
||||||
Recursive Step:
|
|
||||||
2. Iterate over each vertex
|
|
||||||
Check if next vertex is valid for transiting from current vertex
|
|
||||||
2.1 Remember next vertex as next transition
|
|
||||||
2.2 Do recursive call and check if going to this vertex solves problem
|
|
||||||
2.3 If next vertex leads to solution return True
|
|
||||||
2.4 Else backtrack, delete remembered vertex
|
|
||||||
|
|
||||||
Case 1: Use exact graph as in main function, with initialized values
|
|
||||||
>>> graph = [[0, 1, 0, 1, 0],
|
|
||||||
... [1, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 0, 1],
|
|
||||||
... [1, 1, 0, 0, 1],
|
|
||||||
... [0, 1, 1, 1, 0]]
|
|
||||||
>>> path = [0, -1, -1, -1, -1, 0]
|
|
||||||
>>> curr_ind = 1
|
|
||||||
>>> util_hamilton_cycle(graph, path, curr_ind)
|
|
||||||
True
|
|
||||||
>>> path
|
|
||||||
[0, 1, 2, 4, 3, 0]
|
|
||||||
|
|
||||||
Case 2: Use exact graph as in previous case, but in the properties taken from
|
|
||||||
middle of calculation
|
|
||||||
>>> graph = [[0, 1, 0, 1, 0],
|
|
||||||
... [1, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 0, 1],
|
|
||||||
... [1, 1, 0, 0, 1],
|
|
||||||
... [0, 1, 1, 1, 0]]
|
|
||||||
>>> path = [0, 1, 2, -1, -1, 0]
|
|
||||||
>>> curr_ind = 3
|
|
||||||
>>> util_hamilton_cycle(graph, path, curr_ind)
|
|
||||||
True
|
|
||||||
>>> path
|
|
||||||
[0, 1, 2, 4, 3, 0]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Base Case
|
|
||||||
if curr_ind == len(graph):
|
|
||||||
# return whether path exists between current and starting vertices
|
|
||||||
return graph[path[curr_ind - 1]][path[0]] == 1
|
|
||||||
|
|
||||||
# Recursive Step
|
|
||||||
for next_ver in range(len(graph)):
|
|
||||||
if valid_connection(graph, next_ver, curr_ind, path):
|
|
||||||
# Insert current vertex into path as next transition
|
|
||||||
path[curr_ind] = next_ver
|
|
||||||
# Validate created path
|
|
||||||
if util_hamilton_cycle(graph, path, curr_ind + 1):
|
|
||||||
return True
|
|
||||||
# Backtrack
|
|
||||||
path[curr_ind] = -1
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def hamilton_cycle(graph: list[list[int]], start_index: int = 0) -> list[int]:
|
|
||||||
r"""
|
|
||||||
Wrapper function to call subroutine called util_hamilton_cycle,
|
|
||||||
which will either return array of vertices indicating hamiltonian cycle
|
|
||||||
or an empty list indicating that hamiltonian cycle was not found.
|
|
||||||
Case 1:
|
|
||||||
Following graph consists of 5 edges.
|
|
||||||
If we look closely, we can see that there are multiple Hamiltonian cycles.
|
|
||||||
For example one result is when we iterate like:
|
|
||||||
(0)->(1)->(2)->(4)->(3)->(0)
|
|
||||||
|
|
||||||
(0)---(1)---(2)
|
|
||||||
| / \ |
|
|
||||||
| / \ |
|
|
||||||
| / \ |
|
|
||||||
|/ \|
|
|
||||||
(3)---------(4)
|
|
||||||
>>> graph = [[0, 1, 0, 1, 0],
|
|
||||||
... [1, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 0, 1],
|
|
||||||
... [1, 1, 0, 0, 1],
|
|
||||||
... [0, 1, 1, 1, 0]]
|
|
||||||
>>> hamilton_cycle(graph)
|
|
||||||
[0, 1, 2, 4, 3, 0]
|
|
||||||
|
|
||||||
Case 2:
|
|
||||||
Same Graph as it was in Case 1, changed starting index from default to 3
|
|
||||||
|
|
||||||
(0)---(1)---(2)
|
|
||||||
| / \ |
|
|
||||||
| / \ |
|
|
||||||
| / \ |
|
|
||||||
|/ \|
|
|
||||||
(3)---------(4)
|
|
||||||
>>> graph = [[0, 1, 0, 1, 0],
|
|
||||||
... [1, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 0, 1],
|
|
||||||
... [1, 1, 0, 0, 1],
|
|
||||||
... [0, 1, 1, 1, 0]]
|
|
||||||
>>> hamilton_cycle(graph, 3)
|
|
||||||
[3, 0, 1, 2, 4, 3]
|
|
||||||
|
|
||||||
Case 3:
|
|
||||||
Following Graph is exactly what it was before, but edge 3-4 is removed.
|
|
||||||
Result is that there is no Hamiltonian Cycle anymore.
|
|
||||||
|
|
||||||
(0)---(1)---(2)
|
|
||||||
| / \ |
|
|
||||||
| / \ |
|
|
||||||
| / \ |
|
|
||||||
|/ \|
|
|
||||||
(3) (4)
|
|
||||||
>>> graph = [[0, 1, 0, 1, 0],
|
|
||||||
... [1, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 0, 1],
|
|
||||||
... [1, 1, 0, 0, 0],
|
|
||||||
... [0, 1, 1, 0, 0]]
|
|
||||||
>>> hamilton_cycle(graph,4)
|
|
||||||
[]
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Initialize path with -1, indicating that we have not visited them yet
|
|
||||||
path = [-1] * (len(graph) + 1)
|
|
||||||
# initialize start and end of path with starting index
|
|
||||||
path[0] = path[-1] = start_index
|
|
||||||
# evaluate and if we find answer return path either return empty array
|
|
||||||
return path if util_hamilton_cycle(graph, path, 1) else []
|
|
|
@ -1,101 +0,0 @@
|
||||||
# Knight Tour Intro: https://www.youtube.com/watch?v=ab_dY3dZFHM
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def get_valid_pos(position: tuple[int, int], n: int) -> list[tuple[int, int]]:
|
|
||||||
"""
|
|
||||||
Find all the valid positions a knight can move to from the current position.
|
|
||||||
|
|
||||||
>>> get_valid_pos((1, 3), 4)
|
|
||||||
[(2, 1), (0, 1), (3, 2)]
|
|
||||||
"""
|
|
||||||
|
|
||||||
y, x = position
|
|
||||||
positions = [
|
|
||||||
(y + 1, x + 2),
|
|
||||||
(y - 1, x + 2),
|
|
||||||
(y + 1, x - 2),
|
|
||||||
(y - 1, x - 2),
|
|
||||||
(y + 2, x + 1),
|
|
||||||
(y + 2, x - 1),
|
|
||||||
(y - 2, x + 1),
|
|
||||||
(y - 2, x - 1),
|
|
||||||
]
|
|
||||||
permissible_positions = []
|
|
||||||
|
|
||||||
for inner_position in positions:
|
|
||||||
y_test, x_test = inner_position
|
|
||||||
if 0 <= y_test < n and 0 <= x_test < n:
|
|
||||||
permissible_positions.append(inner_position)
|
|
||||||
|
|
||||||
return permissible_positions
|
|
||||||
|
|
||||||
|
|
||||||
def is_complete(board: list[list[int]]) -> bool:
|
|
||||||
"""
|
|
||||||
Check if the board (matrix) has been completely filled with non-zero values.
|
|
||||||
|
|
||||||
>>> is_complete([[1]])
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> is_complete([[1, 2], [3, 0]])
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
return not any(elem == 0 for row in board for elem in row)
|
|
||||||
|
|
||||||
|
|
||||||
def open_knight_tour_helper(
|
|
||||||
board: list[list[int]], pos: tuple[int, int], curr: int
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
Helper function to solve knight tour problem.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if is_complete(board):
|
|
||||||
return True
|
|
||||||
|
|
||||||
for position in get_valid_pos(pos, len(board)):
|
|
||||||
y, x = position
|
|
||||||
|
|
||||||
if board[y][x] == 0:
|
|
||||||
board[y][x] = curr + 1
|
|
||||||
if open_knight_tour_helper(board, position, curr + 1):
|
|
||||||
return True
|
|
||||||
board[y][x] = 0
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def open_knight_tour(n: int) -> list[list[int]]:
|
|
||||||
"""
|
|
||||||
Find the solution for the knight tour problem for a board of size n. Raises
|
|
||||||
ValueError if the tour cannot be performed for the given size.
|
|
||||||
|
|
||||||
>>> open_knight_tour(1)
|
|
||||||
[[1]]
|
|
||||||
|
|
||||||
>>> open_knight_tour(2)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Open Knight Tour cannot be performed on a board of size 2
|
|
||||||
"""
|
|
||||||
|
|
||||||
board = [[0 for i in range(n)] for j in range(n)]
|
|
||||||
|
|
||||||
for i in range(n):
|
|
||||||
for j in range(n):
|
|
||||||
board[i][j] = 1
|
|
||||||
if open_knight_tour_helper(board, (i, j), 1):
|
|
||||||
return board
|
|
||||||
board[i][j] = 0
|
|
||||||
|
|
||||||
msg = f"Open Knight Tour cannot be performed on a board of size {n}"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,61 +0,0 @@
|
||||||
def match_word_pattern(pattern: str, input_string: str) -> bool:
|
|
||||||
"""
|
|
||||||
Determine if a given pattern matches a string using backtracking.
|
|
||||||
|
|
||||||
pattern: The pattern to match.
|
|
||||||
input_string: The string to match against the pattern.
|
|
||||||
return: True if the pattern matches the string, False otherwise.
|
|
||||||
|
|
||||||
>>> match_word_pattern("aba", "GraphTreesGraph")
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> match_word_pattern("xyx", "PythonRubyPython")
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> match_word_pattern("GG", "PythonJavaPython")
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
def backtrack(pattern_index: int, str_index: int) -> bool:
|
|
||||||
"""
|
|
||||||
>>> backtrack(0, 0)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> backtrack(0, 1)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> backtrack(0, 4)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
if pattern_index == len(pattern) and str_index == len(input_string):
|
|
||||||
return True
|
|
||||||
if pattern_index == len(pattern) or str_index == len(input_string):
|
|
||||||
return False
|
|
||||||
char = pattern[pattern_index]
|
|
||||||
if char in pattern_map:
|
|
||||||
mapped_str = pattern_map[char]
|
|
||||||
if input_string.startswith(mapped_str, str_index):
|
|
||||||
return backtrack(pattern_index + 1, str_index + len(mapped_str))
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
for end in range(str_index + 1, len(input_string) + 1):
|
|
||||||
substr = input_string[str_index:end]
|
|
||||||
if substr in str_map:
|
|
||||||
continue
|
|
||||||
pattern_map[char] = substr
|
|
||||||
str_map[substr] = char
|
|
||||||
if backtrack(pattern_index + 1, end):
|
|
||||||
return True
|
|
||||||
del pattern_map[char]
|
|
||||||
del str_map[substr]
|
|
||||||
return False
|
|
||||||
|
|
||||||
pattern_map: dict[str, str] = {}
|
|
||||||
str_map: dict[str, str] = {}
|
|
||||||
return backtrack(0, 0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,95 +0,0 @@
|
||||||
"""
|
|
||||||
Minimax helps to achieve maximum score in a game by checking all possible moves
|
|
||||||
depth is current depth in game tree.
|
|
||||||
|
|
||||||
nodeIndex is index of current node in scores[].
|
|
||||||
if move is of maximizer return true else false
|
|
||||||
leaves of game tree is stored in scores[]
|
|
||||||
height is maximum height of Game tree
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import math
|
|
||||||
|
|
||||||
|
|
||||||
def minimax(
|
|
||||||
depth: int, node_index: int, is_max: bool, scores: list[int], height: float
|
|
||||||
) -> int:
|
|
||||||
"""
|
|
||||||
This function implements the minimax algorithm, which helps achieve the optimal
|
|
||||||
score for a player in a two-player game by checking all possible moves.
|
|
||||||
If the player is the maximizer, then the score is maximized.
|
|
||||||
If the player is the minimizer, then the score is minimized.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
- depth: Current depth in the game tree.
|
|
||||||
- node_index: Index of the current node in the scores list.
|
|
||||||
- is_max: A boolean indicating whether the current move
|
|
||||||
is for the maximizer (True) or minimizer (False).
|
|
||||||
- scores: A list containing the scores of the leaves of the game tree.
|
|
||||||
- height: The maximum height of the game tree.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
- An integer representing the optimal score for the current player.
|
|
||||||
|
|
||||||
>>> import math
|
|
||||||
>>> 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 len(scores) == 0:
|
|
||||||
raise ValueError("Scores cannot be empty")
|
|
||||||
|
|
||||||
# Base case: If the current depth equals the height of the tree,
|
|
||||||
# return the score of the current node.
|
|
||||||
if depth == height:
|
|
||||||
return scores[node_index]
|
|
||||||
|
|
||||||
# If it's the maximizer's turn, choose the maximum score
|
|
||||||
# between the two possible moves.
|
|
||||||
if is_max:
|
|
||||||
return max(
|
|
||||||
minimax(depth + 1, node_index * 2, False, scores, height),
|
|
||||||
minimax(depth + 1, node_index * 2 + 1, False, scores, height),
|
|
||||||
)
|
|
||||||
|
|
||||||
# If it's the minimizer's turn, choose the minimum score
|
|
||||||
# between the two possible moves.
|
|
||||||
return min(
|
|
||||||
minimax(depth + 1, node_index * 2, True, scores, height),
|
|
||||||
minimax(depth + 1, node_index * 2 + 1, True, scores, height),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
# Sample scores and height calculation
|
|
||||||
scores = [90, 23, 6, 33, 21, 65, 123, 34423]
|
|
||||||
height = math.log(len(scores), 2)
|
|
||||||
|
|
||||||
# Calculate and print the optimal value using the minimax algorithm
|
|
||||||
print("Optimal value : ", end="")
|
|
||||||
print(minimax(0, 0, True, scores, height))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
||||||
main()
|
|
|
@ -1,94 +0,0 @@
|
||||||
"""
|
|
||||||
|
|
||||||
The nqueens problem is of placing N queens on a N * N
|
|
||||||
chess board such that no queen can attack any other queens placed
|
|
||||||
on that chess board.
|
|
||||||
This means that one queen cannot have any other queen on its horizontal, vertical and
|
|
||||||
diagonal lines.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
solution = []
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
considering the current state of the board.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
board (2D matrix): The chessboard
|
|
||||||
row, column: Coordinates of the cell on the board
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Boolean Value
|
|
||||||
|
|
||||||
>>> is_safe([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
|
|
||||||
True
|
|
||||||
>>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
n = len(board) # Size of the board
|
|
||||||
|
|
||||||
# Check if there is any queen in the same row, column,
|
|
||||||
# left upper diagonal, and right upper diagonal
|
|
||||||
return (
|
|
||||||
all(board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, n)))
|
|
||||||
and all(
|
|
||||||
board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, -1, -1))
|
|
||||||
)
|
|
||||||
and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, n)))
|
|
||||||
and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, -1, -1)))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def solve(board: list[list[int]], row: int) -> bool:
|
|
||||||
"""
|
|
||||||
This function creates a state space tree and calls the safe function until it
|
|
||||||
receives a False Boolean and terminates that branch and backtracks to the next
|
|
||||||
possible solution branch.
|
|
||||||
"""
|
|
||||||
if row >= len(board):
|
|
||||||
"""
|
|
||||||
If the row number exceeds N, we have a board with a successful combination
|
|
||||||
and that combination is appended to the solution list and the board is printed.
|
|
||||||
"""
|
|
||||||
solution.append(board)
|
|
||||||
printboard(board)
|
|
||||||
print()
|
|
||||||
return True
|
|
||||||
for i in range(len(board)):
|
|
||||||
"""
|
|
||||||
For every row, it iterates through each column to check if it is feasible to
|
|
||||||
place a queen there.
|
|
||||||
If all the combinations for that particular branch are successful, the board is
|
|
||||||
reinitialized for the next possible combination.
|
|
||||||
"""
|
|
||||||
if is_safe(board, row, i):
|
|
||||||
board[row][i] = 1
|
|
||||||
solve(board, row + 1)
|
|
||||||
board[row][i] = 0
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def printboard(board: list[list[int]]) -> None:
|
|
||||||
"""
|
|
||||||
Prints the boards that have a successful combination.
|
|
||||||
"""
|
|
||||||
for i in range(len(board)):
|
|
||||||
for j in range(len(board)):
|
|
||||||
if board[i][j] == 1:
|
|
||||||
print("Q", end=" ") # Queen is present
|
|
||||||
else:
|
|
||||||
print(".", end=" ") # Empty cell
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
# Number of queens (e.g., n=8 for an 8x8 board)
|
|
||||||
n = 8
|
|
||||||
board = [[0 for i in range(n)] for j in range(n)]
|
|
||||||
solve(board, 0)
|
|
||||||
print("The total number of solutions are:", len(solution))
|
|
|
@ -1,158 +0,0 @@
|
||||||
r"""
|
|
||||||
Problem:
|
|
||||||
|
|
||||||
The n queens problem is: placing N queens on a N * N chess board such that no queen
|
|
||||||
can attack any other queens placed on that chess board. This means that one queen
|
|
||||||
cannot have any other queen on its horizontal, vertical and diagonal lines.
|
|
||||||
|
|
||||||
Solution:
|
|
||||||
|
|
||||||
To solve this problem we will use simple math. First we know the queen can move in all
|
|
||||||
the possible ways, we can simplify it in this: vertical, horizontal, diagonal left and
|
|
||||||
diagonal right.
|
|
||||||
|
|
||||||
We can visualize it like this:
|
|
||||||
|
|
||||||
left diagonal = \
|
|
||||||
right diagonal = /
|
|
||||||
|
|
||||||
On a chessboard vertical movement could be the rows and horizontal movement could be
|
|
||||||
the columns.
|
|
||||||
|
|
||||||
In programming we can use an array, and in this array each index could be the rows and
|
|
||||||
each value in the array could be the column. For example:
|
|
||||||
|
|
||||||
. Q . . We have this chessboard with one queen in each column and each queen
|
|
||||||
. . . Q can't attack to each other.
|
|
||||||
Q . . . The array for this example would look like this: [1, 3, 0, 2]
|
|
||||||
. . Q .
|
|
||||||
|
|
||||||
So if we use an array and we verify that each value in the array is different to each
|
|
||||||
other we know that at least the queens can't attack each other in horizontal and
|
|
||||||
vertical.
|
|
||||||
|
|
||||||
At this point we have it halfway completed and we will treat the chessboard as a
|
|
||||||
Cartesian plane. Hereinafter we are going to remember basic math, so in the school we
|
|
||||||
learned this formula:
|
|
||||||
|
|
||||||
Slope of a line:
|
|
||||||
|
|
||||||
y2 - y1
|
|
||||||
m = ----------
|
|
||||||
x2 - x1
|
|
||||||
|
|
||||||
This formula allow us to get the slope. For the angles 45º (right diagonal) and 135º
|
|
||||||
(left diagonal) this formula gives us m = 1, and m = -1 respectively.
|
|
||||||
|
|
||||||
See::
|
|
||||||
https://www.enotes.com/homework-help/write-equation-line-that-hits-origin-45-degree-1474860
|
|
||||||
|
|
||||||
Then we have this other formula:
|
|
||||||
|
|
||||||
Slope intercept:
|
|
||||||
|
|
||||||
y = mx + b
|
|
||||||
|
|
||||||
b is where the line crosses the Y axis (to get more information see:
|
|
||||||
https://www.mathsisfun.com/y_intercept.html), if we change the formula to solve for b
|
|
||||||
we would have:
|
|
||||||
|
|
||||||
y - mx = b
|
|
||||||
|
|
||||||
And since we already have the m values for the angles 45º and 135º, this formula would
|
|
||||||
look like this:
|
|
||||||
|
|
||||||
45º: y - (1)x = b
|
|
||||||
45º: y - x = b
|
|
||||||
|
|
||||||
135º: y - (-1)x = b
|
|
||||||
135º: y + x = b
|
|
||||||
|
|
||||||
y = row
|
|
||||||
x = column
|
|
||||||
|
|
||||||
Applying these two formulas we can check if a queen in some position is being attacked
|
|
||||||
for another one or vice versa.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def depth_first_search(
|
|
||||||
possible_board: list[int],
|
|
||||||
diagonal_right_collisions: list[int],
|
|
||||||
diagonal_left_collisions: list[int],
|
|
||||||
boards: list[list[str]],
|
|
||||||
n: int,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
>>> boards = []
|
|
||||||
>>> depth_first_search([], [], [], boards, 4)
|
|
||||||
>>> for board in boards:
|
|
||||||
... print(board)
|
|
||||||
['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . ']
|
|
||||||
['. . Q . ', 'Q . . . ', '. . . Q ', '. Q . . ']
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Get next row in the current board (possible_board) to fill it with a queen
|
|
||||||
row = len(possible_board)
|
|
||||||
|
|
||||||
# If row is equal to the size of the board it means there are a queen in each row in
|
|
||||||
# the current board (possible_board)
|
|
||||||
if row == n:
|
|
||||||
# We convert the variable possible_board that looks like this: [1, 3, 0, 2] to
|
|
||||||
# this: ['. Q . . ', '. . . Q ', 'Q . . . ', '. . Q . ']
|
|
||||||
boards.append([". " * i + "Q " + ". " * (n - 1 - i) for i in possible_board])
|
|
||||||
return
|
|
||||||
|
|
||||||
# We iterate each column in the row to find all possible results in each row
|
|
||||||
for col in range(n):
|
|
||||||
# We apply that we learned previously. First we check that in the current board
|
|
||||||
# (possible_board) there are not other same value because if there is it means
|
|
||||||
# that there are a collision in vertical. Then we apply the two formulas we
|
|
||||||
# learned before:
|
|
||||||
#
|
|
||||||
# 45º: y - x = b or 45: row - col = b
|
|
||||||
# 135º: y + x = b or row + col = b.
|
|
||||||
#
|
|
||||||
# And we verify if the results of this two formulas not exist in their variables
|
|
||||||
# respectively. (diagonal_right_collisions, diagonal_left_collisions)
|
|
||||||
#
|
|
||||||
# If any or these are True it means there is a collision so we continue to the
|
|
||||||
# next value in the for loop.
|
|
||||||
if (
|
|
||||||
col in possible_board
|
|
||||||
or row - col in diagonal_right_collisions
|
|
||||||
or row + col in diagonal_left_collisions
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If it is False we call dfs function again and we update the inputs
|
|
||||||
depth_first_search(
|
|
||||||
[*possible_board, col],
|
|
||||||
[*diagonal_right_collisions, row - col],
|
|
||||||
[*diagonal_left_collisions, row + col],
|
|
||||||
boards,
|
|
||||||
n,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def n_queens_solution(n: int) -> None:
|
|
||||||
boards: list[list[str]] = []
|
|
||||||
depth_first_search([], [], [], boards, n)
|
|
||||||
|
|
||||||
# Print all the boards
|
|
||||||
for board in boards:
|
|
||||||
for column in board:
|
|
||||||
print(column)
|
|
||||||
print("")
|
|
||||||
|
|
||||||
print(len(boards), "solutions were found.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
||||||
n_queens_solution(4)
|
|
|
@ -1,91 +0,0 @@
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
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(10, 2, 1, 0, 0)
|
|
||||||
(0, 1)
|
|
||||||
>>> backtrack(10, 3, 1, 0, 0)
|
|
||||||
(0, 0)
|
|
||||||
>>> backtrack(20, 2, 1, 0, 0)
|
|
||||||
(0, 1)
|
|
||||||
>>> backtrack(15, 10, 1, 0, 0)
|
|
||||||
(0, 0)
|
|
||||||
>>> backtrack(16, 2, 1, 0, 0)
|
|
||||||
(0, 1)
|
|
||||||
>>> backtrack(20, 1, 1, 0, 0)
|
|
||||||
(0, 64)
|
|
||||||
"""
|
|
||||||
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 = 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(10, 2)
|
|
||||||
1
|
|
||||||
>>> solve(10, 3)
|
|
||||||
0
|
|
||||||
>>> solve(20, 2)
|
|
||||||
1
|
|
||||||
>>> solve(15, 10)
|
|
||||||
0
|
|
||||||
>>> solve(16, 2)
|
|
||||||
1
|
|
||||||
>>> solve(20, 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()
|
|
|
@ -1,197 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def solve_maze(
|
|
||||||
maze: list[list[int]],
|
|
||||||
source_row: int,
|
|
||||||
source_column: int,
|
|
||||||
destination_row: int,
|
|
||||||
destination_column: int,
|
|
||||||
) -> list[list[int]]:
|
|
||||||
"""
|
|
||||||
This method solves the "rat in maze" problem.
|
|
||||||
Parameters :
|
|
||||||
- maze: A two dimensional matrix of zeros and ones.
|
|
||||||
- source_row: The row index of the starting point.
|
|
||||||
- source_column: The column index of the starting point.
|
|
||||||
- destination_row: The row index of the destination point.
|
|
||||||
- destination_column: The column index of the destination point.
|
|
||||||
Returns:
|
|
||||||
- solution: A 2D matrix representing the solution path if it exists.
|
|
||||||
Raises:
|
|
||||||
- ValueError: If no solution exists or if the source or
|
|
||||||
destination coordinates are invalid.
|
|
||||||
Description:
|
|
||||||
This method navigates through a maze represented as an n by n matrix,
|
|
||||||
starting from a specified source cell and
|
|
||||||
aiming to reach a destination cell.
|
|
||||||
The maze consists of walls (1s) and open paths (0s).
|
|
||||||
By providing custom row and column values, the source and destination
|
|
||||||
cells can be adjusted.
|
|
||||||
>>> maze = [[0, 1, 0, 1, 1],
|
|
||||||
... [0, 0, 0, 0, 0],
|
|
||||||
... [1, 0, 1, 0, 1],
|
|
||||||
... [0, 0, 1, 0, 0],
|
|
||||||
... [1, 0, 0, 1, 0]]
|
|
||||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
|
||||||
[[0, 1, 1, 1, 1],
|
|
||||||
[0, 0, 0, 0, 1],
|
|
||||||
[1, 1, 1, 0, 1],
|
|
||||||
[1, 1, 1, 0, 0],
|
|
||||||
[1, 1, 1, 1, 0]]
|
|
||||||
|
|
||||||
Note:
|
|
||||||
In the output maze, the zeros (0s) represent one of the possible
|
|
||||||
paths from the source to the destination.
|
|
||||||
|
|
||||||
>>> maze = [[0, 1, 0, 1, 1],
|
|
||||||
... [0, 0, 0, 0, 0],
|
|
||||||
... [0, 0, 0, 0, 1],
|
|
||||||
... [0, 0, 0, 0, 0],
|
|
||||||
... [0, 0, 0, 0, 0]]
|
|
||||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
|
||||||
[[0, 1, 1, 1, 1],
|
|
||||||
[0, 1, 1, 1, 1],
|
|
||||||
[0, 1, 1, 1, 1],
|
|
||||||
[0, 1, 1, 1, 1],
|
|
||||||
[0, 0, 0, 0, 0]]
|
|
||||||
|
|
||||||
>>> maze = [[0, 0, 0],
|
|
||||||
... [0, 1, 0],
|
|
||||||
... [1, 0, 0]]
|
|
||||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
|
||||||
[[0, 0, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[1, 1, 0]]
|
|
||||||
|
|
||||||
>>> maze = [[1, 0, 0],
|
|
||||||
... [0, 1, 0],
|
|
||||||
... [1, 0, 0]]
|
|
||||||
>>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
|
||||||
[[1, 0, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[1, 1, 0]]
|
|
||||||
|
|
||||||
>>> maze = [[1, 1, 0, 0, 1, 0, 0, 1],
|
|
||||||
... [1, 0, 1, 0, 0, 1, 1, 1],
|
|
||||||
... [0, 1, 0, 1, 0, 0, 1, 0],
|
|
||||||
... [1, 1, 1, 0, 0, 1, 0, 1],
|
|
||||||
... [0, 1, 0, 0, 1, 0, 1, 1],
|
|
||||||
... [0, 0, 0, 1, 1, 1, 0, 1],
|
|
||||||
... [0, 1, 0, 1, 0, 1, 1, 1],
|
|
||||||
... [1, 1, 0, 0, 0, 0, 0, 1]]
|
|
||||||
>>> solve_maze(maze,0,2,len(maze)-1,2) # doctest: +NORMALIZE_WHITESPACE
|
|
||||||
[[1, 1, 0, 0, 1, 1, 1, 1],
|
|
||||||
[1, 1, 1, 0, 0, 1, 1, 1],
|
|
||||||
[1, 1, 1, 1, 0, 1, 1, 1],
|
|
||||||
[1, 1, 1, 0, 0, 1, 1, 1],
|
|
||||||
[1, 1, 0, 0, 1, 1, 1, 1],
|
|
||||||
[1, 1, 0, 1, 1, 1, 1, 1],
|
|
||||||
[1, 1, 0, 1, 1, 1, 1, 1],
|
|
||||||
[1, 1, 0, 1, 1, 1, 1, 1]]
|
|
||||||
>>> maze = [[1, 0, 0],
|
|
||||||
... [0, 1, 1],
|
|
||||||
... [1, 0, 1]]
|
|
||||||
>>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: No solution exists!
|
|
||||||
|
|
||||||
>>> maze = [[0, 0],
|
|
||||||
... [1, 1]]
|
|
||||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: No solution exists!
|
|
||||||
|
|
||||||
>>> maze = [[0, 1],
|
|
||||||
... [1, 0]]
|
|
||||||
>>> solve_maze(maze,2,0,len(maze)-1,len(maze)-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Invalid source or destination coordinates
|
|
||||||
|
|
||||||
>>> maze = [[1, 0, 0],
|
|
||||||
... [0, 1, 0],
|
|
||||||
... [1, 0, 0]]
|
|
||||||
>>> solve_maze(maze,0,1,len(maze),len(maze)-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Invalid source or destination coordinates
|
|
||||||
"""
|
|
||||||
size = len(maze)
|
|
||||||
# Check if source and destination coordinates are Invalid.
|
|
||||||
if not (0 <= source_row <= size - 1 and 0 <= source_column <= size - 1) or (
|
|
||||||
not (0 <= destination_row <= size - 1 and 0 <= destination_column <= size - 1)
|
|
||||||
):
|
|
||||||
raise ValueError("Invalid source or destination coordinates")
|
|
||||||
# We need to create solution object to save path.
|
|
||||||
solutions = [[1 for _ in range(size)] for _ in range(size)]
|
|
||||||
solved = run_maze(
|
|
||||||
maze, source_row, source_column, destination_row, destination_column, solutions
|
|
||||||
)
|
|
||||||
if solved:
|
|
||||||
return solutions
|
|
||||||
else:
|
|
||||||
raise ValueError("No solution exists!")
|
|
||||||
|
|
||||||
|
|
||||||
def run_maze(
|
|
||||||
maze: list[list[int]],
|
|
||||||
i: int,
|
|
||||||
j: int,
|
|
||||||
destination_row: int,
|
|
||||||
destination_column: int,
|
|
||||||
solutions: list[list[int]],
|
|
||||||
) -> bool:
|
|
||||||
"""
|
|
||||||
This method is recursive starting from (i, j) and going in one of four directions:
|
|
||||||
up, down, left, right.
|
|
||||||
If a path is found to destination it returns True otherwise it returns False.
|
|
||||||
Parameters
|
|
||||||
maze: A two dimensional matrix of zeros and ones.
|
|
||||||
i, j : coordinates of matrix
|
|
||||||
solutions: A two dimensional matrix of solutions.
|
|
||||||
Returns:
|
|
||||||
Boolean if path is found True, Otherwise False.
|
|
||||||
"""
|
|
||||||
size = len(maze)
|
|
||||||
# Final check point.
|
|
||||||
if i == destination_row and j == destination_column and maze[i][j] == 0:
|
|
||||||
solutions[i][j] = 0
|
|
||||||
return True
|
|
||||||
|
|
||||||
lower_flag = (not i < 0) and (not j < 0) # Check lower bounds
|
|
||||||
upper_flag = (i < size) and (j < size) # Check upper bounds
|
|
||||||
|
|
||||||
if lower_flag and upper_flag:
|
|
||||||
# check for already visited and block points.
|
|
||||||
block_flag = (solutions[i][j]) and (not maze[i][j])
|
|
||||||
if block_flag:
|
|
||||||
# check visited
|
|
||||||
solutions[i][j] = 0
|
|
||||||
|
|
||||||
# check for directions
|
|
||||||
if (
|
|
||||||
run_maze(maze, i + 1, j, destination_row, destination_column, solutions)
|
|
||||||
or run_maze(
|
|
||||||
maze, i, j + 1, destination_row, destination_column, solutions
|
|
||||||
)
|
|
||||||
or run_maze(
|
|
||||||
maze, i - 1, j, destination_row, destination_column, solutions
|
|
||||||
)
|
|
||||||
or run_maze(
|
|
||||||
maze, i, j - 1, destination_row, destination_column, solutions
|
|
||||||
)
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
solutions[i][j] = 1
|
|
||||||
return False
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
|
|
|
@ -1,133 +0,0 @@
|
||||||
"""
|
|
||||||
Given a partially filled 9x9 2D array, the objective is to fill a 9x9
|
|
||||||
square grid with digits numbered 1 to 9, so that every row, column, and
|
|
||||||
and each of the nine 3x3 sub-grids contains all of the digits.
|
|
||||||
|
|
||||||
This can be solved using Backtracking and is similar to n-queens.
|
|
||||||
We check to see if a cell is safe or not and recursively call the
|
|
||||||
function on the next column to see if it returns True. if yes, we
|
|
||||||
have solved the puzzle. else, we backtrack and place another number
|
|
||||||
in that cell and repeat this process.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
Matrix = list[list[int]]
|
|
||||||
|
|
||||||
# assigning initial values to the grid
|
|
||||||
initial_grid: Matrix = [
|
|
||||||
[3, 0, 6, 5, 0, 8, 4, 0, 0],
|
|
||||||
[5, 2, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 8, 7, 0, 0, 0, 0, 3, 1],
|
|
||||||
[0, 0, 3, 0, 1, 0, 0, 8, 0],
|
|
||||||
[9, 0, 0, 8, 6, 3, 0, 0, 5],
|
|
||||||
[0, 5, 0, 0, 9, 0, 6, 0, 0],
|
|
||||||
[1, 3, 0, 0, 0, 0, 2, 5, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 7, 4],
|
|
||||||
[0, 0, 5, 2, 0, 6, 3, 0, 0],
|
|
||||||
]
|
|
||||||
|
|
||||||
# a grid with no solution
|
|
||||||
no_solution: Matrix = [
|
|
||||||
[5, 0, 6, 5, 0, 8, 4, 0, 3],
|
|
||||||
[5, 2, 0, 0, 0, 0, 0, 0, 2],
|
|
||||||
[1, 8, 7, 0, 0, 0, 0, 3, 1],
|
|
||||||
[0, 0, 3, 0, 1, 0, 0, 8, 0],
|
|
||||||
[9, 0, 0, 8, 6, 3, 0, 0, 5],
|
|
||||||
[0, 5, 0, 0, 9, 0, 6, 0, 0],
|
|
||||||
[1, 3, 0, 0, 0, 0, 2, 5, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 7, 4],
|
|
||||||
[0, 0, 5, 2, 0, 6, 3, 0, 0],
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def is_safe(grid: Matrix, row: int, column: int, n: int) -> bool:
|
|
||||||
"""
|
|
||||||
This function checks the grid to see if each row,
|
|
||||||
column, and the 3x3 subgrids contain the digit 'n'.
|
|
||||||
It returns False if it is not 'safe' (a duplicate digit
|
|
||||||
is found) else returns True if it is 'safe'
|
|
||||||
"""
|
|
||||||
for i in range(9):
|
|
||||||
if n in {grid[row][i], grid[i][column]}:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for i in range(3):
|
|
||||||
for j in range(3):
|
|
||||||
if grid[(row - row % 3) + i][(column - column % 3) + j] == n:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def find_empty_location(grid: Matrix) -> tuple[int, int] | None:
|
|
||||||
"""
|
|
||||||
This function finds an empty location so that we can assign a number
|
|
||||||
for that particular row and column.
|
|
||||||
"""
|
|
||||||
for i in range(9):
|
|
||||||
for j in range(9):
|
|
||||||
if grid[i][j] == 0:
|
|
||||||
return i, j
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def sudoku(grid: Matrix) -> Matrix | None:
|
|
||||||
"""
|
|
||||||
Takes a partially filled-in grid and attempts to assign values to
|
|
||||||
all unassigned locations in such a way to meet the requirements
|
|
||||||
for Sudoku solution (non-duplication across rows, columns, and boxes)
|
|
||||||
|
|
||||||
>>> sudoku(initial_grid) # doctest: +NORMALIZE_WHITESPACE
|
|
||||||
[[3, 1, 6, 5, 7, 8, 4, 9, 2],
|
|
||||||
[5, 2, 9, 1, 3, 4, 7, 6, 8],
|
|
||||||
[4, 8, 7, 6, 2, 9, 5, 3, 1],
|
|
||||||
[2, 6, 3, 4, 1, 5, 9, 8, 7],
|
|
||||||
[9, 7, 4, 8, 6, 3, 1, 2, 5],
|
|
||||||
[8, 5, 1, 7, 9, 2, 6, 4, 3],
|
|
||||||
[1, 3, 8, 9, 4, 7, 2, 5, 6],
|
|
||||||
[6, 9, 2, 3, 5, 1, 8, 7, 4],
|
|
||||||
[7, 4, 5, 2, 8, 6, 3, 1, 9]]
|
|
||||||
>>> sudoku(no_solution) is None
|
|
||||||
True
|
|
||||||
"""
|
|
||||||
if location := find_empty_location(grid):
|
|
||||||
row, column = location
|
|
||||||
else:
|
|
||||||
# If the location is ``None``, then the grid is solved.
|
|
||||||
return grid
|
|
||||||
|
|
||||||
for digit in range(1, 10):
|
|
||||||
if is_safe(grid, row, column, digit):
|
|
||||||
grid[row][column] = digit
|
|
||||||
|
|
||||||
if sudoku(grid) is not None:
|
|
||||||
return grid
|
|
||||||
|
|
||||||
grid[row][column] = 0
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def print_solution(grid: Matrix) -> None:
|
|
||||||
"""
|
|
||||||
A function to print the solution in the form
|
|
||||||
of a 9x9 grid
|
|
||||||
"""
|
|
||||||
for row in grid:
|
|
||||||
for cell in row:
|
|
||||||
print(cell, end=" ")
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# make a copy of grid so that you can compare with the unmodified grid
|
|
||||||
for example_grid in (initial_grid, no_solution):
|
|
||||||
print("\nExample grid:\n" + "=" * 20)
|
|
||||||
print_solution(example_grid)
|
|
||||||
print("\nExample grid solution:")
|
|
||||||
solution = sudoku(example_grid)
|
|
||||||
if solution is not None:
|
|
||||||
print_solution(solution)
|
|
||||||
else:
|
|
||||||
print("Cannot find a solution.")
|
|
|
@ -1,66 +0,0 @@
|
||||||
"""
|
|
||||||
The sum-of-subsetsproblem states that a set of non-negative integers, and a
|
|
||||||
value M, determine all possible subsets of the given set whose summation sum
|
|
||||||
equal to given M.
|
|
||||||
|
|
||||||
Summation of the chosen numbers must be equal to given number M and one number
|
|
||||||
can be used only once.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
|
|
||||||
def generate_sum_of_subsets_soln(nums: list[int], max_sum: int) -> list[list[int]]:
|
|
||||||
result: list[list[int]] = []
|
|
||||||
path: list[int] = []
|
|
||||||
num_index = 0
|
|
||||||
remaining_nums_sum = sum(nums)
|
|
||||||
create_state_space_tree(nums, max_sum, num_index, path, result, remaining_nums_sum)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def create_state_space_tree(
|
|
||||||
nums: list[int],
|
|
||||||
max_sum: int,
|
|
||||||
num_index: int,
|
|
||||||
path: list[int],
|
|
||||||
result: list[list[int]],
|
|
||||||
remaining_nums_sum: int,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Creates a state space tree to iterate through each branch using DFS.
|
|
||||||
It terminates the branching of a node when any of the two conditions
|
|
||||||
given below satisfy.
|
|
||||||
This algorithm follows depth-fist-search and backtracks when the node is not
|
|
||||||
branchable.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if sum(path) > max_sum or (remaining_nums_sum + sum(path)) < max_sum:
|
|
||||||
return
|
|
||||||
if sum(path) == max_sum:
|
|
||||||
result.append(path)
|
|
||||||
return
|
|
||||||
for index in range(num_index, len(nums)):
|
|
||||||
create_state_space_tree(
|
|
||||||
nums,
|
|
||||||
max_sum,
|
|
||||||
index + 1,
|
|
||||||
[*path, nums[index]],
|
|
||||||
result,
|
|
||||||
remaining_nums_sum - nums[index],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
remove the comment to take an input from the user
|
|
||||||
|
|
||||||
print("Enter the elements")
|
|
||||||
nums = list(map(int, input().split()))
|
|
||||||
print("Enter max_sum sum")
|
|
||||||
max_sum = int(input())
|
|
||||||
|
|
||||||
"""
|
|
||||||
nums = [3, 34, 4, 12, 5, 2]
|
|
||||||
max_sum = 9
|
|
||||||
result = generate_sum_of_subsets_soln(nums, max_sum)
|
|
||||||
print(*result)
|
|
|
@ -1,71 +0,0 @@
|
||||||
"""
|
|
||||||
Word Break Problem is a well-known problem in computer science.
|
|
||||||
Given a string and a dictionary of words, the task is to determine if
|
|
||||||
the string can be segmented into a sequence of one or more dictionary words.
|
|
||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Word_break_problem
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def backtrack(input_string: str, word_dict: set[str], start: int) -> bool:
|
|
||||||
"""
|
|
||||||
Helper function that uses backtracking to determine if a valid
|
|
||||||
word segmentation is possible starting from index 'start'.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
input_string (str): The input string to be segmented.
|
|
||||||
word_dict (set[str]): A set of valid dictionary words.
|
|
||||||
start (int): The starting index of the substring to be checked.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if a valid segmentation is possible, otherwise False.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> backtrack("leetcode", {"leet", "code"}, 0)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> backtrack("applepenapple", {"apple", "pen"}, 0)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> backtrack("catsandog", {"cats", "dog", "sand", "and", "cat"}, 0)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Base case: if the starting index has reached the end of the string
|
|
||||||
if start == len(input_string):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Try every possible substring from 'start' to 'end'
|
|
||||||
for end in range(start + 1, len(input_string) + 1):
|
|
||||||
if input_string[start:end] in word_dict and backtrack(
|
|
||||||
input_string, word_dict, end
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def word_break(input_string: str, word_dict: set[str]) -> bool:
|
|
||||||
"""
|
|
||||||
Determines if the input string can be segmented into a sequence of
|
|
||||||
valid dictionary words using backtracking.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
input_string (str): The input string to segment.
|
|
||||||
word_dict (set[str]): The set of valid words.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if the string can be segmented into valid words, otherwise False.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> word_break("leetcode", {"leet", "code"})
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> word_break("applepenapple", {"apple", "pen"})
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> word_break("catsandog", {"cats", "dog", "sand", "and", "cat"})
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
|
|
||||||
return backtrack(input_string, word_dict, 0)
|
|
|
@ -1,100 +0,0 @@
|
||||||
"""
|
|
||||||
Word Ladder is a classic problem in computer science.
|
|
||||||
The problem is to transform a start word into an end word
|
|
||||||
by changing one letter at a time.
|
|
||||||
Each intermediate word must be a valid word from a given list of words.
|
|
||||||
The goal is to find a transformation sequence
|
|
||||||
from the start word to the end word.
|
|
||||||
|
|
||||||
Wikipedia: https://en.wikipedia.org/wiki/Word_ladder
|
|
||||||
"""
|
|
||||||
|
|
||||||
import string
|
|
||||||
|
|
||||||
|
|
||||||
def backtrack(
|
|
||||||
current_word: str, path: list[str], end_word: str, word_set: set[str]
|
|
||||||
) -> list[str]:
|
|
||||||
"""
|
|
||||||
Helper function to perform backtracking to find the transformation
|
|
||||||
from the current_word to the end_word.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
current_word (str): The current word in the transformation sequence.
|
|
||||||
path (list[str]): The list of transformations from begin_word to current_word.
|
|
||||||
end_word (str): The target word for transformation.
|
|
||||||
word_set (set[str]): The set of valid words for transformation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[str]: The list of transformations from begin_word to end_word.
|
|
||||||
Returns an empty list if there is no valid
|
|
||||||
transformation from current_word to end_word.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log", "cog"})
|
|
||||||
['hit', 'hot', 'dot', 'lot', 'log', 'cog']
|
|
||||||
|
|
||||||
>>> backtrack("hit", ["hit"], "cog", {"hot", "dot", "dog", "lot", "log"})
|
|
||||||
[]
|
|
||||||
|
|
||||||
>>> backtrack("lead", ["lead"], "gold", {"load", "goad", "gold", "lead", "lord"})
|
|
||||||
['lead', 'lead', 'load', 'goad', 'gold']
|
|
||||||
|
|
||||||
>>> backtrack("game", ["game"], "code", {"came", "cage", "code", "cade", "gave"})
|
|
||||||
['game', 'came', 'cade', 'code']
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Base case: If the current word is the end word, return the path
|
|
||||||
if current_word == end_word:
|
|
||||||
return path
|
|
||||||
|
|
||||||
# Try all possible single-letter transformations
|
|
||||||
for i in range(len(current_word)):
|
|
||||||
for c in string.ascii_lowercase: # Try changing each letter
|
|
||||||
transformed_word = current_word[:i] + c + current_word[i + 1 :]
|
|
||||||
if transformed_word in word_set:
|
|
||||||
word_set.remove(transformed_word)
|
|
||||||
# Recur with the new word added to the path
|
|
||||||
result = backtrack(
|
|
||||||
transformed_word, [*path, transformed_word], end_word, word_set
|
|
||||||
)
|
|
||||||
if result: # valid transformation found
|
|
||||||
return result
|
|
||||||
word_set.add(transformed_word) # backtrack
|
|
||||||
|
|
||||||
return [] # No valid transformation found
|
|
||||||
|
|
||||||
|
|
||||||
def word_ladder(begin_word: str, end_word: str, word_set: set[str]) -> list[str]:
|
|
||||||
"""
|
|
||||||
Solve the Word Ladder problem using Backtracking and return
|
|
||||||
the list of transformations from begin_word to end_word.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
begin_word (str): The word from which the transformation starts.
|
|
||||||
end_word (str): The target word for transformation.
|
|
||||||
word_list (list[str]): The list of valid words for transformation.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list[str]: The list of transformations from begin_word to end_word.
|
|
||||||
Returns an empty list if there is no valid transformation.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"])
|
|
||||||
['hit', 'hot', 'dot', 'lot', 'log', 'cog']
|
|
||||||
|
|
||||||
>>> word_ladder("hit", "cog", ["hot", "dot", "dog", "lot", "log"])
|
|
||||||
[]
|
|
||||||
|
|
||||||
>>> word_ladder("lead", "gold", ["load", "goad", "gold", "lead", "lord"])
|
|
||||||
['lead', 'lead', 'load', 'goad', 'gold']
|
|
||||||
|
|
||||||
>>> word_ladder("game", "code", ["came", "cage", "code", "cade", "gave"])
|
|
||||||
['game', 'came', 'cade', 'code']
|
|
||||||
"""
|
|
||||||
|
|
||||||
if end_word not in word_set: # no valid transformation possible
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Perform backtracking starting from the begin_word
|
|
||||||
return backtrack(begin_word, [begin_word], end_word, word_set)
|
|
|
@ -1,162 +0,0 @@
|
||||||
"""
|
|
||||||
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([["B", "A", "A"], ["A", "A", "A"], ["A", "B", "A"]], "ABB")
|
|
||||||
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()
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Bit manipulation
|
|
||||||
|
|
||||||
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://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>
|
|
|
@ -1,52 +0,0 @@
|
||||||
# https://www.tutorialspoint.com/python3/bitwise_operators_example.htm
|
|
||||||
|
|
||||||
|
|
||||||
def binary_and(a: int, b: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in 2 integers, convert them to binary,
|
|
||||||
return a binary number that is the
|
|
||||||
result of a binary and operation on the integers provided.
|
|
||||||
|
|
||||||
>>> binary_and(25, 32)
|
|
||||||
'0b000000'
|
|
||||||
>>> binary_and(37, 50)
|
|
||||||
'0b100000'
|
|
||||||
>>> binary_and(21, 30)
|
|
||||||
'0b10100'
|
|
||||||
>>> binary_and(58, 73)
|
|
||||||
'0b0001000'
|
|
||||||
>>> binary_and(0, 255)
|
|
||||||
'0b00000000'
|
|
||||||
>>> binary_and(256, 256)
|
|
||||||
'0b100000000'
|
|
||||||
>>> binary_and(0, -1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: the value of both inputs must be positive
|
|
||||||
>>> binary_and(0, 1.1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Unknown format code 'b' for object of type 'float'
|
|
||||||
>>> binary_and("0", "1")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
|
||||||
if a < 0 or b < 0:
|
|
||||||
raise ValueError("the value of both inputs must be positive")
|
|
||||||
|
|
||||||
a_binary = format(a, "b")
|
|
||||||
b_binary = format(b, "b")
|
|
||||||
|
|
||||||
max_len = max(len(a_binary), len(b_binary))
|
|
||||||
|
|
||||||
return "0b" + "".join(
|
|
||||||
str(int(char_a == "1" and char_b == "1"))
|
|
||||||
for char_a, char_b in zip(a_binary.zfill(max_len), b_binary.zfill(max_len))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,29 +0,0 @@
|
||||||
def binary_coded_decimal(number: int) -> str:
|
|
||||||
"""
|
|
||||||
Find binary coded decimal (bcd) of integer base 10.
|
|
||||||
Each digit of the number is represented by a 4-bit binary.
|
|
||||||
Example:
|
|
||||||
>>> binary_coded_decimal(-2)
|
|
||||||
'0b0000'
|
|
||||||
>>> binary_coded_decimal(-1)
|
|
||||||
'0b0000'
|
|
||||||
>>> binary_coded_decimal(0)
|
|
||||||
'0b0000'
|
|
||||||
>>> binary_coded_decimal(3)
|
|
||||||
'0b0011'
|
|
||||||
>>> binary_coded_decimal(2)
|
|
||||||
'0b0010'
|
|
||||||
>>> binary_coded_decimal(12)
|
|
||||||
'0b00010010'
|
|
||||||
>>> binary_coded_decimal(987)
|
|
||||||
'0b100110000111'
|
|
||||||
"""
|
|
||||||
return "0b" + "".join(
|
|
||||||
str(bin(int(digit)))[2:].zfill(4) for digit in str(max(0, number))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,41 +0,0 @@
|
||||||
def binary_count_setbits(a: int) -> int:
|
|
||||||
"""
|
|
||||||
Take in 1 integer, return a number that is
|
|
||||||
the number of 1's in binary representation of that number.
|
|
||||||
|
|
||||||
>>> binary_count_setbits(25)
|
|
||||||
3
|
|
||||||
>>> binary_count_setbits(36)
|
|
||||||
2
|
|
||||||
>>> binary_count_setbits(16)
|
|
||||||
1
|
|
||||||
>>> binary_count_setbits(58)
|
|
||||||
4
|
|
||||||
>>> binary_count_setbits(4294967295)
|
|
||||||
32
|
|
||||||
>>> binary_count_setbits(0)
|
|
||||||
0
|
|
||||||
>>> binary_count_setbits(-10)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Input value must be a positive integer
|
|
||||||
>>> binary_count_setbits(0.8)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Input value must be a 'int' type
|
|
||||||
>>> binary_count_setbits("0")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
|
||||||
if a < 0:
|
|
||||||
raise ValueError("Input value must be a positive integer")
|
|
||||||
elif isinstance(a, float):
|
|
||||||
raise TypeError("Input value must be a 'int' type")
|
|
||||||
return bin(a).count("1")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,44 +0,0 @@
|
||||||
from math import log2
|
|
||||||
|
|
||||||
|
|
||||||
def binary_count_trailing_zeros(a: int) -> int:
|
|
||||||
"""
|
|
||||||
Take in 1 integer, return a number that is
|
|
||||||
the number of trailing zeros in binary representation of that number.
|
|
||||||
|
|
||||||
>>> binary_count_trailing_zeros(25)
|
|
||||||
0
|
|
||||||
>>> binary_count_trailing_zeros(36)
|
|
||||||
2
|
|
||||||
>>> binary_count_trailing_zeros(16)
|
|
||||||
4
|
|
||||||
>>> binary_count_trailing_zeros(58)
|
|
||||||
1
|
|
||||||
>>> binary_count_trailing_zeros(4294967296)
|
|
||||||
32
|
|
||||||
>>> binary_count_trailing_zeros(0)
|
|
||||||
0
|
|
||||||
>>> binary_count_trailing_zeros(-10)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Input value must be a positive integer
|
|
||||||
>>> binary_count_trailing_zeros(0.8)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Input value must be a 'int' type
|
|
||||||
>>> binary_count_trailing_zeros("0")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
|
||||||
if a < 0:
|
|
||||||
raise ValueError("Input value must be a positive integer")
|
|
||||||
elif isinstance(a, float):
|
|
||||||
raise TypeError("Input value must be a 'int' type")
|
|
||||||
return 0 if (a == 0) else int(log2(a & -a))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,48 +0,0 @@
|
||||||
# https://www.tutorialspoint.com/python3/bitwise_operators_example.htm
|
|
||||||
|
|
||||||
|
|
||||||
def binary_or(a: int, b: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in 2 integers, convert them to binary, and return a binary number that is the
|
|
||||||
result of a binary or operation on the integers provided.
|
|
||||||
|
|
||||||
>>> binary_or(25, 32)
|
|
||||||
'0b111001'
|
|
||||||
>>> binary_or(37, 50)
|
|
||||||
'0b110111'
|
|
||||||
>>> binary_or(21, 30)
|
|
||||||
'0b11111'
|
|
||||||
>>> binary_or(58, 73)
|
|
||||||
'0b1111011'
|
|
||||||
>>> binary_or(0, 255)
|
|
||||||
'0b11111111'
|
|
||||||
>>> binary_or(0, 256)
|
|
||||||
'0b100000000'
|
|
||||||
>>> binary_or(0, -1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: the value of both inputs must be positive
|
|
||||||
>>> binary_or(0, 1.1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: 'float' object cannot be interpreted as an integer
|
|
||||||
>>> binary_or("0", "1")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
|
||||||
if a < 0 or b < 0:
|
|
||||||
raise ValueError("the value of both inputs must be positive")
|
|
||||||
a_binary = str(bin(a))[2:] # remove the leading "0b"
|
|
||||||
b_binary = str(bin(b))[2:]
|
|
||||||
max_len = max(len(a_binary), len(b_binary))
|
|
||||||
return "0b" + "".join(
|
|
||||||
str(int("1" in (char_a, char_b)))
|
|
||||||
for char_a, char_b in zip(a_binary.zfill(max_len), b_binary.zfill(max_len))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,109 +0,0 @@
|
||||||
# Information on binary shifts:
|
|
||||||
# https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types
|
|
||||||
# https://www.interviewcake.com/concept/java/bit-shift
|
|
||||||
|
|
||||||
|
|
||||||
def logical_left_shift(number: int, shift_amount: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in 2 positive integers.
|
|
||||||
'number' is the integer to be logically left shifted 'shift_amount' times.
|
|
||||||
i.e. (number << shift_amount)
|
|
||||||
Return the shifted binary representation.
|
|
||||||
|
|
||||||
>>> logical_left_shift(0, 1)
|
|
||||||
'0b00'
|
|
||||||
>>> logical_left_shift(1, 1)
|
|
||||||
'0b10'
|
|
||||||
>>> logical_left_shift(1, 5)
|
|
||||||
'0b100000'
|
|
||||||
>>> logical_left_shift(17, 2)
|
|
||||||
'0b1000100'
|
|
||||||
>>> logical_left_shift(1983, 4)
|
|
||||||
'0b111101111110000'
|
|
||||||
>>> logical_left_shift(1, -1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: both inputs must be positive integers
|
|
||||||
"""
|
|
||||||
if number < 0 or shift_amount < 0:
|
|
||||||
raise ValueError("both inputs must be positive integers")
|
|
||||||
|
|
||||||
binary_number = str(bin(number))
|
|
||||||
binary_number += "0" * shift_amount
|
|
||||||
return binary_number
|
|
||||||
|
|
||||||
|
|
||||||
def logical_right_shift(number: int, shift_amount: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in positive 2 integers.
|
|
||||||
'number' is the integer to be logically right shifted 'shift_amount' times.
|
|
||||||
i.e. (number >>> shift_amount)
|
|
||||||
Return the shifted binary representation.
|
|
||||||
|
|
||||||
>>> logical_right_shift(0, 1)
|
|
||||||
'0b0'
|
|
||||||
>>> logical_right_shift(1, 1)
|
|
||||||
'0b0'
|
|
||||||
>>> logical_right_shift(1, 5)
|
|
||||||
'0b0'
|
|
||||||
>>> logical_right_shift(17, 2)
|
|
||||||
'0b100'
|
|
||||||
>>> logical_right_shift(1983, 4)
|
|
||||||
'0b1111011'
|
|
||||||
>>> logical_right_shift(1, -1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: both inputs must be positive integers
|
|
||||||
"""
|
|
||||||
if number < 0 or shift_amount < 0:
|
|
||||||
raise ValueError("both inputs must be positive integers")
|
|
||||||
|
|
||||||
binary_number = str(bin(number))[2:]
|
|
||||||
if shift_amount >= len(binary_number):
|
|
||||||
return "0b0"
|
|
||||||
shifted_binary_number = binary_number[: len(binary_number) - shift_amount]
|
|
||||||
return "0b" + shifted_binary_number
|
|
||||||
|
|
||||||
|
|
||||||
def arithmetic_right_shift(number: int, shift_amount: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in 2 integers.
|
|
||||||
'number' is the integer to be arithmetically right shifted 'shift_amount' times.
|
|
||||||
i.e. (number >> shift_amount)
|
|
||||||
Return the shifted binary representation.
|
|
||||||
|
|
||||||
>>> arithmetic_right_shift(0, 1)
|
|
||||||
'0b00'
|
|
||||||
>>> arithmetic_right_shift(1, 1)
|
|
||||||
'0b00'
|
|
||||||
>>> arithmetic_right_shift(-1, 1)
|
|
||||||
'0b11'
|
|
||||||
>>> arithmetic_right_shift(17, 2)
|
|
||||||
'0b000100'
|
|
||||||
>>> arithmetic_right_shift(-17, 2)
|
|
||||||
'0b111011'
|
|
||||||
>>> arithmetic_right_shift(-1983, 4)
|
|
||||||
'0b111110000100'
|
|
||||||
"""
|
|
||||||
if number >= 0: # Get binary representation of positive number
|
|
||||||
binary_number = "0" + str(bin(number)).strip("-")[2:]
|
|
||||||
else: # Get binary (2's complement) representation of negative number
|
|
||||||
binary_number_length = len(bin(number)[3:]) # Find 2's complement of number
|
|
||||||
binary_number = bin(abs(number) - (1 << binary_number_length))[3:]
|
|
||||||
binary_number = (
|
|
||||||
"1" + "0" * (binary_number_length - len(binary_number)) + binary_number
|
|
||||||
)
|
|
||||||
|
|
||||||
if shift_amount >= len(binary_number):
|
|
||||||
return "0b" + binary_number[0] * len(binary_number)
|
|
||||||
return (
|
|
||||||
"0b"
|
|
||||||
+ binary_number[0] * shift_amount
|
|
||||||
+ binary_number[: len(binary_number) - shift_amount]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,43 +0,0 @@
|
||||||
# Information on 2's complement: https://en.wikipedia.org/wiki/Two%27s_complement
|
|
||||||
|
|
||||||
|
|
||||||
def twos_complement(number: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in a negative integer 'number'.
|
|
||||||
Return the two's complement representation of 'number'.
|
|
||||||
|
|
||||||
>>> twos_complement(0)
|
|
||||||
'0b0'
|
|
||||||
>>> twos_complement(-1)
|
|
||||||
'0b11'
|
|
||||||
>>> twos_complement(-5)
|
|
||||||
'0b1011'
|
|
||||||
>>> twos_complement(-17)
|
|
||||||
'0b101111'
|
|
||||||
>>> twos_complement(-207)
|
|
||||||
'0b100110001'
|
|
||||||
>>> twos_complement(1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: input must be a negative integer
|
|
||||||
"""
|
|
||||||
if number > 0:
|
|
||||||
raise ValueError("input must be a negative integer")
|
|
||||||
binary_number_length = len(bin(number)[3:])
|
|
||||||
twos_complement_number = bin(abs(number) - (1 << binary_number_length))[3:]
|
|
||||||
twos_complement_number = (
|
|
||||||
(
|
|
||||||
"1"
|
|
||||||
+ "0" * (binary_number_length - len(twos_complement_number))
|
|
||||||
+ twos_complement_number
|
|
||||||
)
|
|
||||||
if number < 0
|
|
||||||
else "0"
|
|
||||||
)
|
|
||||||
return "0b" + twos_complement_number
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,52 +0,0 @@
|
||||||
# https://www.tutorialspoint.com/python3/bitwise_operators_example.htm
|
|
||||||
|
|
||||||
|
|
||||||
def binary_xor(a: int, b: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in 2 integers, convert them to binary,
|
|
||||||
return a binary number that is the
|
|
||||||
result of a binary xor operation on the integers provided.
|
|
||||||
|
|
||||||
>>> binary_xor(25, 32)
|
|
||||||
'0b111001'
|
|
||||||
>>> binary_xor(37, 50)
|
|
||||||
'0b010111'
|
|
||||||
>>> binary_xor(21, 30)
|
|
||||||
'0b01011'
|
|
||||||
>>> binary_xor(58, 73)
|
|
||||||
'0b1110011'
|
|
||||||
>>> binary_xor(0, 255)
|
|
||||||
'0b11111111'
|
|
||||||
>>> binary_xor(256, 256)
|
|
||||||
'0b000000000'
|
|
||||||
>>> binary_xor(0, -1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: the value of both inputs must be positive
|
|
||||||
>>> binary_xor(0, 1.1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: 'float' object cannot be interpreted as an integer
|
|
||||||
>>> binary_xor("0", "1")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
|
||||||
if a < 0 or b < 0:
|
|
||||||
raise ValueError("the value of both inputs must be positive")
|
|
||||||
|
|
||||||
a_binary = str(bin(a))[2:] # remove the leading "0b"
|
|
||||||
b_binary = str(bin(b))[2:] # remove the leading "0b"
|
|
||||||
|
|
||||||
max_len = max(len(a_binary), len(b_binary))
|
|
||||||
|
|
||||||
return "0b" + "".join(
|
|
||||||
str(int(char_a != char_b))
|
|
||||||
for char_a, char_b in zip(a_binary.zfill(max_len), b_binary.zfill(max_len))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,55 +0,0 @@
|
||||||
"""
|
|
||||||
Calculates the sum of two non-negative integers using bitwise operators
|
|
||||||
Wikipedia explanation: https://en.wikipedia.org/wiki/Binary_number
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def bitwise_addition_recursive(number: int, other_number: int) -> int:
|
|
||||||
"""
|
|
||||||
>>> bitwise_addition_recursive(4, 5)
|
|
||||||
9
|
|
||||||
>>> bitwise_addition_recursive(8, 9)
|
|
||||||
17
|
|
||||||
>>> bitwise_addition_recursive(0, 4)
|
|
||||||
4
|
|
||||||
>>> bitwise_addition_recursive(4.5, 9)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Both arguments MUST be integers!
|
|
||||||
>>> bitwise_addition_recursive('4', 9)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Both arguments MUST be integers!
|
|
||||||
>>> bitwise_addition_recursive('4.5', 9)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Both arguments MUST be integers!
|
|
||||||
>>> bitwise_addition_recursive(-1, 9)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Both arguments MUST be non-negative!
|
|
||||||
>>> bitwise_addition_recursive(1, -9)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Both arguments MUST be non-negative!
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not isinstance(number, int) or not isinstance(other_number, int):
|
|
||||||
raise TypeError("Both arguments MUST be integers!")
|
|
||||||
|
|
||||||
if number < 0 or other_number < 0:
|
|
||||||
raise ValueError("Both arguments MUST be non-negative!")
|
|
||||||
|
|
||||||
bitwise_sum = number ^ other_number
|
|
||||||
carry = number & other_number
|
|
||||||
|
|
||||||
if carry == 0:
|
|
||||||
return bitwise_sum
|
|
||||||
|
|
||||||
return bitwise_addition_recursive(bitwise_sum, carry << 1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,46 +0,0 @@
|
||||||
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()
|
|
|
@ -1,93 +0,0 @@
|
||||||
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
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(25)
|
|
||||||
3
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(37)
|
|
||||||
3
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(21)
|
|
||||||
3
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(58)
|
|
||||||
4
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(0)
|
|
||||||
0
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(256)
|
|
||||||
1
|
|
||||||
>>> get_set_bits_count_using_brian_kernighans_algorithm(-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
|
|
||||||
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
|
|
||||||
while number:
|
|
||||||
if number % 2 == 1:
|
|
||||||
result += 1
|
|
||||||
number >>= 1
|
|
||||||
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(
|
|
||||||
f"z.get_set_bits_count_using_modulo_operator({number})", setup=setup
|
|
||||||
)
|
|
||||||
print(f"timeit() runs in {timing} seconds")
|
|
||||||
print(f"{get_set_bits_count_using_brian_kernighans_algorithm(number) = }")
|
|
||||||
timing = timeit(
|
|
||||||
f"z.get_set_bits_count_using_brian_kernighans_algorithm({number})",
|
|
||||||
setup=setup,
|
|
||||||
)
|
|
||||||
print(f"timeit() runs in {timing} seconds")
|
|
||||||
|
|
||||||
for number in (25, 37, 58, 0):
|
|
||||||
do_benchmark(number)
|
|
||||||
print()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
||||||
benchmark()
|
|
|
@ -1,27 +0,0 @@
|
||||||
def excess_3_code(number: int) -> str:
|
|
||||||
"""
|
|
||||||
Find excess-3 code of integer base 10.
|
|
||||||
Add 3 to all digits in a decimal number then convert to a binary-coded decimal.
|
|
||||||
https://en.wikipedia.org/wiki/Excess-3
|
|
||||||
|
|
||||||
>>> excess_3_code(0)
|
|
||||||
'0b0011'
|
|
||||||
>>> excess_3_code(3)
|
|
||||||
'0b0110'
|
|
||||||
>>> excess_3_code(2)
|
|
||||||
'0b0101'
|
|
||||||
>>> excess_3_code(20)
|
|
||||||
'0b01010011'
|
|
||||||
>>> excess_3_code(120)
|
|
||||||
'0b010001010011'
|
|
||||||
"""
|
|
||||||
num = ""
|
|
||||||
for digit in str(max(0, number)):
|
|
||||||
num += str(bin(int(digit) + 3))[2:].zfill(4)
|
|
||||||
return "0b" + num
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,30 +0,0 @@
|
||||||
def find_previous_power_of_two(number: int) -> int:
|
|
||||||
"""
|
|
||||||
Find the largest power of two that is less than or equal to a given integer.
|
|
||||||
https://stackoverflow.com/questions/1322510
|
|
||||||
|
|
||||||
>>> [find_previous_power_of_two(i) for i in range(18)]
|
|
||||||
[0, 1, 2, 2, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16]
|
|
||||||
>>> find_previous_power_of_two(-5)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Input must be a non-negative integer
|
|
||||||
>>> find_previous_power_of_two(10.5)
|
|
||||||
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")
|
|
||||||
if number == 0:
|
|
||||||
return 0
|
|
||||||
power = 1
|
|
||||||
while power <= number:
|
|
||||||
power <<= 1 # Equivalent to multiplying by 2
|
|
||||||
return power >> 1 if number > 1 else 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,94 +0,0 @@
|
||||||
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()
|
|
|
@ -1,34 +0,0 @@
|
||||||
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()
|
|
|
@ -1,51 +0,0 @@
|
||||||
# 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)
|
|
|
@ -1,37 +0,0 @@
|
||||||
def is_even(number: int) -> bool:
|
|
||||||
"""
|
|
||||||
return true if the input integer is even
|
|
||||||
Explanation: Lets take a look at the following decimal 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()
|
|
|
@ -1,57 +0,0 @@
|
||||||
"""
|
|
||||||
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()
|
|
|
@ -1,60 +0,0 @@
|
||||||
"""
|
|
||||||
Author : Naman Sharma
|
|
||||||
Date : October 2, 2023
|
|
||||||
|
|
||||||
Task:
|
|
||||||
To Find the largest power of 2 less than or equal to a given number.
|
|
||||||
|
|
||||||
Implementation notes: Use bit manipulation.
|
|
||||||
We start from 1 & left shift the set bit to check if (res<<1)<=number.
|
|
||||||
Each left bit shift represents a pow of 2.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
number: 15
|
|
||||||
res: 1 0b1
|
|
||||||
2 0b10
|
|
||||||
4 0b100
|
|
||||||
8 0b1000
|
|
||||||
16 0b10000 (Exit)
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def largest_pow_of_two_le_num(number: int) -> int:
|
|
||||||
"""
|
|
||||||
Return the largest power of two less than or equal to a number.
|
|
||||||
|
|
||||||
>>> largest_pow_of_two_le_num(0)
|
|
||||||
0
|
|
||||||
>>> largest_pow_of_two_le_num(1)
|
|
||||||
1
|
|
||||||
>>> largest_pow_of_two_le_num(-1)
|
|
||||||
0
|
|
||||||
>>> largest_pow_of_two_le_num(3)
|
|
||||||
2
|
|
||||||
>>> largest_pow_of_two_le_num(15)
|
|
||||||
8
|
|
||||||
>>> largest_pow_of_two_le_num(99)
|
|
||||||
64
|
|
||||||
>>> largest_pow_of_two_le_num(178)
|
|
||||||
128
|
|
||||||
>>> largest_pow_of_two_le_num(999999)
|
|
||||||
524288
|
|
||||||
>>> largest_pow_of_two_le_num(99.9)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Input value must be a 'int' type
|
|
||||||
"""
|
|
||||||
if isinstance(number, float):
|
|
||||||
raise TypeError("Input value must be a 'int' type")
|
|
||||||
if number <= 0:
|
|
||||||
return 0
|
|
||||||
res = 1
|
|
||||||
while (res << 1) <= number:
|
|
||||||
res <<= 1
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,40 +0,0 @@
|
||||||
def find_missing_number(nums: list[int]) -> int:
|
|
||||||
"""
|
|
||||||
Finds the missing number in a list of consecutive integers.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
nums: A list of integers.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The missing number.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
>>> find_missing_number([0, 1, 3, 4])
|
|
||||||
2
|
|
||||||
>>> find_missing_number([4, 3, 1, 0])
|
|
||||||
2
|
|
||||||
>>> find_missing_number([-4, -3, -1, 0])
|
|
||||||
-2
|
|
||||||
>>> find_missing_number([-2, 2, 1, 3, 0])
|
|
||||||
-1
|
|
||||||
>>> find_missing_number([1, 3, 4, 5, 6])
|
|
||||||
2
|
|
||||||
>>> find_missing_number([6, 5, 4, 2, 1])
|
|
||||||
3
|
|
||||||
>>> find_missing_number([6, 1, 5, 3, 4])
|
|
||||||
2
|
|
||||||
"""
|
|
||||||
low = min(nums)
|
|
||||||
high = max(nums)
|
|
||||||
missing_number = high
|
|
||||||
|
|
||||||
for i in range(low, high):
|
|
||||||
missing_number ^= i ^ nums[i - low]
|
|
||||||
|
|
||||||
return missing_number
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
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()
|
|
|
@ -1,67 +0,0 @@
|
||||||
"""
|
|
||||||
|
|
||||||
Task:
|
|
||||||
Given a positive int number. Return True if this number is power of 4
|
|
||||||
or False otherwise.
|
|
||||||
|
|
||||||
Implementation notes: Use bit manipulation.
|
|
||||||
For example if the number is the power of 2 it's bits representation:
|
|
||||||
n = 0..100..00
|
|
||||||
n - 1 = 0..011..11
|
|
||||||
|
|
||||||
n & (n - 1) - no intersections = 0
|
|
||||||
If the number is a power of 4 then it should be a power of 2
|
|
||||||
and the set bit should be at an odd position.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def power_of_4(number: int) -> bool:
|
|
||||||
"""
|
|
||||||
Return True if this number is power of 4 or False otherwise.
|
|
||||||
|
|
||||||
>>> power_of_4(0)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: number must be positive
|
|
||||||
>>> power_of_4(1)
|
|
||||||
True
|
|
||||||
>>> power_of_4(2)
|
|
||||||
False
|
|
||||||
>>> power_of_4(4)
|
|
||||||
True
|
|
||||||
>>> power_of_4(6)
|
|
||||||
False
|
|
||||||
>>> power_of_4(8)
|
|
||||||
False
|
|
||||||
>>> power_of_4(17)
|
|
||||||
False
|
|
||||||
>>> power_of_4(64)
|
|
||||||
True
|
|
||||||
>>> power_of_4(-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: number must be positive
|
|
||||||
>>> power_of_4(1.2)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: number must be an integer
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not isinstance(number, int):
|
|
||||||
raise TypeError("number must be an integer")
|
|
||||||
if number <= 0:
|
|
||||||
raise ValueError("number must be positive")
|
|
||||||
if number & (number - 1) == 0:
|
|
||||||
c = 0
|
|
||||||
while number:
|
|
||||||
c += 1
|
|
||||||
number >>= 1
|
|
||||||
return c % 2 == 1
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,86 +0,0 @@
|
||||||
def get_reverse_bit_string(number: int) -> str:
|
|
||||||
"""
|
|
||||||
return the bit string of an integer
|
|
||||||
|
|
||||||
>>> get_reverse_bit_string(9)
|
|
||||||
'10010000000000000000000000000000'
|
|
||||||
>>> get_reverse_bit_string(43)
|
|
||||||
'11010100000000000000000000000000'
|
|
||||||
>>> get_reverse_bit_string(2873)
|
|
||||||
'10011100110100000000000000000000'
|
|
||||||
>>> get_reverse_bit_string("this is not a number")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: operation can not be conducted on a object of type str
|
|
||||||
"""
|
|
||||||
if not isinstance(number, int):
|
|
||||||
msg = (
|
|
||||||
"operation can not be conducted on a object of type "
|
|
||||||
f"{type(number).__name__}"
|
|
||||||
)
|
|
||||||
raise TypeError(msg)
|
|
||||||
bit_string = ""
|
|
||||||
for _ in range(32):
|
|
||||||
bit_string += str(number % 2)
|
|
||||||
number = number >> 1
|
|
||||||
return bit_string
|
|
||||||
|
|
||||||
|
|
||||||
def reverse_bit(number: int) -> str:
|
|
||||||
"""
|
|
||||||
Take in an 32 bit integer, reverse its bits,
|
|
||||||
return a string of reverse bits
|
|
||||||
|
|
||||||
result of a reverse_bit and operation on the integer provided.
|
|
||||||
|
|
||||||
>>> reverse_bit(25)
|
|
||||||
'00000000000000000000000000011001'
|
|
||||||
>>> reverse_bit(37)
|
|
||||||
'00000000000000000000000000100101'
|
|
||||||
>>> reverse_bit(21)
|
|
||||||
'00000000000000000000000000010101'
|
|
||||||
>>> reverse_bit(58)
|
|
||||||
'00000000000000000000000000111010'
|
|
||||||
>>> reverse_bit(0)
|
|
||||||
'00000000000000000000000000000000'
|
|
||||||
>>> reverse_bit(256)
|
|
||||||
'00000000000000000000000100000000'
|
|
||||||
>>> reverse_bit(-1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: the value of input must be positive
|
|
||||||
|
|
||||||
>>> reverse_bit(1.1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: Input value must be a 'int' type
|
|
||||||
|
|
||||||
>>> reverse_bit("0")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
|
||||||
"""
|
|
||||||
if number < 0:
|
|
||||||
raise ValueError("the value of input must be positive")
|
|
||||||
elif isinstance(number, float):
|
|
||||||
raise TypeError("Input value must be a 'int' type")
|
|
||||||
elif isinstance(number, str):
|
|
||||||
raise TypeError("'<' not supported between instances of 'str' and 'int'")
|
|
||||||
result = 0
|
|
||||||
# iterator over [1 to 32],since we are dealing with 32 bit integer
|
|
||||||
for _ in range(1, 33):
|
|
||||||
# left shift the bits by unity
|
|
||||||
result = result << 1
|
|
||||||
# get the end bit
|
|
||||||
end_bit = number % 2
|
|
||||||
# right shift the bits by unity
|
|
||||||
number = number >> 1
|
|
||||||
# add that bit to our ans
|
|
||||||
result = result | end_bit
|
|
||||||
return get_reverse_bit_string(result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,100 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""Provide the functionality to manipulate a single bit."""
|
|
||||||
|
|
||||||
|
|
||||||
def set_bit(number: int, position: int) -> int:
|
|
||||||
"""
|
|
||||||
Set the bit at position to 1.
|
|
||||||
|
|
||||||
Details: perform bitwise or for given number and X.
|
|
||||||
Where X is a number with all the bits - zeroes and bit on given
|
|
||||||
position - one.
|
|
||||||
|
|
||||||
>>> set_bit(0b1101, 1) # 0b1111
|
|
||||||
15
|
|
||||||
>>> set_bit(0b0, 5) # 0b100000
|
|
||||||
32
|
|
||||||
>>> set_bit(0b1111, 1) # 0b1111
|
|
||||||
15
|
|
||||||
"""
|
|
||||||
return number | (1 << position)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_bit(number: int, position: int) -> int:
|
|
||||||
"""
|
|
||||||
Set the bit at position to 0.
|
|
||||||
|
|
||||||
Details: perform bitwise and for given number and X.
|
|
||||||
Where X is a number with all the bits - ones and bit on given
|
|
||||||
position - zero.
|
|
||||||
|
|
||||||
>>> clear_bit(0b10010, 1) # 0b10000
|
|
||||||
16
|
|
||||||
>>> clear_bit(0b0, 5) # 0b0
|
|
||||||
0
|
|
||||||
"""
|
|
||||||
return number & ~(1 << position)
|
|
||||||
|
|
||||||
|
|
||||||
def flip_bit(number: int, position: int) -> int:
|
|
||||||
"""
|
|
||||||
Flip the bit at position.
|
|
||||||
|
|
||||||
Details: perform bitwise xor for given number and X.
|
|
||||||
Where X is a number with all the bits - zeroes and bit on given
|
|
||||||
position - one.
|
|
||||||
|
|
||||||
>>> flip_bit(0b101, 1) # 0b111
|
|
||||||
7
|
|
||||||
>>> flip_bit(0b101, 0) # 0b100
|
|
||||||
4
|
|
||||||
"""
|
|
||||||
return number ^ (1 << position)
|
|
||||||
|
|
||||||
|
|
||||||
def is_bit_set(number: int, position: int) -> bool:
|
|
||||||
"""
|
|
||||||
Is the bit at position set?
|
|
||||||
|
|
||||||
Details: Shift the bit at position to be the first (smallest) bit.
|
|
||||||
Then check if the first bit is set by anding the shifted number with 1.
|
|
||||||
|
|
||||||
>>> is_bit_set(0b1010, 0)
|
|
||||||
False
|
|
||||||
>>> is_bit_set(0b1010, 1)
|
|
||||||
True
|
|
||||||
>>> is_bit_set(0b1010, 2)
|
|
||||||
False
|
|
||||||
>>> is_bit_set(0b1010, 3)
|
|
||||||
True
|
|
||||||
>>> is_bit_set(0b0, 17)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
return ((number >> position) & 1) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def get_bit(number: int, position: int) -> int:
|
|
||||||
"""
|
|
||||||
Get the bit at the given position
|
|
||||||
|
|
||||||
Details: perform bitwise and for the given number and X,
|
|
||||||
Where X is a number with all the bits - zeroes and bit on given position - one.
|
|
||||||
If the result is not equal to 0, then the bit on the given position is 1, else 0.
|
|
||||||
|
|
||||||
>>> get_bit(0b1010, 0)
|
|
||||||
0
|
|
||||||
>>> get_bit(0b1010, 1)
|
|
||||||
1
|
|
||||||
>>> get_bit(0b1010, 2)
|
|
||||||
0
|
|
||||||
>>> get_bit(0b1010, 3)
|
|
||||||
1
|
|
||||||
"""
|
|
||||||
return int((number & (1 << position)) != 0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,58 +0,0 @@
|
||||||
def show_bits(before: int, after: int) -> str:
|
|
||||||
"""
|
|
||||||
>>> print(show_bits(0, 0xFFFF))
|
|
||||||
0: 00000000
|
|
||||||
65535: 1111111111111111
|
|
||||||
"""
|
|
||||||
return f"{before:>5}: {before:08b}\n{after:>5}: {after:08b}"
|
|
||||||
|
|
||||||
|
|
||||||
def swap_odd_even_bits(num: int) -> int:
|
|
||||||
"""
|
|
||||||
1. We use bitwise AND operations to separate the even bits (0, 2, 4, 6, etc.) and
|
|
||||||
odd bits (1, 3, 5, 7, etc.) in the input number.
|
|
||||||
2. We then right-shift the even bits by 1 position and left-shift the odd bits by
|
|
||||||
1 position to swap them.
|
|
||||||
3. Finally, we combine the swapped even and odd bits using a bitwise OR operation
|
|
||||||
to obtain the final result.
|
|
||||||
>>> print(show_bits(0, swap_odd_even_bits(0)))
|
|
||||||
0: 00000000
|
|
||||||
0: 00000000
|
|
||||||
>>> print(show_bits(1, swap_odd_even_bits(1)))
|
|
||||||
1: 00000001
|
|
||||||
2: 00000010
|
|
||||||
>>> print(show_bits(2, swap_odd_even_bits(2)))
|
|
||||||
2: 00000010
|
|
||||||
1: 00000001
|
|
||||||
>>> print(show_bits(3, swap_odd_even_bits(3)))
|
|
||||||
3: 00000011
|
|
||||||
3: 00000011
|
|
||||||
>>> print(show_bits(4, swap_odd_even_bits(4)))
|
|
||||||
4: 00000100
|
|
||||||
8: 00001000
|
|
||||||
>>> print(show_bits(5, swap_odd_even_bits(5)))
|
|
||||||
5: 00000101
|
|
||||||
10: 00001010
|
|
||||||
>>> print(show_bits(6, swap_odd_even_bits(6)))
|
|
||||||
6: 00000110
|
|
||||||
9: 00001001
|
|
||||||
>>> print(show_bits(23, swap_odd_even_bits(23)))
|
|
||||||
23: 00010111
|
|
||||||
43: 00101011
|
|
||||||
"""
|
|
||||||
# Get all even bits - 0xAAAAAAAA is a 32-bit number with all even bits set to 1
|
|
||||||
even_bits = num & 0xAAAAAAAA
|
|
||||||
|
|
||||||
# Get all odd bits - 0x55555555 is a 32-bit number with all odd bits set to 1
|
|
||||||
odd_bits = num & 0x55555555
|
|
||||||
|
|
||||||
# Right shift even bits and left shift odd bits and swap them
|
|
||||||
return even_bits >> 1 | odd_bits << 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
||||||
for i in (-1, 0, 1, 2, 3, 4, 23, 24):
|
|
||||||
print(show_bits(i, swap_odd_even_bits(i)), "\n")
|
|
|
@ -1,45 +0,0 @@
|
||||||
# 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/>
|
|
|
@ -1,109 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from maths.greatest_common_divisor import greatest_common_divisor
|
|
||||||
|
|
||||||
|
|
||||||
def diophantine(a: int, b: int, c: int) -> tuple[float, float]:
|
|
||||||
"""
|
|
||||||
Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the
|
|
||||||
diophantine equation a*x + b*y = c has a solution (where x and y are integers)
|
|
||||||
iff greatest_common_divisor(a,b) divides c.
|
|
||||||
|
|
||||||
GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor )
|
|
||||||
|
|
||||||
>>> diophantine(10,6,14)
|
|
||||||
(-7.0, 14.0)
|
|
||||||
|
|
||||||
>>> diophantine(391,299,-69)
|
|
||||||
(9.0, -12.0)
|
|
||||||
|
|
||||||
But above equation has one more solution i.e., x = -4, y = 5.
|
|
||||||
That's why we need diophantine all solution function.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
assert (
|
|
||||||
c % greatest_common_divisor(a, b) == 0
|
|
||||||
) # greatest_common_divisor(a,b) is in maths directory
|
|
||||||
(d, x, y) = extended_gcd(a, b) # extended_gcd(a,b) function implemented below
|
|
||||||
r = c / d
|
|
||||||
return (r * x, r * y)
|
|
||||||
|
|
||||||
|
|
||||||
def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None:
|
|
||||||
"""
|
|
||||||
Lemma : if n|ab and gcd(a,n) = 1, then n|b.
|
|
||||||
|
|
||||||
Finding All solutions of Diophantine Equations:
|
|
||||||
|
|
||||||
Theorem : Let gcd(a,b) = d, a = d*p, b = d*q. If (x0,y0) is a solution of
|
|
||||||
Diophantine Equation a*x + b*y = c. a*x0 + b*y0 = c, then all the
|
|
||||||
solutions have the form a(x0 + t*q) + b(y0 - t*p) = c,
|
|
||||||
where t is an arbitrary integer.
|
|
||||||
|
|
||||||
n is the number of solution you want, n = 2 by default
|
|
||||||
|
|
||||||
>>> diophantine_all_soln(10, 6, 14)
|
|
||||||
-7.0 14.0
|
|
||||||
-4.0 9.0
|
|
||||||
|
|
||||||
>>> diophantine_all_soln(10, 6, 14, 4)
|
|
||||||
-7.0 14.0
|
|
||||||
-4.0 9.0
|
|
||||||
-1.0 4.0
|
|
||||||
2.0 -1.0
|
|
||||||
|
|
||||||
>>> diophantine_all_soln(391, 299, -69, n = 4)
|
|
||||||
9.0 -12.0
|
|
||||||
22.0 -29.0
|
|
||||||
35.0 -46.0
|
|
||||||
48.0 -63.0
|
|
||||||
|
|
||||||
"""
|
|
||||||
(x0, y0) = diophantine(a, b, c) # Initial value
|
|
||||||
d = greatest_common_divisor(a, b)
|
|
||||||
p = a // d
|
|
||||||
q = b // d
|
|
||||||
|
|
||||||
for i in range(n):
|
|
||||||
x = x0 + i * q
|
|
||||||
y = y0 - i * p
|
|
||||||
print(x, y)
|
|
||||||
|
|
||||||
|
|
||||||
def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
|
|
||||||
"""
|
|
||||||
Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers
|
|
||||||
x and y, then d = gcd(a,b)
|
|
||||||
|
|
||||||
>>> extended_gcd(10, 6)
|
|
||||||
(2, -1, 2)
|
|
||||||
|
|
||||||
>>> extended_gcd(7, 5)
|
|
||||||
(1, -2, 3)
|
|
||||||
|
|
||||||
"""
|
|
||||||
assert a >= 0
|
|
||||||
assert b >= 0
|
|
||||||
|
|
||||||
if b == 0:
|
|
||||||
d, x, y = a, 1, 0
|
|
||||||
else:
|
|
||||||
(d, p, q) = extended_gcd(b, a % b)
|
|
||||||
x = q
|
|
||||||
y = p - q * (a // b)
|
|
||||||
|
|
||||||
assert a % d == 0
|
|
||||||
assert b % d == 0
|
|
||||||
assert d == a * x + b * y
|
|
||||||
|
|
||||||
return (d, x, y)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from doctest import testmod
|
|
||||||
|
|
||||||
testmod(name="diophantine", verbose=True)
|
|
||||||
testmod(name="diophantine_all_soln", verbose=True)
|
|
||||||
testmod(name="extended_gcd", verbose=True)
|
|
||||||
testmod(name="greatest_common_divisor", verbose=True)
|
|
|
@ -1,7 +0,0 @@
|
||||||
# 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/>
|
|
|
@ -1,38 +0,0 @@
|
||||||
"""
|
|
||||||
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 and input_2)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
An IMPLY Gate is a logic gate in boolean algebra which results to 1 if
|
|
||||||
either input 1 is 0, or if input 1 is 1, then the output is 1 only if input 2 is 1.
|
|
||||||
It is true if input 1 implies input 2.
|
|
||||||
|
|
||||||
Following is the truth table of an IMPLY Gate:
|
|
||||||
------------------------------
|
|
||||||
| Input 1 | Input 2 | Output |
|
|
||||||
------------------------------
|
|
||||||
| 0 | 0 | 1 |
|
|
||||||
| 0 | 1 | 1 |
|
|
||||||
| 1 | 0 | 0 |
|
|
||||||
| 1 | 1 | 1 |
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
Refer - https://en.wikipedia.org/wiki/IMPLY_gate
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def imply_gate(input_1: int, input_2: int) -> int:
|
|
||||||
"""
|
|
||||||
Calculate IMPLY of the input values
|
|
||||||
|
|
||||||
>>> imply_gate(0, 0)
|
|
||||||
1
|
|
||||||
>>> imply_gate(0, 1)
|
|
||||||
1
|
|
||||||
>>> imply_gate(1, 0)
|
|
||||||
0
|
|
||||||
>>> imply_gate(1, 1)
|
|
||||||
1
|
|
||||||
"""
|
|
||||||
return int(input_1 == 0 or input_2 == 1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,55 +0,0 @@
|
||||||
"""
|
|
||||||
https://en.wikipedia.org/wiki/Karnaugh_map
|
|
||||||
https://www.allaboutcircuits.com/technical-articles/karnaugh-map-boolean-algebraic-simplification-technique
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def simplify_kmap(kmap: list[list[int]]) -> str:
|
|
||||||
"""
|
|
||||||
Simplify the Karnaugh map.
|
|
||||||
>>> simplify_kmap(kmap=[[0, 1], [1, 1]])
|
|
||||||
"A'B + AB' + AB"
|
|
||||||
>>> simplify_kmap(kmap=[[0, 0], [0, 0]])
|
|
||||||
''
|
|
||||||
>>> simplify_kmap(kmap=[[0, 1], [1, -1]])
|
|
||||||
"A'B + AB' + AB"
|
|
||||||
>>> simplify_kmap(kmap=[[0, 1], [1, 2]])
|
|
||||||
"A'B + AB' + AB"
|
|
||||||
>>> simplify_kmap(kmap=[[0, 1], [1, 1.1]])
|
|
||||||
"A'B + AB' + AB"
|
|
||||||
>>> simplify_kmap(kmap=[[0, 1], [1, 'a']])
|
|
||||||
"A'B + AB' + AB"
|
|
||||||
"""
|
|
||||||
simplified_f = []
|
|
||||||
for a, row in enumerate(kmap):
|
|
||||||
for b, item in enumerate(row):
|
|
||||||
if item:
|
|
||||||
term = ("A" if a else "A'") + ("B" if b else "B'")
|
|
||||||
simplified_f.append(term)
|
|
||||||
return " + ".join(simplified_f)
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
"""
|
|
||||||
Main function to create and simplify a K-Map.
|
|
||||||
|
|
||||||
>>> main()
|
|
||||||
[0, 1]
|
|
||||||
[1, 1]
|
|
||||||
Simplified Expression:
|
|
||||||
A'B + AB' + AB
|
|
||||||
"""
|
|
||||||
kmap = [[0, 1], [1, 1]]
|
|
||||||
|
|
||||||
# Manually generate the product of [0, 1] and [0, 1]
|
|
||||||
|
|
||||||
for row in kmap:
|
|
||||||
print(row)
|
|
||||||
|
|
||||||
print("Simplified Expression:")
|
|
||||||
print(simplify_kmap(kmap))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
print(f"{simplify_kmap(kmap=[[0, 1], [1, 1]]) = }")
|
|
|
@ -1,42 +0,0 @@
|
||||||
def mux(input0: int, input1: int, select: int) -> int:
|
|
||||||
"""
|
|
||||||
Implement a 2-to-1 Multiplexer.
|
|
||||||
|
|
||||||
:param input0: The first input value (0 or 1).
|
|
||||||
:param input1: The second input value (0 or 1).
|
|
||||||
:param select: The select signal (0 or 1) to choose between input0 and input1.
|
|
||||||
:return: The output based on the select signal. input1 if select else input0.
|
|
||||||
|
|
||||||
https://www.electrically4u.com/solved-problems-on-multiplexer
|
|
||||||
https://en.wikipedia.org/wiki/Multiplexer
|
|
||||||
|
|
||||||
>>> mux(0, 1, 0)
|
|
||||||
0
|
|
||||||
>>> mux(0, 1, 1)
|
|
||||||
1
|
|
||||||
>>> mux(1, 0, 0)
|
|
||||||
1
|
|
||||||
>>> mux(1, 0, 1)
|
|
||||||
0
|
|
||||||
>>> mux(2, 1, 0)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Inputs and select signal must be 0 or 1
|
|
||||||
>>> mux(0, -1, 0)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Inputs and select signal must be 0 or 1
|
|
||||||
>>> mux(0, 1, 1.1)
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: Inputs and select signal must be 0 or 1
|
|
||||||
"""
|
|
||||||
if all(i in (0, 1) for i in (input0, input1, select)):
|
|
||||||
return input1 if select else input0
|
|
||||||
raise ValueError("Inputs and select signal must be 0 or 1")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,36 +0,0 @@
|
||||||
"""
|
|
||||||
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(not (input_1 and input_2))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,39 +0,0 @@
|
||||||
"""
|
|
||||||
An NIMPLY Gate is a logic gate in boolean algebra which results to 0 if
|
|
||||||
either input 1 is 0, or if input 1 is 1, then it is 0 only if input 2 is 1.
|
|
||||||
It is false if input 1 implies input 2. It is the negated form of imply
|
|
||||||
|
|
||||||
Following is the truth table of an NIMPLY Gate:
|
|
||||||
------------------------------
|
|
||||||
| Input 1 | Input 2 | Output |
|
|
||||||
------------------------------
|
|
||||||
| 0 | 0 | 0 |
|
|
||||||
| 0 | 1 | 0 |
|
|
||||||
| 1 | 0 | 1 |
|
|
||||||
| 1 | 1 | 0 |
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
Refer - https://en.wikipedia.org/wiki/NIMPLY_gate
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def nimply_gate(input_1: int, input_2: int) -> int:
|
|
||||||
"""
|
|
||||||
Calculate NIMPLY of the input values
|
|
||||||
|
|
||||||
>>> nimply_gate(0, 0)
|
|
||||||
0
|
|
||||||
>>> nimply_gate(0, 1)
|
|
||||||
0
|
|
||||||
>>> nimply_gate(1, 0)
|
|
||||||
1
|
|
||||||
>>> nimply_gate(1, 1)
|
|
||||||
0
|
|
||||||
"""
|
|
||||||
return int(input_1 == 1 and input_2 == 0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,68 +0,0 @@
|
||||||
"""
|
|
||||||
A NOR Gate is a logic gate in boolean algebra which results in false(0) if any of the
|
|
||||||
inputs is 1, and True(1) if all inputs are 0.
|
|
||||||
Following is the truth table of a NOR Gate:
|
|
||||||
Truth Table of NOR Gate:
|
|
||||||
| Input 1 | Input 2 | Output |
|
|
||||||
| 0 | 0 | 1 |
|
|
||||||
| 0 | 1 | 0 |
|
|
||||||
| 1 | 0 | 0 |
|
|
||||||
| 1 | 1 | 0 |
|
|
||||||
|
|
||||||
Code provided by Akshaj Vishwanathan
|
|
||||||
https://www.geeksforgeeks.org/logic-gates-in-python
|
|
||||||
"""
|
|
||||||
|
|
||||||
from collections.abc import Callable
|
|
||||||
|
|
||||||
|
|
||||||
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 truth_table(func: Callable) -> str:
|
|
||||||
"""
|
|
||||||
>>> print(truth_table(nor_gate))
|
|
||||||
Truth Table of NOR Gate:
|
|
||||||
| Input 1 | Input 2 | Output |
|
|
||||||
| 0 | 0 | 1 |
|
|
||||||
| 0 | 1 | 0 |
|
|
||||||
| 1 | 0 | 0 |
|
|
||||||
| 1 | 1 | 0 |
|
|
||||||
"""
|
|
||||||
|
|
||||||
def make_table_row(items: list | tuple) -> str:
|
|
||||||
"""
|
|
||||||
>>> make_table_row(("One", "Two", "Three"))
|
|
||||||
'| One | Two | Three |'
|
|
||||||
"""
|
|
||||||
return f"| {' | '.join(f'{item:^8}' for item in items)} |"
|
|
||||||
|
|
||||||
return "\n".join(
|
|
||||||
(
|
|
||||||
"Truth Table of NOR Gate:",
|
|
||||||
make_table_row(("Input 1", "Input 2", "Output")),
|
|
||||||
*[make_table_row((i, j, func(i, j))) for i in (0, 1) for j in (0, 1)],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
||||||
print(truth_table(nor_gate))
|
|
|
@ -1,30 +0,0 @@
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,35 +0,0 @@
|
||||||
"""
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,163 +0,0 @@
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from collections.abc import Sequence
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
|
|
||||||
def compare_string(string1: str, string2: str) -> str | Literal[False]:
|
|
||||||
"""
|
|
||||||
>>> compare_string('0010','0110')
|
|
||||||
'0_10'
|
|
||||||
|
|
||||||
>>> compare_string('0110','1101')
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
list1 = list(string1)
|
|
||||||
list2 = list(string2)
|
|
||||||
count = 0
|
|
||||||
for i in range(len(list1)):
|
|
||||||
if list1[i] != list2[i]:
|
|
||||||
count += 1
|
|
||||||
list1[i] = "_"
|
|
||||||
if count > 1:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return "".join(list1)
|
|
||||||
|
|
||||||
|
|
||||||
def check(binary: list[str]) -> list[str]:
|
|
||||||
"""
|
|
||||||
>>> check(['0.00.01.5'])
|
|
||||||
['0.00.01.5']
|
|
||||||
"""
|
|
||||||
pi = []
|
|
||||||
while True:
|
|
||||||
check1 = ["$"] * len(binary)
|
|
||||||
temp = []
|
|
||||||
for i in range(len(binary)):
|
|
||||||
for j in range(i + 1, len(binary)):
|
|
||||||
k = compare_string(binary[i], binary[j])
|
|
||||||
if k is False:
|
|
||||||
check1[i] = "*"
|
|
||||||
check1[j] = "*"
|
|
||||||
temp.append("X")
|
|
||||||
for i in range(len(binary)):
|
|
||||||
if check1[i] == "$":
|
|
||||||
pi.append(binary[i])
|
|
||||||
if len(temp) == 0:
|
|
||||||
return pi
|
|
||||||
binary = list(set(temp))
|
|
||||||
|
|
||||||
|
|
||||||
def decimal_to_binary(no_of_variable: int, minterms: Sequence[float]) -> list[str]:
|
|
||||||
"""
|
|
||||||
>>> decimal_to_binary(3,[1.5])
|
|
||||||
['0.00.01.5']
|
|
||||||
"""
|
|
||||||
temp = []
|
|
||||||
for minterm in minterms:
|
|
||||||
string = ""
|
|
||||||
for _ in range(no_of_variable):
|
|
||||||
string = str(minterm % 2) + string
|
|
||||||
minterm //= 2
|
|
||||||
temp.append(string)
|
|
||||||
return temp
|
|
||||||
|
|
||||||
|
|
||||||
def is_for_table(string1: str, string2: str, count: int) -> bool:
|
|
||||||
"""
|
|
||||||
>>> is_for_table('__1','011',2)
|
|
||||||
True
|
|
||||||
|
|
||||||
>>> is_for_table('01_','001',1)
|
|
||||||
False
|
|
||||||
"""
|
|
||||||
list1 = list(string1)
|
|
||||||
list2 = list(string2)
|
|
||||||
count_n = sum(item1 != item2 for item1, item2 in zip(list1, list2))
|
|
||||||
return count_n == count
|
|
||||||
|
|
||||||
|
|
||||||
def selection(chart: list[list[int]], prime_implicants: list[str]) -> list[str]:
|
|
||||||
"""
|
|
||||||
>>> selection([[1]],['0.00.01.5'])
|
|
||||||
['0.00.01.5']
|
|
||||||
|
|
||||||
>>> selection([[1]],['0.00.01.5'])
|
|
||||||
['0.00.01.5']
|
|
||||||
"""
|
|
||||||
temp = []
|
|
||||||
select = [0] * len(chart)
|
|
||||||
for i in range(len(chart[0])):
|
|
||||||
count = sum(row[i] == 1 for row in chart)
|
|
||||||
if count == 1:
|
|
||||||
rem = max(j for j, row in enumerate(chart) if row[i] == 1)
|
|
||||||
select[rem] = 1
|
|
||||||
for i, item in enumerate(select):
|
|
||||||
if item != 1:
|
|
||||||
continue
|
|
||||||
for j in range(len(chart[0])):
|
|
||||||
if chart[i][j] != 1:
|
|
||||||
continue
|
|
||||||
for row in chart:
|
|
||||||
row[j] = 0
|
|
||||||
temp.append(prime_implicants[i])
|
|
||||||
while True:
|
|
||||||
counts = [chart[i].count(1) for i in range(len(chart))]
|
|
||||||
max_n = max(counts)
|
|
||||||
rem = counts.index(max_n)
|
|
||||||
|
|
||||||
if max_n == 0:
|
|
||||||
return temp
|
|
||||||
|
|
||||||
temp.append(prime_implicants[rem])
|
|
||||||
|
|
||||||
for j in range(len(chart[0])):
|
|
||||||
if chart[rem][j] != 1:
|
|
||||||
continue
|
|
||||||
for i in range(len(chart)):
|
|
||||||
chart[i][j] = 0
|
|
||||||
|
|
||||||
|
|
||||||
def prime_implicant_chart(
|
|
||||||
prime_implicants: list[str], binary: list[str]
|
|
||||||
) -> list[list[int]]:
|
|
||||||
"""
|
|
||||||
>>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5'])
|
|
||||||
[[1]]
|
|
||||||
"""
|
|
||||||
chart = [[0 for x in range(len(binary))] for x in range(len(prime_implicants))]
|
|
||||||
for i in range(len(prime_implicants)):
|
|
||||||
count = prime_implicants[i].count("_")
|
|
||||||
for j in range(len(binary)):
|
|
||||||
if is_for_table(prime_implicants[i], binary[j], count):
|
|
||||||
chart[i][j] = 1
|
|
||||||
|
|
||||||
return chart
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
no_of_variable = int(input("Enter the no. of variables\n"))
|
|
||||||
minterms = [
|
|
||||||
float(x)
|
|
||||||
for x in input(
|
|
||||||
"Enter the decimal representation of Minterms 'Spaces Separated'\n"
|
|
||||||
).split()
|
|
||||||
]
|
|
||||||
binary = decimal_to_binary(no_of_variable, minterms)
|
|
||||||
|
|
||||||
prime_implicants = check(binary)
|
|
||||||
print("Prime Implicants are:")
|
|
||||||
print(prime_implicants)
|
|
||||||
chart = prime_implicant_chart(prime_implicants, binary)
|
|
||||||
|
|
||||||
essential_prime_implicants = selection(chart, prime_implicants)
|
|
||||||
print("Essential Prime Implicants are:")
|
|
||||||
print(essential_prime_implicants)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
||||||
main()
|
|
|
@ -1,37 +0,0 @@
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,37 +0,0 @@
|
||||||
"""
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import doctest
|
|
||||||
|
|
||||||
doctest.testmod()
|
|
|
@ -1,8 +0,0 @@
|
||||||
# Cellular Automata
|
|
||||||
|
|
||||||
Cellular automata are a way to simulate the behavior of "life", no matter if it is a robot or cell.
|
|
||||||
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>
|
|
|
@ -1,98 +0,0 @@
|
||||||
"""
|
|
||||||
Conway's Game of Life implemented in Python.
|
|
||||||
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
# Define glider example
|
|
||||||
GLIDER = [
|
|
||||||
[0, 1, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 1, 0, 0, 0, 0, 0],
|
|
||||||
[1, 1, 1, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
]
|
|
||||||
|
|
||||||
# Define blinker example
|
|
||||||
BLINKER = [[0, 1, 0], [0, 1, 0], [0, 1, 0]]
|
|
||||||
|
|
||||||
|
|
||||||
def new_generation(cells: list[list[int]]) -> list[list[int]]:
|
|
||||||
"""
|
|
||||||
Generates the next generation for a given state of Conway's Game of Life.
|
|
||||||
>>> new_generation(BLINKER)
|
|
||||||
[[0, 0, 0], [1, 1, 1], [0, 0, 0]]
|
|
||||||
"""
|
|
||||||
next_generation = []
|
|
||||||
for i in range(len(cells)):
|
|
||||||
next_generation_row = []
|
|
||||||
for j in range(len(cells[i])):
|
|
||||||
# Get the number of live neighbours
|
|
||||||
neighbour_count = 0
|
|
||||||
if i > 0 and j > 0:
|
|
||||||
neighbour_count += cells[i - 1][j - 1]
|
|
||||||
if i > 0:
|
|
||||||
neighbour_count += cells[i - 1][j]
|
|
||||||
if i > 0 and j < len(cells[i]) - 1:
|
|
||||||
neighbour_count += cells[i - 1][j + 1]
|
|
||||||
if j > 0:
|
|
||||||
neighbour_count += cells[i][j - 1]
|
|
||||||
if j < len(cells[i]) - 1:
|
|
||||||
neighbour_count += cells[i][j + 1]
|
|
||||||
if i < len(cells) - 1 and j > 0:
|
|
||||||
neighbour_count += cells[i + 1][j - 1]
|
|
||||||
if i < len(cells) - 1:
|
|
||||||
neighbour_count += cells[i + 1][j]
|
|
||||||
if i < len(cells) - 1 and j < len(cells[i]) - 1:
|
|
||||||
neighbour_count += cells[i + 1][j + 1]
|
|
||||||
|
|
||||||
# Rules of the game of life (excerpt from Wikipedia):
|
|
||||||
# 1. Any live cell with two or three live neighbours survives.
|
|
||||||
# 2. Any dead cell with three live neighbours becomes a live cell.
|
|
||||||
# 3. All other live cells die in the next generation.
|
|
||||||
# Similarly, all other dead cells stay dead.
|
|
||||||
alive = cells[i][j] == 1
|
|
||||||
if (
|
|
||||||
(alive and 2 <= neighbour_count <= 3)
|
|
||||||
or not alive
|
|
||||||
and neighbour_count == 3
|
|
||||||
):
|
|
||||||
next_generation_row.append(1)
|
|
||||||
else:
|
|
||||||
next_generation_row.append(0)
|
|
||||||
|
|
||||||
next_generation.append(next_generation_row)
|
|
||||||
return next_generation
|
|
||||||
|
|
||||||
|
|
||||||
def generate_images(cells: list[list[int]], frames: int) -> list[Image.Image]:
|
|
||||||
"""
|
|
||||||
Generates a list of images of subsequent Game of Life states.
|
|
||||||
"""
|
|
||||||
images = []
|
|
||||||
for _ in range(frames):
|
|
||||||
# Create output image
|
|
||||||
img = Image.new("RGB", (len(cells[0]), len(cells)))
|
|
||||||
pixels = img.load()
|
|
||||||
|
|
||||||
# Save cells to image
|
|
||||||
for x in range(len(cells)):
|
|
||||||
for y in range(len(cells[0])):
|
|
||||||
colour = 255 - cells[y][x] * 255
|
|
||||||
pixels[x, y] = (colour, colour, colour)
|
|
||||||
|
|
||||||
# Save image
|
|
||||||
images.append(img)
|
|
||||||
cells = new_generation(cells)
|
|
||||||
return images
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
images = generate_images(GLIDER, 16)
|
|
||||||
images[0].save("out.gif", save_all=True, append_images=images[1:])
|
|
|
@ -1,129 +0,0 @@
|
||||||
"""Conway's Game Of Life, Author Anurag Kumar(mailto:anuragkumarak95@gmail.com)
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
- numpy
|
|
||||||
- random
|
|
||||||
- time
|
|
||||||
- matplotlib
|
|
||||||
|
|
||||||
Python:
|
|
||||||
- 3.5
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
- $python3 game_of_life <canvas_size:int>
|
|
||||||
|
|
||||||
Game-Of-Life Rules:
|
|
||||||
|
|
||||||
1.
|
|
||||||
Any live cell with fewer than two live neighbours
|
|
||||||
dies, as if caused by under-population.
|
|
||||||
2.
|
|
||||||
Any live cell with two or three live neighbours lives
|
|
||||||
on to the next generation.
|
|
||||||
3.
|
|
||||||
Any live cell with more than three live neighbours
|
|
||||||
dies, as if by over-population.
|
|
||||||
4.
|
|
||||||
Any dead cell with exactly three live neighbours be-
|
|
||||||
comes a live cell, as if by reproduction.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import random
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
from matplotlib.colors import ListedColormap
|
|
||||||
|
|
||||||
usage_doc = "Usage of script: script_name <size_of_canvas:int>"
|
|
||||||
|
|
||||||
choice = [0] * 100 + [1] * 10
|
|
||||||
random.shuffle(choice)
|
|
||||||
|
|
||||||
|
|
||||||
def create_canvas(size: int) -> list[list[bool]]:
|
|
||||||
canvas = [[False for i in range(size)] for j in range(size)]
|
|
||||||
return canvas
|
|
||||||
|
|
||||||
|
|
||||||
def seed(canvas: list[list[bool]]) -> None:
|
|
||||||
for i, row in enumerate(canvas):
|
|
||||||
for j, _ in enumerate(row):
|
|
||||||
canvas[i][j] = bool(random.getrandbits(1))
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
@Args:
|
|
||||||
--
|
|
||||||
canvas : canvas of population to run the rules on.
|
|
||||||
|
|
||||||
@returns:
|
|
||||||
--
|
|
||||||
canvas of population after one step
|
|
||||||
"""
|
|
||||||
current_canvas = np.array(canvas)
|
|
||||||
next_gen_canvas = np.array(create_canvas(current_canvas.shape[0]))
|
|
||||||
for r, row in enumerate(current_canvas):
|
|
||||||
for c, pt in enumerate(row):
|
|
||||||
next_gen_canvas[r][c] = __judge_point(
|
|
||||||
pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2]
|
|
||||||
)
|
|
||||||
|
|
||||||
return next_gen_canvas.tolist()
|
|
||||||
|
|
||||||
|
|
||||||
def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool:
|
|
||||||
dead = 0
|
|
||||||
alive = 0
|
|
||||||
# finding dead or alive neighbours count.
|
|
||||||
for i in neighbours:
|
|
||||||
for status in i:
|
|
||||||
if status:
|
|
||||||
alive += 1
|
|
||||||
else:
|
|
||||||
dead += 1
|
|
||||||
|
|
||||||
# handling duplicate entry for focus pt.
|
|
||||||
if pt:
|
|
||||||
alive -= 1
|
|
||||||
else:
|
|
||||||
dead -= 1
|
|
||||||
|
|
||||||
# running the rules of game here.
|
|
||||||
state = pt
|
|
||||||
if pt:
|
|
||||||
if alive < 2:
|
|
||||||
state = False
|
|
||||||
elif alive in {2, 3}:
|
|
||||||
state = True
|
|
||||||
elif alive > 3:
|
|
||||||
state = False
|
|
||||||
elif alive == 3:
|
|
||||||
state = True
|
|
||||||
|
|
||||||
return state
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
raise Exception(usage_doc)
|
|
||||||
|
|
||||||
canvas_size = int(sys.argv[1])
|
|
||||||
# main working structure of this module.
|
|
||||||
c = create_canvas(canvas_size)
|
|
||||||
seed(c)
|
|
||||||
fig, ax = plt.subplots()
|
|
||||||
fig.show()
|
|
||||||
cmap = ListedColormap(["w", "k"])
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
c = run(c)
|
|
||||||
ax.matshow(c, cmap=cmap)
|
|
||||||
fig.canvas.draw()
|
|
||||||
ax.cla()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
# do nothing.
|
|
||||||
pass
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user