Compare commits

..

57 Commits

Author SHA1 Message Date
Arijit De
de92865043
Merge branch 'TheAlgorithms:master' into master 2023-08-04 15:59:39 +05:30
AmirSoroush
ce218c57f1
fixes #8673; Add operator's associativity check for stacks/infix_to_p… (#8674)
* fixes #8673; Add operator's associativity check for stacks/infix_to_postfix_conversion.py

* fix ruff N806 in stacks/infix_to_postfix_conversion.py

* Update data_structures/stacks/infix_to_postfix_conversion.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* Update data_structures/stacks/infix_to_postfix_conversion.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
2023-08-01 11:23:34 -07:00
pre-commit-ci[bot]
c9a7234a95
[pre-commit.ci] pre-commit autoupdate (#8914)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.280 → v0.0.281](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.280...v0.0.281)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-08-01 09:26:23 +05:30
Jan Wojciechowski
f7c5e55609
Window closing fix (#8625)
* The window will now remain open after the fractal is finished being drawn, and will only close upon your click.

* Update fractals/sierpinski_triangle.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-31 20:02:49 -07:00
Minha, Jeong
f8fe72dc37
Update game_of_life.py (#4921)
* Update game_of_life.py

docstring error fix
delete no reason delete next_gen_canvas code(local variable)

* Update cellular_automata/game_of_life.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-31 14:24:12 -07:00
Tianyi Zheng
5cf34d901e
Ruff fixes (#8913)
* updating DIRECTORY.md

* Fix ruff error in eulerian_path_and_circuit_for_undirected_graph.py

* Fix ruff error in newtons_second_law_of_motion.py

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-07-31 13:53:26 -07:00
Dylan Buchi
90a8e6e0d2
Update sorts/bubble_sort.py (#5802)
* Add missing type annotations in bubble_sort.py

* Refactor bubble_sort function
2023-07-31 11:50:00 -07:00
roger-sato
0b0214c42f
Handle empty input case in Segment Tree build process (#8718) 2023-07-31 11:46:30 -07:00
Tianyi Zheng
629eb86ce0
Fix merge conflicts to merge change from #5080 (#8911)
* Input for user choose his Collatz sequence

Now the user can tell the algorithm what number he wants to run on the Collatz Sequence.

* updating DIRECTORY.md

---------

Co-authored-by: Hugo Folloni <hugofollogua07@gmail.com>
Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-07-31 07:23:23 +02:00
AmirSoroush
384c407a26
Enhance the implementation of Queue using list (#8608)
* enhance the implementation of queue using list

* enhance readability of queue_on_list.py

* rename 'queue_on_list' to 'queue_by_list' to match the class name
2023-07-30 19:07:35 -07:00
Almas Bekbayev
8cce9cf066
Fix linear_search docstring return value (#8644) 2023-07-30 18:32:05 -07:00
David Leal
4710e51deb
chore: use newest Discord invite link (#8696)
* updating DIRECTORY.md

* chore: use newest Discord invite link

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-07-30 18:15:30 -07:00
AmirSoroush
d4f2873e39
add reverse_inorder traversal to binary_tree_traversals.py (#8726)
* add reverse_inorder traversal to binary_tree_traversals.py

* Apply suggestions from code review

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

---------

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
2023-07-30 17:54:15 -07:00
Bazif Rasool
8b831cb600
Added Altitude Pressure equation (#8909)
* Added Altitude Pressure equation

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Removed trailing whitespaces

* Removed pylint

* Fix lru_cache_pythonic.py

* Fixed spellings

* Fix again lru_cache_pythonic.py

* Update .vscode/settings.json

Co-authored-by: Christian Clauss <cclauss@me.com>

* Third fix lru_cache_pythonic.py

* Update .vscode/settings.json

Co-authored-by: Christian Clauss <cclauss@me.com>

* 4th fix lru_cache_pythonic.py

* Update physics/altitude_pressure.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* lru_cache_pythonic.py: def get(self, key: Any, /) -> Any | None:

* Delete lru_cache_pythonic.py

* Added positive and negative pressure test cases

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-30 17:00:58 +02:00
Yatharth Mathur
d31750adec
Pythonic implementation of LRU Cache (#4630)
* Added a more pythonic implementation of LRU_Cache.[#4628]

* Added test cases and doctest

* Fixed doc tests

* Added more tests in doctests and fixed return types fixes [#4628]

* better doctests

* added doctests to main()

* Added dutch_national_flag.py in sorts. fixing [#4636]

* Delete dutch_national_flag.py

incorrect commit

* Update lru_cache_pythonic.py

* Remove pontification

---------

Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-30 11:27:45 +02:00
Colin Leroy-Mira
2cfef0913a
Fix greyscale computation and inverted coords (#8905)
* Fix greyscale computation and inverted coords

* Fix test

* Add test cases

* Add reference to the greyscaling formula

---------

Co-authored-by: Colin Leroy-Mira <colin.leroy-mira@sigfox.com>
2023-07-29 10:03:43 -07:00
Tianyi Zheng
0ef9306976
Disable quantum/quantum_random.py (attempt 2) (#8902)
* Disable quantum/quantum_random.py

Temporarily disable quantum/quantum_random.py because it produces an illegal instruction error that causes all builds to fail

* updating DIRECTORY.md

* Disable quantum/quantum_random.py attempt 2

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-07-28 22:08:40 +02:00
Alex Bernhardt
a0b642cfe5
Physics/basic orbital capture (#8857)
* Added file basic_orbital_capture

* updating DIRECTORY.md

* added second source

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixed spelling errors

* accepted changes

* updating DIRECTORY.md

* corrected spelling error

* Added file basic_orbital_capture

* added second source

* fixed spelling errors

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* applied changes

* reviewed and checked file

* added doctest

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* removed redundant constnant

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added scipy imports

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added doctests to capture_radii and scipy const

* fixed conflicts

* finalizing file. Added tests

* Update physics/basic_orbital_capture.py

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-28 20:30:05 +02:00
Tianyi Zheng
e406801f9e
Reimplement polynomial_regression.py (#8889)
* Reimplement polynomial_regression.py

Rename machine_learning/polymonial_regression.py to
machine_learning/polynomial_regression.py

Reimplement machine_learning/polynomial_regression.py using numpy
because the old original implementation was just a how-to on doing
polynomial regression using sklearn

Add detailed function documentation, doctests, and algorithm
explanation

* updating DIRECTORY.md

* Fix matrix formatting in docstrings

* Try to fix failing doctest

* Debugging failing doctest

* Fix failing doctest attempt 2

* Remove unnecessary return value descriptions in docstrings

* Readd placeholder doctest for main function

* Fix typo in algorithm description

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-07-28 20:17:46 +02:00
Caeden Perelli-Harris
4a83e3f0b1
Fix failing build due to missing requirement (#8900)
* feat(cellular_automata): Create wa-tor algorithm

* updating DIRECTORY.md

* chore(quality): Implement algo-keeper bot changes

* build: Fix broken ci

* git rm cellular_automata/wa_tor.py

* updating DIRECTORY.md

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-28 20:12:31 +02:00
Christian Clauss
46454e204c
[skip-ci] In .devcontainer/Dockerfile: pipx install pre-commit ruff (#8893)
[skip-ci] In .devcontainer/Dockerfile: pipx install pre-commit ruff
2023-07-28 18:54:45 +02:00
Christian Clauss
dbaff34572
Fix ruff rules ISC flake8-implicit-str-concat (#8892) 2023-07-28 17:53:09 +01:00
HManiac74
b77e6adf3a
Add Docker devcontainer configuration files (#8887)
* Added Docker container configuration files

* Update Dockerfile

Copy and install requirements

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Updated Docker devcontainer configuration

* Update requierements.txt

* Update Dockerfile

* Update Dockerfile

* Update .devcontainer/devcontainer.json

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update Dockerfile

* Update Dockerfile. Add linebreak

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-25 22:23:20 +02:00
Sangmin Jeon
a03b739d23
Fix radix_tree.py insertion fail in ["*X", "*XX"] cases (#8870)
* Fix insertion fail in ["*X", "*XX"] cases

Consider a word, and a copy of that word, but with the last letter repeating twice. (e.g., ["ABC", "ABCC"])
When adding the second word's last letter, it only compares the previous word's prefix—the last letter of the word already in the Radix Tree: 'C'—and the letter to be added—the last letter of the word we're currently adding: 'C'. So it wrongly passes the "Case 1" check, marks the current node as a leaf node when it already was, then returns when there's still one more letter to add.
The issue arises because `prefix` includes the letter of the node itself. (e.g., `nodes: {'C' : RadixNode()}, is_leaf: True, prefix: 'C'`) It can be easily fixed by simply adding the `is_leaf` check, asking if there are more letters to be added.

- Test Case: `"A AA AAA AAAA"`
  - Fixed correct output:
  ```
  Words: ['A', 'AA', 'AAA', 'AAAA']
  Tree:
  - A   (leaf)
  -- A   (leaf)
  --- A   (leaf)
  ---- A   (leaf)
  ```
  - Current incorrect output:
  ```
  Words: ['A', 'AA', 'AAA', 'AAAA']
  Tree:
  - A   (leaf)
  -- AA   (leaf)
  --- A   (leaf)
  ```

*N.B.* This passed test cases for [Croatian Open Competition in Informatics 2012/2013 Contest #3 Task 5 HERKABE](https://hsin.hr/coci/archive/2012_2013/)

* Add a doctest for previous fix

* improve doctest readability
2023-07-24 11:29:05 +02:00
Caeden Perelli-Harris
9e08c7726d
Small docstring time complexity fix in number_container _system (#8875)
* fix: Write time is O(log n) not O(n log n)

* chore: Update pre-commit ruff version

* revert: Undo previous commit
2023-07-22 12:34:19 +02:00
Tianyi Zheng
f7531d9874
Add note in CONTRIBUTING.md about not asking to be assigned to issues (#8871)
* Add note in CONTRIBUTING.md about not asking to be assigned to issues

Add a paragraph to CONTRIBUTING.md explicitly asking contributors to not ask to be assigned to issues

* Update CONTRIBUTING.md

* Update CONTRIBUTING.md

---------

Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-22 12:11:04 +02:00
Caeden Perelli-Harris
93fb169627
[Upgrade Ruff] Fix all errors raised from ruff (#8879)
* chore: Fix tests

* chore: Fix failing ruff

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* chore: Fix ruff errors

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* chore: Fix ruff errors

* chore: Fix ruff errors

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update cellular_automata/game_of_life.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* chore: Update ruff version in pre-commit

* chore: Fix ruff errors

* Update edmonds_karp_multiple_source_and_sink.py

* Update factorial.py

* Update primelib.py

* Update min_cost_string_conversion.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-22 12:05:10 +02:00
pre-commit-ci[bot]
5aefc00f0f
[pre-commit.ci] pre-commit autoupdate (#8872)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.277 → v0.0.278](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.277...v0.0.278)
- [github.com/psf/black: 23.3.0 → 23.7.0](https://github.com/psf/black/compare/23.3.0...23.7.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-18 09:58:22 +05:30
pre-commit-ci[bot]
f614ed7217
[pre-commit.ci] pre-commit autoupdate (#8860)
updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.276 → v0.0.277](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.276...v0.0.277)
- [github.com/tox-dev/pyproject-fmt: 0.12.1 → 0.13.0](https://github.com/tox-dev/pyproject-fmt/compare/0.12.1...0.13.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-11 11:55:32 +02:00
Caeden Perelli-Harris
44b1bcc7c7
Fix failing tests from ruff/newton_raphson (ignore S307 "possibly insecure function") (#8862)
* chore: Fix failing tests (ignore S307 "possibly insecure function")

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix: Move noqa back to right line

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-11 11:51:21 +02:00
Tianyi Zheng
a0eec90466
Consolidate duplicate implementations of max subarray (#8849)
* Remove max subarray sum duplicate implementations

* updating DIRECTORY.md

* Rename max_sum_contiguous_subsequence.py

* Fix typo in dynamic_programming/max_subarray_sum.py

* Remove duplicate divide and conquer max subarray

* updating DIRECTORY.md

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-07-11 11:44:12 +02:00
pre-commit-ci[bot]
c9ee6ed188
[pre-commit.ci] pre-commit autoupdate (#8853)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.275 → v0.0.276](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.275...v0.0.276)

* Update double_ended_queue.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update double_ended_queue.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-07-04 00:20:35 +02:00
pre-commit-ci[bot]
929d3d9219
[pre-commit.ci] pre-commit autoupdate (#8842)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.0.274 → v0.0.275](https://github.com/astral-sh/ruff-pre-commit/compare/v0.0.274...v0.0.275)
- [github.com/tox-dev/pyproject-fmt: 0.12.0 → 0.12.1](https://github.com/tox-dev/pyproject-fmt/compare/0.12.0...0.12.1)
- [github.com/pre-commit/mirrors-mypy: v1.3.0 → v1.4.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.3.0...v1.4.1)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-06-27 07:23:54 +02:00
Tianyi Zheng
69f20033e5
Remove duplicate implementation of Collatz sequence (#8836)
* updating DIRECTORY.md

* Remove duplicate implementation of Collatz sequence

* updating DIRECTORY.md

* Add suggestions from PR review

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-06-26 11:15:31 +02:00
duongoku
62dcbea943
Add power sum problem (#8832)
* Add powersum problem

* Add doctest

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add more doctests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add more doctests

* Improve paramater name

* Fix line too long

* Remove global variables

* Apply suggestions from code review

* Apply suggestions from code review

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-06-26 09:39:18 +02:00
Christian Clauss
d764eec655
Fix failing pytest quantum/bb84.py (#8838)
* Fix failing pytest quantum/bb84.py

* Update bb84.py test results to match current qiskit
2023-06-26 08:54:50 +05:30
Christian Clauss
3bfa89dacf
GitHub Actions build: Add more tests (#8837)
* GitHub Actions build: Add more tests

Re-enable some tests that were disabled in #6591.
Fixes #8818

* updating DIRECTORY.md

* TODO: Re-enable quantum tests

* fails: pytest quantum/bb84.py quantum/q_fourier_transform.py

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-06-25 21:58:01 +05:30
Tianyi Zheng
267a8b72f9
Clarify how to add issue numbers in PR template and CONTRIBUTING.md (#8833)
* updating DIRECTORY.md

* Clarify wording in PR template

* Clarify CONTRIBUTING.md wording about adding issue numbers

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add suggested change from review to CONTRIBUTING.md

Co-authored-by: Christian Clauss <cclauss@me.com>

* Incorporate review edit to CONTRIBUTING.md

Co-authored-by: Christian Clauss <cclauss@me.com>

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-06-23 15:56:58 +02:00
Himanshu Tomar
331585f3f8
Algorithm: Calculating Product Sum from a Special Array with Nested Structures (#8761)
* Added minimum waiting time problem solution using greedy algorithm

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ruff --fix

* Add type hints

* Added two more doc test

* Removed unnecessary comments

* updated type hints

* Updated the code as per the code review

* Added recursive algo to calculate product sum from an array

* Added recursive algo to calculate product sum from an array

* Update doc string

* Added doctest for product_sum function

* Updated the code and added more doctests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added more test coverage for product_sum method

* Update product_sum.py

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-06-23 10:26:05 +02:00
Jan-Lukas Huhn
f54a966810
Energy conversions (#8801)
* Create TestShiva

* Delete TestShiva

* Create energy_conversions.py

* Update conversions/energy_conversions.py

Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>

---------

Co-authored-by: ShivaDahal99 <130563462+ShivaDahal99@users.noreply.github.com>
Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>
2023-06-22 14:31:48 +02:00
Tianyi Zheng
5ffe601c86
Fix mypy errors in maths/sigmoid_linear_unit.py (#8786)
* updating DIRECTORY.md

* Fix mypy errors in sigmoid_linear_unit.py

* updating DIRECTORY.md

* updating DIRECTORY.md

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-06-22 14:24:34 +02:00
Linus M. Henkel
5b0890bd83
Dijkstra algorithm with binary grid (#8802)
* Create TestShiva

* Delete TestShiva

* Implementation of the Dijkstra-Algorithm in a binary grid

* Update double_ended_queue.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update least_common_multiple.py

* Update sol1.py

* Update pyproject.toml

* Update pyproject.toml

* https://github.com/astral-sh/ruff-pre-commit v0.0.274

---------

Co-authored-by: ShivaDahal99 <130563462+ShivaDahal99@users.noreply.github.com>
Co-authored-by: jlhuhn <134317018+jlhuhn@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-22 13:49:09 +02:00
Christian Clauss
07e6812888
Update .pre-commit-config.yaml (#8828)
* Update .pre-commit-config.yaml

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-20 21:33:16 +05:30
pre-commit-ci[bot]
0dee4a402c
[pre-commit.ci] pre-commit autoupdate (#8827)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/codespell-project/codespell: v2.2.4 → v2.2.5](https://github.com/codespell-project/codespell/compare/v2.2.4...v2.2.5)
- [github.com/tox-dev/pyproject-fmt: 0.11.2 → 0.12.0](https://github.com/tox-dev/pyproject-fmt/compare/0.11.2...0.12.0)

* updating DIRECTORY.md

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
2023-06-20 15:56:14 +02:00
Turro
ea6c6056cf
Added apr_interest function to financial (#6025)
* Added apr_interest function to financial

* Update interest.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update financial/interest.py

* float

---------

Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-19 13:46:29 +02:00
Frank-1998
b0f871032e
Fix removing the root node in binary_search_tree.py removes the whole tree (#8752)
* fix issue #8715

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-18 18:30:06 +02:00
Ilkin Mengusoglu
e6f89a6b89
Simplex algorithm (#8825)
* feat: added simplex.py

* added docstrings

* Update linear_programming/simplex.py

Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>

* Update linear_programming/simplex.py

Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update linear_programming/simplex.py

Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* ruff fix

Co-authored by: CaedenPH <caedenperelliharris@gmail.com>

* removed README to add in separate PR

* Update linear_programming/simplex.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* Update linear_programming/simplex.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

* fix class docstring

* add comments

---------

Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
2023-06-18 18:00:02 +02:00
pre-commit-ci[bot]
4637986125
[pre-commit.ci] pre-commit autoupdate (#8817)
updates:
- [github.com/charliermarsh/ruff-pre-commit: v0.0.270 → v0.0.272](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.270...v0.0.272)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-13 00:09:33 +02:00
Caeden Perelli-Harris
daa0c8f3d3
Create count negative numbers in matrix algorithm (#8813)
* updating DIRECTORY.md

* feat: Count negative numbers in sorted matrix

* updating DIRECTORY.md

* chore: Fix pre-commit

* refactor: Combine functions into iteration

* style: Reformat reference

* feat: Add timings of each implementation

* chore: Fix problems with algorithms-keeper bot

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* test: Remove doctest from benchmark function

* Update matrix/count_negative_numbers_in_sorted_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update matrix/count_negative_numbers_in_sorted_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update matrix/count_negative_numbers_in_sorted_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update matrix/count_negative_numbers_in_sorted_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update matrix/count_negative_numbers_in_sorted_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update matrix/count_negative_numbers_in_sorted_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* refactor: Use sum instead of large iteration

* refactor: Use len not sum

* Update count_negative_numbers_in_sorted_matrix.py

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-06-10 14:21:49 +02:00
Jan Wojciechowski
9c9da8ebf1
Improve readability of ciphers/mixed_keyword_cypher.py (#8626)
* refactored the code

* the code will now pass the test

* looked more into it and fixed the logic

* made the code easier to read, added comments and fixed the logic

* got rid of redundant code + plaintext can contain chars that are not in the alphabet

* fixed the reduntant conversion of ascii_uppercase to a list

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* keyword and plaintext won't have default values

* ran the ruff command

* Update linear_discriminant_analysis.py and rsa_cipher.py (#8680)

* Update rsa_cipher.py by replacing %s with {}

* Update rsa_cipher.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update linear_discriminant_analysis.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update linear_discriminant_analysis.py

* Update linear_discriminant_analysis.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update linear_discriminant_analysis.py

* Update linear_discriminant_analysis.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update linear_discriminant_analysis.py

* Update machine_learning/linear_discriminant_analysis.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update linear_discriminant_analysis.py

* updated

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>

* fixed some difficulties

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added comments, made printing mapping optional, added 1 test

* shortened the line that was too long

* Update ciphers/mixed_keyword_cypher.py

Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Rohan Anand <96521078+rohan472000@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: Tianyi Zheng <tianyizheng02@gmail.com>
2023-06-09 11:06:37 +02:00
Caeden Perelli-Harris
7775de0ef7
Create number container system algorithm (#8808)
* feat: Create number container system algorithm

* updating DIRECTORY.md

* chore: Fix failing tests

* Update other/number_container_system.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update other/number_container_system.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update other/number_container_system.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* chore: Add more tests

* chore: Create binary_search_insert failing test

* type: Update typehints to accept str, list and range

---------

Co-authored-by: github-actions <${GITHUB_ACTOR}@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-06-08 14:40:38 +02:00
ShivaDahal99
fa12b9a286
Speed of sound (#8803)
* Create TestShiva

* Delete TestShiva

* Add speed of sound

* Update physics/speed_of_sound.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update physics/speed_of_sound.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update speed_of_sound.py

* Update speed_of_sound.py

---------

Co-authored-by: jlhuhn <134317018+jlhuhn@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-06-07 23:47:27 +02:00
Caeden Perelli-Harris
80d95fccc3
Pytest locally fails due to API_KEY env variable (#8738)
* fix: Pytest locally fails due to API_KEY env variable (#8737)

* chore: Fix ruff errors
2023-06-03 18:16:33 +02:00
Chris O
3a9e5fa5ec
Create a Simultaneous Equation Solver Algorithm (#8773)
* Added simultaneous_linear_equation_solver.py

* Removed Augment class, replaced with recursive functions

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixed edge cases

* Update settings.json

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2023-06-02 07:14:25 +02:00
nith2001
4621b0bb4f
Improved Graph Implementations (#8730)
* Improved Graph Implementations

Provides new implementation for graph_list.py and graph_matrix.py along with pytest suites for each. Fixes #8709

* Graph implementation style fixes, corrections, and refactored tests

* Helpful docs about graph implementation

* Refactored code to separate files and applied enumerate()

* Renamed files and refactored code to fail fast

* Error handling style fix

* Fixed f-string code quality issue

* Last f-string fix

* Added return types to test functions and more style fixes

* Added more function return types

* Added more function return types pt2

* Fixed error messages
2023-05-31 22:06:12 +02:00
Rudransh Bhardwaj
e871540e37
Added rank of matrix in linear algebra (#8687)
* Added rank of matrix in linear algebra

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Corrected name of function

* Corrected Rank_of_Matrix.py

* Completed rank_of_matrix.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* delete to rename Rank_of_Matrix.py

* created rank_of_matrix

* added more doctests in rank_of_matrix.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fixed some issues in rank_of_matrix.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* added moreeee doctestsss in rank_of_mtrix.py and fixed some bugss

* Update linear_algebra/src/rank_of_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update linear_algebra/src/rank_of_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update linear_algebra/src/rank_of_matrix.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update rank_of_matrix.py

* Update linear_algebra/src/rank_of_matrix.py

Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
Co-authored-by: Caeden Perelli-Harris <caedenperelliharris@gmail.com>
2023-05-31 17:03:02 +02:00
Sundaram Kumar Jha
4a27b54430
Update permutations.py (#8102) 2023-05-31 12:56:59 +12:00
80 changed files with 3847 additions and 917 deletions

8
.devcontainer/Dockerfile Normal file
View File

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

View File

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

View File

@ -17,4 +17,4 @@
* [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html). * [ ] All function parameters and return values are annotated with Python [type hints](https://docs.python.org/3/library/typing.html).
* [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing. * [ ] All functions have [doctests](https://docs.python.org/3/library/doctest.html) that pass the automated testing.
* [ ] All new algorithms include at least one URL that points to Wikipedia or another similar explanation. * [ ] All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
* [ ] If this pull request resolves one or more open issues then the commit message contains `Fixes: #{$ISSUE_NO}`. * [ ] If this pull request resolves one or more open issues then the description above includes the issue number(s) with a [closing keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue): "Fixes #ISSUE-NUMBER".

View File

@ -22,11 +22,9 @@ jobs:
python -m pip install --upgrade pip setuptools six wheel python -m pip install --upgrade pip setuptools six wheel
python -m pip install pytest-cov -r requirements.txt python -m pip install pytest-cov -r requirements.txt
- name: Run tests - name: Run tests
# See: #6591 for re-enabling tests on Python v3.11 # TODO: #8818 Re-enable quantum tests
run: pytest run: pytest
--ignore=computer_vision/cnn_classification.py --ignore=quantum/q_fourier_transform.py
--ignore=machine_learning/lstm/lstm_prediction.py
--ignore=quantum/
--ignore=project_euler/ --ignore=project_euler/
--ignore=scripts/validate_solutions.py --ignore=scripts/validate_solutions.py
--cov-report=term-missing:skip-covered --cov-report=term-missing:skip-covered

View File

@ -15,25 +15,25 @@ repos:
hooks: hooks:
- id: auto-walrus - id: auto-walrus
- repo: https://github.com/charliermarsh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.270 rev: v0.0.281
hooks: hooks:
- id: ruff - id: ruff
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 23.3.0 rev: 23.7.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.2.4 rev: v2.2.5
hooks: hooks:
- id: codespell - id: codespell
additional_dependencies: additional_dependencies:
- tomli - tomli
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: "0.11.2" rev: "0.13.0"
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt
@ -51,7 +51,7 @@ repos:
- id: validate-pyproject - id: validate-pyproject
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.3.0 rev: v1.4.1
hooks: hooks:
- id: mypy - id: mypy
args: args:

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

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

View File

@ -25,7 +25,14 @@ We appreciate any contribution, from fixing a grammar mistake in a comment to im
Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the GitHub Actions output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help. Your contribution will be tested by our [automated testing on GitHub Actions](https://github.com/TheAlgorithms/Python/actions) to save time and mental energy. After you have submitted your pull request, you should see the GitHub Actions tests start to run at the bottom of your submission page. If those tests fail, then click on the ___details___ button try to read through the GitHub Actions output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help.
Please help us keep our issue list small by adding fixes: #{$ISSUE_NO} to the commit message of pull requests that resolve open issues. GitHub will use this tag to auto-close the issue when the PR is merged. If you are interested in resolving an [open issue](https://github.com/TheAlgorithms/Python/issues), simply make a pull request with your proposed fix. __We do not assign issues in this repo__ so please do not ask for permission to work on an issue.
Please help us keep our issue list small by adding `Fixes #{$ISSUE_NUMBER}` to the description of pull requests that resolve open issues.
For example, if your pull request fixes issue #10, then please add the following to its description:
```
Fixes #10
```
GitHub will use this tag to [auto-close the issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if and when the PR is merged.
#### What is an Algorithm? #### What is an Algorithm?

View File

@ -29,6 +29,7 @@
* [Minmax](backtracking/minmax.py) * [Minmax](backtracking/minmax.py)
* [N Queens](backtracking/n_queens.py) * [N Queens](backtracking/n_queens.py)
* [N Queens Math](backtracking/n_queens_math.py) * [N Queens Math](backtracking/n_queens_math.py)
* [Power Sum](backtracking/power_sum.py)
* [Rat In Maze](backtracking/rat_in_maze.py) * [Rat In Maze](backtracking/rat_in_maze.py)
* [Sudoku](backtracking/sudoku.py) * [Sudoku](backtracking/sudoku.py)
* [Sum Of Subsets](backtracking/sum_of_subsets.py) * [Sum Of Subsets](backtracking/sum_of_subsets.py)
@ -146,6 +147,7 @@
* [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py) * [Decimal To Binary Recursion](conversions/decimal_to_binary_recursion.py)
* [Decimal To Hexadecimal](conversions/decimal_to_hexadecimal.py) * [Decimal To Hexadecimal](conversions/decimal_to_hexadecimal.py)
* [Decimal To Octal](conversions/decimal_to_octal.py) * [Decimal To Octal](conversions/decimal_to_octal.py)
* [Energy Conversions](conversions/energy_conversions.py)
* [Excel Title To Column](conversions/excel_title_to_column.py) * [Excel Title To Column](conversions/excel_title_to_column.py)
* [Hex To Bin](conversions/hex_to_bin.py) * [Hex To Bin](conversions/hex_to_bin.py)
* [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py) * [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py)
@ -166,6 +168,7 @@
* Arrays * Arrays
* [Permutations](data_structures/arrays/permutations.py) * [Permutations](data_structures/arrays/permutations.py)
* [Prefix Sum](data_structures/arrays/prefix_sum.py) * [Prefix Sum](data_structures/arrays/prefix_sum.py)
* [Product Sum](data_structures/arrays/product_sum.py)
* Binary Tree * Binary Tree
* [Avl Tree](data_structures/binary_tree/avl_tree.py) * [Avl Tree](data_structures/binary_tree/avl_tree.py)
* [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py) * [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py)
@ -233,8 +236,8 @@
* [Double Ended Queue](data_structures/queue/double_ended_queue.py) * [Double Ended Queue](data_structures/queue/double_ended_queue.py)
* [Linked Queue](data_structures/queue/linked_queue.py) * [Linked Queue](data_structures/queue/linked_queue.py)
* [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py) * [Priority Queue Using List](data_structures/queue/priority_queue_using_list.py)
* [Queue By List](data_structures/queue/queue_by_list.py)
* [Queue By Two Stacks](data_structures/queue/queue_by_two_stacks.py) * [Queue By Two Stacks](data_structures/queue/queue_by_two_stacks.py)
* [Queue On List](data_structures/queue/queue_on_list.py)
* [Queue On Pseudo Stack](data_structures/queue/queue_on_pseudo_stack.py) * [Queue On Pseudo Stack](data_structures/queue/queue_on_pseudo_stack.py)
* Stacks * Stacks
* [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py) * [Balanced Parentheses](data_structures/stacks/balanced_parentheses.py)
@ -290,7 +293,7 @@
* [Inversions](divide_and_conquer/inversions.py) * [Inversions](divide_and_conquer/inversions.py)
* [Kth Order Statistic](divide_and_conquer/kth_order_statistic.py) * [Kth Order Statistic](divide_and_conquer/kth_order_statistic.py)
* [Max Difference Pair](divide_and_conquer/max_difference_pair.py) * [Max Difference Pair](divide_and_conquer/max_difference_pair.py)
* [Max Subarray Sum](divide_and_conquer/max_subarray_sum.py) * [Max Subarray](divide_and_conquer/max_subarray.py)
* [Mergesort](divide_and_conquer/mergesort.py) * [Mergesort](divide_and_conquer/mergesort.py)
* [Peak](divide_and_conquer/peak.py) * [Peak](divide_and_conquer/peak.py)
* [Power](divide_and_conquer/power.py) * [Power](divide_and_conquer/power.py)
@ -321,8 +324,7 @@
* [Matrix Chain Order](dynamic_programming/matrix_chain_order.py) * [Matrix Chain Order](dynamic_programming/matrix_chain_order.py)
* [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py) * [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py)
* [Max Product Subarray](dynamic_programming/max_product_subarray.py) * [Max Product Subarray](dynamic_programming/max_product_subarray.py)
* [Max Sub Array](dynamic_programming/max_sub_array.py) * [Max Subarray Sum](dynamic_programming/max_subarray_sum.py)
* [Max Sum Contiguous Subsequence](dynamic_programming/max_sum_contiguous_subsequence.py)
* [Min Distance Up Bottom](dynamic_programming/min_distance_up_bottom.py) * [Min Distance Up Bottom](dynamic_programming/min_distance_up_bottom.py)
* [Minimum Coin Change](dynamic_programming/minimum_coin_change.py) * [Minimum Coin Change](dynamic_programming/minimum_coin_change.py)
* [Minimum Cost Path](dynamic_programming/minimum_cost_path.py) * [Minimum Cost Path](dynamic_programming/minimum_cost_path.py)
@ -410,6 +412,7 @@
* [Dijkstra 2](graphs/dijkstra_2.py) * [Dijkstra 2](graphs/dijkstra_2.py)
* [Dijkstra Algorithm](graphs/dijkstra_algorithm.py) * [Dijkstra Algorithm](graphs/dijkstra_algorithm.py)
* [Dijkstra Alternate](graphs/dijkstra_alternate.py) * [Dijkstra Alternate](graphs/dijkstra_alternate.py)
* [Dijkstra Binary Grid](graphs/dijkstra_binary_grid.py)
* [Dinic](graphs/dinic.py) * [Dinic](graphs/dinic.py)
* [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py) * [Directed And Undirected (Weighted) Graph](graphs/directed_and_undirected_(weighted)_graph.py)
* [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py) * [Edmonds Karp Multiple Source And Sink](graphs/edmonds_karp_multiple_source_and_sink.py)
@ -419,8 +422,9 @@
* [Frequent Pattern Graph Miner](graphs/frequent_pattern_graph_miner.py) * [Frequent Pattern Graph Miner](graphs/frequent_pattern_graph_miner.py)
* [G Topological Sort](graphs/g_topological_sort.py) * [G Topological Sort](graphs/g_topological_sort.py)
* [Gale Shapley Bigraph](graphs/gale_shapley_bigraph.py) * [Gale Shapley Bigraph](graphs/gale_shapley_bigraph.py)
* [Graph Adjacency List](graphs/graph_adjacency_list.py)
* [Graph Adjacency Matrix](graphs/graph_adjacency_matrix.py)
* [Graph List](graphs/graph_list.py) * [Graph List](graphs/graph_list.py)
* [Graph Matrix](graphs/graph_matrix.py)
* [Graphs Floyd Warshall](graphs/graphs_floyd_warshall.py) * [Graphs Floyd Warshall](graphs/graphs_floyd_warshall.py)
* [Greedy Best First](graphs/greedy_best_first.py) * [Greedy Best First](graphs/greedy_best_first.py)
* [Greedy Min Vertex Cover](graphs/greedy_min_vertex_cover.py) * [Greedy Min Vertex Cover](graphs/greedy_min_vertex_cover.py)
@ -479,11 +483,15 @@
* [Lib](linear_algebra/src/lib.py) * [Lib](linear_algebra/src/lib.py)
* [Polynom For Points](linear_algebra/src/polynom_for_points.py) * [Polynom For Points](linear_algebra/src/polynom_for_points.py)
* [Power Iteration](linear_algebra/src/power_iteration.py) * [Power Iteration](linear_algebra/src/power_iteration.py)
* [Rank Of Matrix](linear_algebra/src/rank_of_matrix.py)
* [Rayleigh Quotient](linear_algebra/src/rayleigh_quotient.py) * [Rayleigh Quotient](linear_algebra/src/rayleigh_quotient.py)
* [Schur Complement](linear_algebra/src/schur_complement.py) * [Schur Complement](linear_algebra/src/schur_complement.py)
* [Test Linear Algebra](linear_algebra/src/test_linear_algebra.py) * [Test Linear Algebra](linear_algebra/src/test_linear_algebra.py)
* [Transformations 2D](linear_algebra/src/transformations_2d.py) * [Transformations 2D](linear_algebra/src/transformations_2d.py)
## Linear Programming
* [Simplex](linear_programming/simplex.py)
## Machine Learning ## Machine Learning
* [Astar](machine_learning/astar.py) * [Astar](machine_learning/astar.py)
* [Data Transformations](machine_learning/data_transformations.py) * [Data Transformations](machine_learning/data_transformations.py)
@ -503,7 +511,7 @@
* Lstm * Lstm
* [Lstm Prediction](machine_learning/lstm/lstm_prediction.py) * [Lstm Prediction](machine_learning/lstm/lstm_prediction.py)
* [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py) * [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
* [Polymonial Regression](machine_learning/polymonial_regression.py) * [Polynomial Regression](machine_learning/polynomial_regression.py)
* [Scoring Functions](machine_learning/scoring_functions.py) * [Scoring Functions](machine_learning/scoring_functions.py)
* [Self Organizing Map](machine_learning/self_organizing_map.py) * [Self Organizing Map](machine_learning/self_organizing_map.py)
* [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py) * [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py)
@ -514,7 +522,6 @@
* [Xgboost Regressor](machine_learning/xgboost_regressor.py) * [Xgboost Regressor](machine_learning/xgboost_regressor.py)
## Maths ## Maths
* [3N Plus 1](maths/3n_plus_1.py)
* [Abs](maths/abs.py) * [Abs](maths/abs.py)
* [Add](maths/add.py) * [Add](maths/add.py)
* [Addition Without Arithmetic](maths/addition_without_arithmetic.py) * [Addition Without Arithmetic](maths/addition_without_arithmetic.py)
@ -583,12 +590,10 @@
* [Is Square Free](maths/is_square_free.py) * [Is Square Free](maths/is_square_free.py)
* [Jaccard Similarity](maths/jaccard_similarity.py) * [Jaccard Similarity](maths/jaccard_similarity.py)
* [Juggler Sequence](maths/juggler_sequence.py) * [Juggler Sequence](maths/juggler_sequence.py)
* [Kadanes](maths/kadanes.py)
* [Karatsuba](maths/karatsuba.py) * [Karatsuba](maths/karatsuba.py)
* [Krishnamurthy Number](maths/krishnamurthy_number.py) * [Krishnamurthy Number](maths/krishnamurthy_number.py)
* [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py) * [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py)
* [Largest Of Very Large Numbers](maths/largest_of_very_large_numbers.py) * [Largest Of Very Large Numbers](maths/largest_of_very_large_numbers.py)
* [Largest Subarray Sum](maths/largest_subarray_sum.py)
* [Least Common Multiple](maths/least_common_multiple.py) * [Least Common Multiple](maths/least_common_multiple.py)
* [Line Length](maths/line_length.py) * [Line Length](maths/line_length.py)
* [Liouville Lambda](maths/liouville_lambda.py) * [Liouville Lambda](maths/liouville_lambda.py)
@ -651,6 +656,7 @@
* [Sigmoid Linear Unit](maths/sigmoid_linear_unit.py) * [Sigmoid Linear Unit](maths/sigmoid_linear_unit.py)
* [Signum](maths/signum.py) * [Signum](maths/signum.py)
* [Simpson Rule](maths/simpson_rule.py) * [Simpson Rule](maths/simpson_rule.py)
* [Simultaneous Linear Equation Solver](maths/simultaneous_linear_equation_solver.py)
* [Sin](maths/sin.py) * [Sin](maths/sin.py)
* [Sock Merchant](maths/sock_merchant.py) * [Sock Merchant](maths/sock_merchant.py)
* [Softmax](maths/softmax.py) * [Softmax](maths/softmax.py)
@ -676,6 +682,7 @@
## Matrix ## Matrix
* [Binary Search Matrix](matrix/binary_search_matrix.py) * [Binary Search Matrix](matrix/binary_search_matrix.py)
* [Count Islands In Matrix](matrix/count_islands_in_matrix.py) * [Count Islands In Matrix](matrix/count_islands_in_matrix.py)
* [Count Negative Numbers In Sorted Matrix](matrix/count_negative_numbers_in_sorted_matrix.py)
* [Count Paths](matrix/count_paths.py) * [Count Paths](matrix/count_paths.py)
* [Cramers Rule 2X2](matrix/cramers_rule_2x2.py) * [Cramers Rule 2X2](matrix/cramers_rule_2x2.py)
* [Inverse Of Matrix](matrix/inverse_of_matrix.py) * [Inverse Of Matrix](matrix/inverse_of_matrix.py)
@ -723,9 +730,9 @@
* [Linear Congruential Generator](other/linear_congruential_generator.py) * [Linear Congruential Generator](other/linear_congruential_generator.py)
* [Lru Cache](other/lru_cache.py) * [Lru Cache](other/lru_cache.py)
* [Magicdiamondpattern](other/magicdiamondpattern.py) * [Magicdiamondpattern](other/magicdiamondpattern.py)
* [Maximum Subarray](other/maximum_subarray.py)
* [Maximum Subsequence](other/maximum_subsequence.py) * [Maximum Subsequence](other/maximum_subsequence.py)
* [Nested Brackets](other/nested_brackets.py) * [Nested Brackets](other/nested_brackets.py)
* [Number Container System](other/number_container_system.py)
* [Password](other/password.py) * [Password](other/password.py)
* [Quine](other/quine.py) * [Quine](other/quine.py)
* [Scoring Algorithm](other/scoring_algorithm.py) * [Scoring Algorithm](other/scoring_algorithm.py)
@ -733,7 +740,9 @@
* [Tower Of Hanoi](other/tower_of_hanoi.py) * [Tower Of Hanoi](other/tower_of_hanoi.py)
## Physics ## Physics
* [Altitude Pressure](physics/altitude_pressure.py)
* [Archimedes Principle](physics/archimedes_principle.py) * [Archimedes Principle](physics/archimedes_principle.py)
* [Basic Orbital Capture](physics/basic_orbital_capture.py)
* [Casimir Effect](physics/casimir_effect.py) * [Casimir Effect](physics/casimir_effect.py)
* [Centripetal Force](physics/centripetal_force.py) * [Centripetal Force](physics/centripetal_force.py)
* [Grahams Law](physics/grahams_law.py) * [Grahams Law](physics/grahams_law.py)
@ -749,6 +758,7 @@
* [Potential Energy](physics/potential_energy.py) * [Potential Energy](physics/potential_energy.py)
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py) * [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
* [Shear Stress](physics/shear_stress.py) * [Shear Stress](physics/shear_stress.py)
* [Speed Of Sound](physics/speed_of_sound.py)
## Project Euler ## Project Euler
* Problem 001 * Problem 001
@ -1054,7 +1064,6 @@
* [Q Fourier Transform](quantum/q_fourier_transform.py) * [Q Fourier Transform](quantum/q_fourier_transform.py)
* [Q Full Adder](quantum/q_full_adder.py) * [Q Full Adder](quantum/q_full_adder.py)
* [Quantum Entanglement](quantum/quantum_entanglement.py) * [Quantum Entanglement](quantum/quantum_entanglement.py)
* [Quantum Random](quantum/quantum_random.py)
* [Quantum Teleportation](quantum/quantum_teleportation.py) * [Quantum Teleportation](quantum/quantum_teleportation.py)
* [Ripple Adder Classic](quantum/ripple_adder_classic.py) * [Ripple Adder Classic](quantum/ripple_adder_classic.py)
* [Single Qubit Measure](quantum/single_qubit_measure.py) * [Single Qubit Measure](quantum/single_qubit_measure.py)

View File

@ -13,7 +13,7 @@
<img src="https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square" height="20" alt="Contributions Welcome"> <img src="https://img.shields.io/static/v1.svg?label=Contributions&message=Welcome&color=0059b3&style=flat-square" height="20" alt="Contributions Welcome">
</a> </a>
<img src="https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square" height="20"> <img src="https://img.shields.io/github/repo-size/TheAlgorithms/Python.svg?label=Repo%20size&style=flat-square" height="20">
<a href="https://discord.gg/c7MnfGFGa6"> <a href="https://the-algorithms.com/discord">
<img src="https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square" height="20" alt="Discord chat"> <img src="https://img.shields.io/discord/808045925556682782.svg?logo=discord&colorB=7289DA&style=flat-square" height="20" alt="Discord chat">
</a> </a>
<a href="https://gitter.im/TheAlgorithms/community"> <a href="https://gitter.im/TheAlgorithms/community">
@ -42,7 +42,7 @@ Read through our [Contribution Guidelines](CONTRIBUTING.md) before you contribut
## Community Channels ## Community Channels
We are on [Discord](https://discord.gg/c7MnfGFGa6) and [Gitter](https://gitter.im/TheAlgorithms/community)! Community channels are a great way for you to ask questions and get help. Please join us! We are on [Discord](https://the-algorithms.com/discord) and [Gitter](https://gitter.im/TheAlgorithms/community)! Community channels are a great way for you to ask questions and get help. Please join us!
## List of Algorithms ## List of Algorithms

View File

@ -25,9 +25,11 @@ def newton_raphson(
""" """
x = a x = a
while True: while True:
x = Decimal(x) - (Decimal(eval(func)) / Decimal(eval(str(diff(func))))) x = Decimal(x) - (
Decimal(eval(func)) / Decimal(eval(str(diff(func)))) # noqa: S307
)
# This number dictates the accuracy of the answer # This number dictates the accuracy of the answer
if abs(eval(func)) < precision: if abs(eval(func)) < precision: # noqa: S307
return float(x) return float(x)

93
backtracking/power_sum.py Normal file
View File

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

View File

@ -10,7 +10,7 @@ Python:
- 3.5 - 3.5
Usage: Usage:
- $python3 game_o_life <canvas_size:int> - $python3 game_of_life <canvas_size:int>
Game-Of-Life Rules: Game-Of-Life Rules:
@ -52,7 +52,8 @@ def seed(canvas: list[list[bool]]) -> None:
def run(canvas: list[list[bool]]) -> list[list[bool]]: def run(canvas: list[list[bool]]) -> list[list[bool]]:
"""This function runs the rules of game through all points, and changes their """
This function runs the rules of game through all points, and changes their
status accordingly.(in the same canvas) status accordingly.(in the same canvas)
@Args: @Args:
-- --
@ -60,7 +61,7 @@ def run(canvas: list[list[bool]]) -> list[list[bool]]:
@returns: @returns:
-- --
None canvas of population after one step
""" """
current_canvas = np.array(canvas) current_canvas = np.array(canvas)
next_gen_canvas = np.array(create_canvas(current_canvas.shape[0])) next_gen_canvas = np.array(create_canvas(current_canvas.shape[0]))
@ -70,10 +71,7 @@ def run(canvas: list[list[bool]]) -> list[list[bool]]:
pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2] pt, current_canvas[r - 1 : r + 2, c - 1 : c + 2]
) )
current_canvas = next_gen_canvas return next_gen_canvas.tolist()
del next_gen_canvas # cleaning memory as we move on.
return_canvas: list[list[bool]] = current_canvas.tolist()
return return_canvas
def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool: def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool:
@ -98,7 +96,7 @@ def __judge_point(pt: bool, neighbours: list[list[bool]]) -> bool:
if pt: if pt:
if alive < 2: if alive < 2:
state = False state = False
elif alive == 2 or alive == 3: elif alive in {2, 3}:
state = True state = True
elif alive > 3: elif alive > 3:
state = False state = False

View File

@ -10,13 +10,13 @@ primes = {
5: { 5: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -25,16 +25,16 @@ primes = {
14: { 14: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AACAA68FFFFFFFFFFFFFFFF", "15728E5A8AACAA68FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -43,21 +43,21 @@ primes = {
15: { 15: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -66,27 +66,27 @@ primes = {
16: { 16: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6"
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+ "FFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -95,33 +95,33 @@ primes = {
17: { 17: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+ "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+ "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+ "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+ "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+ "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+ "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+ "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+ "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+ "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+ "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+ "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+ "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+ "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+ "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+ "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+ "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+ "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+ "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+ "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+ "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+ "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+ "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+ "6DCC4024FFFFFFFFFFFFFFFF", "6DCC4024FFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,
@ -130,48 +130,48 @@ primes = {
18: { 18: {
"prime": int( "prime": int(
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+ "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" "29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
+ "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+ "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
+ "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+ "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
+ "83655D23DCA3AD961C62F356208552BB9ED529077096966D" "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+ "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
+ "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+ "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" "DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
+ "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+ "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
+ "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+ "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+ "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+ "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7"
+ "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+ "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6"
+ "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+ "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9"
+ "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+ "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD"
+ "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831"
+ "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B"
+ "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF"
+ "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6"
+ "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3"
+ "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+ "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328"
+ "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C"
+ "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE"
+ "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4"
+ "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300"
+ "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568"
+ "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+ "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B"
+ "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A"
+ "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36"
+ "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
+ "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92"
+ "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
+ "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71"
+ "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", "60C980DD98EDD3DFFFFFFFFFFFFFFFFF",
base=16, base=16,
), ),
"generator": 2, "generator": 2,

View File

@ -1,7 +1,11 @@
def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str: from string import ascii_uppercase
"""
For key:hello
def mixed_keyword(
keyword: str, plaintext: str, verbose: bool = False, alphabet: str = ascii_uppercase
) -> str:
"""
For keyword: hello
H E L O H E L O
A B C D A B C D
@ -12,58 +16,60 @@ def mixed_keyword(key: str = "college", pt: str = "UNIVERSITY") -> str:
Y Z Y Z
and map vertically and map vertically
>>> mixed_keyword("college", "UNIVERSITY") # doctest: +NORMALIZE_WHITESPACE >>> mixed_keyword("college", "UNIVERSITY", True) # doctest: +NORMALIZE_WHITESPACE
{'A': 'C', 'B': 'A', 'C': 'I', 'D': 'P', 'E': 'U', 'F': 'Z', 'G': 'O', 'H': 'B', {'A': 'C', 'B': 'A', 'C': 'I', 'D': 'P', 'E': 'U', 'F': 'Z', 'G': 'O', 'H': 'B',
'I': 'J', 'J': 'Q', 'K': 'V', 'L': 'L', 'M': 'D', 'N': 'K', 'O': 'R', 'P': 'W', 'I': 'J', 'J': 'Q', 'K': 'V', 'L': 'L', 'M': 'D', 'N': 'K', 'O': 'R', 'P': 'W',
'Q': 'E', 'R': 'F', 'S': 'M', 'T': 'S', 'U': 'X', 'V': 'G', 'W': 'H', 'X': 'N', 'Q': 'E', 'R': 'F', 'S': 'M', 'T': 'S', 'U': 'X', 'V': 'G', 'W': 'H', 'X': 'N',
'Y': 'T', 'Z': 'Y'} 'Y': 'T', 'Z': 'Y'}
'XKJGUFMJST' 'XKJGUFMJST'
>>> mixed_keyword("college", "UNIVERSITY", False) # doctest: +NORMALIZE_WHITESPACE
'XKJGUFMJST'
""" """
key = key.upper() keyword = keyword.upper()
pt = pt.upper() plaintext = plaintext.upper()
temp = [] alphabet_set = set(alphabet)
for i in key:
if i not in temp: # create a list of unique characters in the keyword - their order matters
temp.append(i) # it determines how we will map plaintext characters to the ciphertext
len_temp = len(temp) unique_chars = []
# print(temp) for char in keyword:
alpha = [] if char in alphabet_set and char not in unique_chars:
modalpha = [] unique_chars.append(char)
for j in range(65, 91): # the number of those unique characters will determine the number of rows
t = chr(j) num_unique_chars_in_keyword = len(unique_chars)
alpha.append(t)
if t not in temp: # create a shifted version of the alphabet
temp.append(t) shifted_alphabet = unique_chars + [
# print(temp) char for char in alphabet if char not in unique_chars
r = int(26 / 4) ]
# print(r)
k = 0 # create a modified alphabet by splitting the shifted alphabet into rows
for _ in range(r): modified_alphabet = [
s = [] shifted_alphabet[k : k + num_unique_chars_in_keyword]
for _ in range(len_temp): for k in range(0, 26, num_unique_chars_in_keyword)
s.append(temp[k]) ]
if k >= 25:
# map the alphabet characters to the modified alphabet characters
# going 'vertically' through the modified alphabet - consider columns first
mapping = {}
letter_index = 0
for column in range(num_unique_chars_in_keyword):
for row in modified_alphabet:
# if current row (the last one) is too short, break out of loop
if len(row) <= column:
break break
k += 1
modalpha.append(s) # map current letter to letter in modified alphabet
# print(modalpha) mapping[alphabet[letter_index]] = row[column]
d = {} letter_index += 1
j = 0
k = 0 if verbose:
for j in range(len_temp): print(mapping)
for m in modalpha: # create the encrypted text by mapping the plaintext to the modified alphabet
if not len(m) - 1 >= j: return "".join(mapping[char] if char in mapping else char for char in plaintext)
break
d[alpha[k]] = m[j]
if not k < 25:
break
k += 1
print(d)
cypher = ""
for i in pt:
cypher += d[i]
return cypher
if __name__ == "__main__": if __name__ == "__main__":
# example use
print(mixed_keyword("college", "UNIVERSITY")) print(mixed_keyword("college", "UNIVERSITY"))

View File

@ -150,7 +150,7 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str:
raise ValueError("The parameter idx_original_string must not be lower than 0.") raise ValueError("The parameter idx_original_string must not be lower than 0.")
if idx_original_string >= len(bwt_string): if idx_original_string >= len(bwt_string):
raise ValueError( raise ValueError(
"The parameter idx_original_string must be lower than" " len(bwt_string)." "The parameter idx_original_string must be lower than len(bwt_string)."
) )
ordered_rotations = [""] * len(bwt_string) ordered_rotations = [""] * len(bwt_string)

View File

@ -0,0 +1,114 @@
"""
Conversion of energy units.
Available units: joule, kilojoule, megajoule, gigajoule,\
wattsecond, watthour, kilowatthour, newtonmeter, calorie_nutr,\
kilocalorie_nutr, electronvolt, britishthermalunit_it, footpound
USAGE :
-> Import this file into their respective project.
-> Use the function energy_conversion() for conversion of energy units.
-> Parameters :
-> from_type : From which type you want to convert
-> to_type : To which type you want to convert
-> value : the value which you want to convert
REFERENCES :
-> Wikipedia reference: https://en.wikipedia.org/wiki/Units_of_energy
-> Wikipedia reference: https://en.wikipedia.org/wiki/Joule
-> Wikipedia reference: https://en.wikipedia.org/wiki/Kilowatt-hour
-> Wikipedia reference: https://en.wikipedia.org/wiki/Newton-metre
-> Wikipedia reference: https://en.wikipedia.org/wiki/Calorie
-> Wikipedia reference: https://en.wikipedia.org/wiki/Electronvolt
-> Wikipedia reference: https://en.wikipedia.org/wiki/British_thermal_unit
-> Wikipedia reference: https://en.wikipedia.org/wiki/Foot-pound_(energy)
-> Unit converter reference: https://www.unitconverters.net/energy-converter.html
"""
ENERGY_CONVERSION: dict[str, float] = {
"joule": 1.0,
"kilojoule": 1_000,
"megajoule": 1_000_000,
"gigajoule": 1_000_000_000,
"wattsecond": 1.0,
"watthour": 3_600,
"kilowatthour": 3_600_000,
"newtonmeter": 1.0,
"calorie_nutr": 4_186.8,
"kilocalorie_nutr": 4_186_800.00,
"electronvolt": 1.602_176_634e-19,
"britishthermalunit_it": 1_055.055_85,
"footpound": 1.355_818,
}
def energy_conversion(from_type: str, to_type: str, value: float) -> float:
"""
Conversion of energy units.
>>> energy_conversion("joule", "joule", 1)
1.0
>>> energy_conversion("joule", "kilojoule", 1)
0.001
>>> energy_conversion("joule", "megajoule", 1)
1e-06
>>> energy_conversion("joule", "gigajoule", 1)
1e-09
>>> energy_conversion("joule", "wattsecond", 1)
1.0
>>> energy_conversion("joule", "watthour", 1)
0.0002777777777777778
>>> energy_conversion("joule", "kilowatthour", 1)
2.7777777777777776e-07
>>> energy_conversion("joule", "newtonmeter", 1)
1.0
>>> energy_conversion("joule", "calorie_nutr", 1)
0.00023884589662749592
>>> energy_conversion("joule", "kilocalorie_nutr", 1)
2.388458966274959e-07
>>> energy_conversion("joule", "electronvolt", 1)
6.241509074460763e+18
>>> energy_conversion("joule", "britishthermalunit_it", 1)
0.0009478171226670134
>>> energy_conversion("joule", "footpound", 1)
0.7375621211696556
>>> energy_conversion("joule", "megajoule", 1000)
0.001
>>> energy_conversion("calorie_nutr", "kilocalorie_nutr", 1000)
1.0
>>> energy_conversion("kilowatthour", "joule", 10)
36000000.0
>>> energy_conversion("britishthermalunit_it", "footpound", 1)
778.1692306784539
>>> energy_conversion("watthour", "joule", "a") # doctest: +ELLIPSIS
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for /: 'str' and 'float'
>>> energy_conversion("wrongunit", "joule", 1) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: Incorrect 'from_type' or 'to_type' value: 'wrongunit', 'joule'
Valid values are: joule, ... footpound
>>> energy_conversion("joule", "wrongunit", 1) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: Incorrect 'from_type' or 'to_type' value: 'joule', 'wrongunit'
Valid values are: joule, ... footpound
>>> energy_conversion("123", "abc", 1) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
ValueError: Incorrect 'from_type' or 'to_type' value: '123', 'abc'
Valid values are: joule, ... footpound
"""
if to_type not in ENERGY_CONVERSION or from_type not in ENERGY_CONVERSION:
msg = (
f"Incorrect 'from_type' or 'to_type' value: {from_type!r}, {to_type!r}\n"
f"Valid values are: {', '.join(ENERGY_CONVERSION)}"
)
raise ValueError(msg)
return value * ENERGY_CONVERSION[from_type] / ENERGY_CONVERSION[to_type]
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -1,7 +1,6 @@
def permute(nums: list[int]) -> list[list[int]]: def permute(nums: list[int]) -> list[list[int]]:
""" """
Return all permutations. Return all permutations.
>>> from itertools import permutations >>> from itertools import permutations
>>> numbers= [1,2,3] >>> numbers= [1,2,3]
>>> all(list(nums) in permute(numbers) for nums in permutations(numbers)) >>> all(list(nums) in permute(numbers) for nums in permutations(numbers))
@ -20,7 +19,32 @@ def permute(nums: list[int]) -> list[list[int]]:
return result return result
def permute2(nums):
"""
Return all permutations of the given list.
>>> permute2([1, 2, 3])
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 2, 1], [3, 1, 2]]
"""
def backtrack(start):
if start == len(nums) - 1:
output.append(nums[:])
else:
for i in range(start, len(nums)):
nums[start], nums[i] = nums[i], nums[start]
backtrack(start + 1)
nums[start], nums[i] = nums[i], nums[start] # backtrack
output = []
backtrack(0)
return output
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest
# use res to print the data in permute2 function
res = permute2([1, 2, 3])
print(res)
doctest.testmod() doctest.testmod()

View File

@ -0,0 +1,98 @@
"""
Calculate the Product Sum from a Special Array.
reference: https://dev.to/sfrasica/algorithms-product-sum-from-an-array-dc6
Python doctests can be run with the following command:
python -m doctest -v product_sum.py
Calculate the product sum of a "special" array which can contain integers or nested
arrays. The product sum is obtained by adding all elements and multiplying by their
respective depths.
For example, in the array [x, y], the product sum is (x + y). In the array [x, [y, z]],
the product sum is x + 2 * (y + z). In the array [x, [y, [z]]],
the product sum is x + 2 * (y + 3z).
Example Input:
[5, 2, [-7, 1], 3, [6, [-13, 8], 4]]
Output: 12
"""
def product_sum(arr: list[int | list], depth: int) -> int:
"""
Recursively calculates the product sum of an array.
The product sum of an array is defined as the sum of its elements multiplied by
their respective depths. If an element is a list, its product sum is calculated
recursively by multiplying the sum of its elements with its depth plus one.
Args:
arr: The array of integers and nested lists.
depth: The current depth level.
Returns:
int: The product sum of the array.
Examples:
>>> product_sum([1, 2, 3], 1)
6
>>> product_sum([-1, 2, [-3, 4]], 2)
8
>>> product_sum([1, 2, 3], -1)
-6
>>> product_sum([1, 2, 3], 0)
0
>>> product_sum([1, 2, 3], 7)
42
>>> product_sum((1, 2, 3), 7)
42
>>> product_sum({1, 2, 3}, 7)
42
>>> product_sum([1, -1], 1)
0
>>> product_sum([1, -2], 1)
-1
>>> product_sum([-3.5, [1, [0.5]]], 1)
1.5
"""
total_sum = 0
for ele in arr:
total_sum += product_sum(ele, depth + 1) if isinstance(ele, list) else ele
return total_sum * depth
def product_sum_array(array: list[int | list]) -> int:
"""
Calculates the product sum of an array.
Args:
array (List[Union[int, List]]): The array of integers and nested lists.
Returns:
int: The product sum of the array.
Examples:
>>> product_sum_array([1, 2, 3])
6
>>> product_sum_array([1, [2, 3]])
11
>>> product_sum_array([1, [2, [3, 4]]])
47
>>> product_sum_array([0])
0
>>> product_sum_array([-3.5, [1, [0.5]]])
1.5
>>> product_sum_array([1, -2])
-1
"""
return product_sum(array, 1)
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -40,7 +40,7 @@ class BinarySearchTree:
else: else:
node.parent.left = new_children node.parent.left = new_children
else: else:
self.root = None self.root = new_children
def is_right(self, node: Node) -> bool: def is_right(self, node: Node) -> bool:
if node.parent and node.parent.right: if node.parent and node.parent.right:

View File

@ -58,6 +58,19 @@ def inorder(root: Node | None) -> list[int]:
return [*inorder(root.left), root.data, *inorder(root.right)] if root else [] return [*inorder(root.left), root.data, *inorder(root.right)] if root else []
def reverse_inorder(root: Node | None) -> list[int]:
"""
Reverse in-order traversal visits right subtree, root node, left subtree.
>>> reverse_inorder(make_tree())
[3, 1, 5, 2, 4]
"""
return (
[*reverse_inorder(root.right), root.data, *reverse_inorder(root.left)]
if root
else []
)
def height(root: Node | None) -> int: def height(root: Node | None) -> int:
""" """
Recursive function for calculating the height of the binary tree. Recursive function for calculating the height of the binary tree.
@ -161,15 +174,12 @@ def zigzag(root: Node | None) -> Sequence[Node | None] | list[Any]:
def main() -> None: # Main function for testing. def main() -> None: # Main function for testing.
""" # Create binary tree.
Create binary tree.
"""
root = make_tree() root = make_tree()
"""
All Traversals of the binary are as follows:
"""
# All Traversals of the binary are as follows:
print(f"In-order Traversal: {inorder(root)}") print(f"In-order Traversal: {inorder(root)}")
print(f"Reverse In-order Traversal: {reverse_inorder(root)}")
print(f"Pre-order Traversal: {preorder(root)}") print(f"Pre-order Traversal: {preorder(root)}")
print(f"Post-order Traversal: {postorder(root)}", "\n") print(f"Post-order Traversal: {postorder(root)}", "\n")

View File

@ -152,7 +152,7 @@ class RedBlackTree:
self.grandparent.color = 1 self.grandparent.color = 1
self.grandparent._insert_repair() self.grandparent._insert_repair()
def remove(self, label: int) -> RedBlackTree: def remove(self, label: int) -> RedBlackTree: # noqa: PLR0912
"""Remove label from this tree.""" """Remove label from this tree."""
if self.label == label: if self.label == label:
if self.left and self.right: if self.left and self.right:

View File

@ -7,7 +7,8 @@ class SegmentTree:
self.st = [0] * ( self.st = [0] * (
4 * self.N 4 * self.N
) # approximate the overall size of segment tree with array N ) # approximate the overall size of segment tree with array N
self.build(1, 0, self.N - 1) if self.N:
self.build(1, 0, self.N - 1)
def left(self, idx): def left(self, idx):
return idx * 2 return idx * 2

View File

@ -32,7 +32,7 @@ class Deque:
the number of nodes the number of nodes
""" """
__slots__ = ["_front", "_back", "_len"] __slots__ = ("_front", "_back", "_len")
@dataclass @dataclass
class _Node: class _Node:
@ -54,7 +54,7 @@ class Deque:
the current node of the iteration. the current node of the iteration.
""" """
__slots__ = ["_cur"] __slots__ = ("_cur",)
def __init__(self, cur: Deque._Node | None) -> None: def __init__(self, cur: Deque._Node | None) -> None:
self._cur = cur self._cur = cur

View File

@ -0,0 +1,141 @@
"""Queue represented by a Python list"""
from collections.abc import Iterable
from typing import Generic, TypeVar
_T = TypeVar("_T")
class QueueByList(Generic[_T]):
def __init__(self, iterable: Iterable[_T] | None = None) -> None:
"""
>>> QueueByList()
Queue(())
>>> QueueByList([10, 20, 30])
Queue((10, 20, 30))
>>> QueueByList((i**2 for i in range(1, 4)))
Queue((1, 4, 9))
"""
self.entries: list[_T] = list(iterable or [])
def __len__(self) -> int:
"""
>>> len(QueueByList())
0
>>> from string import ascii_lowercase
>>> len(QueueByList(ascii_lowercase))
26
>>> queue = QueueByList()
>>> for i in range(1, 11):
... queue.put(i)
>>> len(queue)
10
>>> for i in range(2):
... queue.get()
1
2
>>> len(queue)
8
"""
return len(self.entries)
def __repr__(self) -> str:
"""
>>> queue = QueueByList()
>>> queue
Queue(())
>>> str(queue)
'Queue(())'
>>> queue.put(10)
>>> queue
Queue((10,))
>>> queue.put(20)
>>> queue.put(30)
>>> queue
Queue((10, 20, 30))
"""
return f"Queue({tuple(self.entries)})"
def put(self, item: _T) -> None:
"""Put `item` to the Queue
>>> queue = QueueByList()
>>> queue.put(10)
>>> queue.put(20)
>>> len(queue)
2
>>> queue
Queue((10, 20))
"""
self.entries.append(item)
def get(self) -> _T:
"""
Get `item` from the Queue
>>> queue = QueueByList((10, 20, 30))
>>> queue.get()
10
>>> queue.put(40)
>>> queue.get()
20
>>> queue.get()
30
>>> len(queue)
1
>>> queue.get()
40
>>> queue.get()
Traceback (most recent call last):
...
IndexError: Queue is empty
"""
if not self.entries:
raise IndexError("Queue is empty")
return self.entries.pop(0)
def rotate(self, rotation: int) -> None:
"""Rotate the items of the Queue `rotation` times
>>> queue = QueueByList([10, 20, 30, 40])
>>> queue
Queue((10, 20, 30, 40))
>>> queue.rotate(1)
>>> queue
Queue((20, 30, 40, 10))
>>> queue.rotate(2)
>>> queue
Queue((40, 10, 20, 30))
"""
put = self.entries.append
get = self.entries.pop
for _ in range(rotation):
put(get(0))
def get_front(self) -> _T:
"""Get the front item from the Queue
>>> queue = QueueByList((10, 20, 30))
>>> queue.get_front()
10
>>> queue
Queue((10, 20, 30))
>>> queue.get()
10
>>> queue.get_front()
20
"""
return self.entries[0]
if __name__ == "__main__":
from doctest import testmod
testmod()

View File

@ -1,52 +0,0 @@
"""Queue represented by a Python list"""
class Queue:
def __init__(self):
self.entries = []
self.length = 0
self.front = 0
def __str__(self):
printed = "<" + str(self.entries)[1:-1] + ">"
return printed
"""Enqueues {@code item}
@param item
item to enqueue"""
def put(self, item):
self.entries.append(item)
self.length = self.length + 1
"""Dequeues {@code item}
@requirement: |self.length| > 0
@return dequeued
item that was dequeued"""
def get(self):
self.length = self.length - 1
dequeued = self.entries[self.front]
# self.front-=1
# self.entries = self.entries[self.front:]
self.entries = self.entries[1:]
return dequeued
"""Rotates the queue {@code rotation} times
@param rotation
number of times to rotate queue"""
def rotate(self, rotation):
for _ in range(rotation):
self.put(self.get())
"""Enqueues {@code item}
@return item at front of self.entries"""
def get_front(self):
return self.entries[0]
"""Returns the length of this.entries"""
def size(self):
return self.length

View File

@ -4,9 +4,26 @@ https://en.wikipedia.org/wiki/Reverse_Polish_notation
https://en.wikipedia.org/wiki/Shunting-yard_algorithm https://en.wikipedia.org/wiki/Shunting-yard_algorithm
""" """
from typing import Literal
from .balanced_parentheses import balanced_parentheses from .balanced_parentheses import balanced_parentheses
from .stack import Stack from .stack import Stack
PRECEDENCES: dict[str, int] = {
"+": 1,
"-": 1,
"*": 2,
"/": 2,
"^": 3,
}
ASSOCIATIVITIES: dict[str, Literal["LR", "RL"]] = {
"+": "LR",
"-": "LR",
"*": "LR",
"/": "LR",
"^": "RL",
}
def precedence(char: str) -> int: def precedence(char: str) -> int:
""" """
@ -14,7 +31,15 @@ def precedence(char: str) -> int:
order of operation. order of operation.
https://en.wikipedia.org/wiki/Order_of_operations https://en.wikipedia.org/wiki/Order_of_operations
""" """
return {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3}.get(char, -1) return PRECEDENCES.get(char, -1)
def associativity(char: str) -> Literal["LR", "RL"]:
"""
Return the associativity of the operator `char`.
https://en.wikipedia.org/wiki/Operator_associativity
"""
return ASSOCIATIVITIES[char]
def infix_to_postfix(expression_str: str) -> str: def infix_to_postfix(expression_str: str) -> str:
@ -35,6 +60,8 @@ def infix_to_postfix(expression_str: str) -> str:
'a b c * + d e * f + g * +' 'a b c * + d e * f + g * +'
>>> infix_to_postfix("x^y/(5*z)+2") >>> infix_to_postfix("x^y/(5*z)+2")
'x y ^ 5 z * / 2 +' 'x y ^ 5 z * / 2 +'
>>> infix_to_postfix("2^3^2")
'2 3 2 ^ ^'
""" """
if not balanced_parentheses(expression_str): if not balanced_parentheses(expression_str):
raise ValueError("Mismatched parentheses") raise ValueError("Mismatched parentheses")
@ -50,9 +77,26 @@ def infix_to_postfix(expression_str: str) -> str:
postfix.append(stack.pop()) postfix.append(stack.pop())
stack.pop() stack.pop()
else: else:
while not stack.is_empty() and precedence(char) <= precedence(stack.peek()): while True:
if stack.is_empty():
stack.push(char)
break
char_precedence = precedence(char)
tos_precedence = precedence(stack.peek())
if char_precedence > tos_precedence:
stack.push(char)
break
if char_precedence < tos_precedence:
postfix.append(stack.pop())
continue
# Precedences are equal
if associativity(char) == "RL":
stack.push(char)
break
postfix.append(stack.pop()) postfix.append(stack.pop())
stack.push(char)
while not stack.is_empty(): while not stack.is_empty():
postfix.append(stack.pop()) postfix.append(stack.pop())
return " ".join(postfix) return " ".join(postfix)

View File

@ -54,10 +54,17 @@ class RadixNode:
word (str): word to insert word (str): word to insert
>>> RadixNode("myprefix").insert("mystring") >>> RadixNode("myprefix").insert("mystring")
>>> root = RadixNode()
>>> root.insert_many(['myprefix', 'myprefixA', 'myprefixAA'])
>>> root.print_tree()
- myprefix (leaf)
-- A (leaf)
--- A (leaf)
""" """
# Case 1: If the word is the prefix of the node # Case 1: If the word is the prefix of the node
# Solution: We set the current node as leaf # Solution: We set the current node as leaf
if self.prefix == word: if self.prefix == word and not self.is_leaf:
self.is_leaf = True self.is_leaf = True
# Case 2: The node has no edges that have a prefix to the word # Case 2: The node has no edges that have a prefix to the word
@ -156,7 +163,7 @@ class RadixNode:
del self.nodes[word[0]] del self.nodes[word[0]]
# We merge the current node with its only child # We merge the current node with its only child
if len(self.nodes) == 1 and not self.is_leaf: if len(self.nodes) == 1 and not self.is_leaf:
merging_node = list(self.nodes.values())[0] merging_node = next(iter(self.nodes.values()))
self.is_leaf = merging_node.is_leaf self.is_leaf = merging_node.is_leaf
self.prefix += merging_node.prefix self.prefix += merging_node.prefix
self.nodes = merging_node.nodes self.nodes = merging_node.nodes
@ -165,7 +172,7 @@ class RadixNode:
incoming_node.is_leaf = False incoming_node.is_leaf = False
# If there is 1 edge, we merge it with its child # If there is 1 edge, we merge it with its child
else: else:
merging_node = list(incoming_node.nodes.values())[0] merging_node = next(iter(incoming_node.nodes.values()))
incoming_node.is_leaf = merging_node.is_leaf incoming_node.is_leaf = merging_node.is_leaf
incoming_node.prefix += merging_node.prefix incoming_node.prefix += merging_node.prefix
incoming_node.nodes = merging_node.nodes incoming_node.nodes = merging_node.nodes

View File

@ -39,9 +39,18 @@ class Burkes:
def get_greyscale(cls, blue: int, green: int, red: int) -> float: def get_greyscale(cls, blue: int, green: int, red: int) -> float:
""" """
>>> Burkes.get_greyscale(3, 4, 5) >>> Burkes.get_greyscale(3, 4, 5)
3.753 4.185
>>> Burkes.get_greyscale(0, 0, 0)
0.0
>>> Burkes.get_greyscale(255, 255, 255)
255.0
""" """
return 0.114 * blue + 0.587 * green + 0.2126 * red """
Formula from https://en.wikipedia.org/wiki/HSL_and_HSV
cf Lightness section, and Fig 13c.
We use the first of four possible.
"""
return 0.114 * blue + 0.587 * green + 0.299 * red
def process(self) -> None: def process(self) -> None:
for y in range(self.height): for y in range(self.height):
@ -49,10 +58,10 @@ class Burkes:
greyscale = int(self.get_greyscale(*self.input_img[y][x])) greyscale = int(self.get_greyscale(*self.input_img[y][x]))
if self.threshold > greyscale + self.error_table[y][x]: if self.threshold > greyscale + self.error_table[y][x]:
self.output_img[y][x] = (0, 0, 0) self.output_img[y][x] = (0, 0, 0)
current_error = greyscale + self.error_table[x][y] current_error = greyscale + self.error_table[y][x]
else: else:
self.output_img[y][x] = (255, 255, 255) self.output_img[y][x] = (255, 255, 255)
current_error = greyscale + self.error_table[x][y] - 255 current_error = greyscale + self.error_table[y][x] - 255
""" """
Burkes error propagation (`*` is current pixel): Burkes error propagation (`*` is current pixel):

View File

@ -266,7 +266,7 @@ def convex_hull_bf(points: list[Point]) -> list[Point]:
points_left_of_ij = points_right_of_ij = False points_left_of_ij = points_right_of_ij = False
ij_part_of_convex_hull = True ij_part_of_convex_hull = True
for k in range(n): for k in range(n):
if k != i and k != j: if k not in {i, j}:
det_k = _det(points[i], points[j], points[k]) det_k = _det(points[i], points[j], points[k])
if det_k > 0: if det_k > 0:

View File

@ -0,0 +1,112 @@
"""
The maximum subarray problem is the task of finding the continuous subarray that has the
maximum sum within a given array of numbers. For example, given the array
[-2, 1, -3, 4, -1, 2, 1, -5, 4], the contiguous subarray with the maximum sum is
[4, -1, 2, 1], which has a sum of 6.
This divide-and-conquer algorithm finds the maximum subarray in O(n log n) time.
"""
from __future__ import annotations
import time
from collections.abc import Sequence
from random import randint
from matplotlib import pyplot as plt
def max_subarray(
arr: Sequence[float], low: int, high: int
) -> tuple[int | None, int | None, float]:
"""
Solves the maximum subarray problem using divide and conquer.
:param arr: the given array of numbers
:param low: the start index
:param high: the end index
:return: the start index of the maximum subarray, the end index of the
maximum subarray, and the maximum subarray sum
>>> nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
>>> max_subarray(nums, 0, len(nums) - 1)
(3, 6, 6)
>>> nums = [2, 8, 9]
>>> max_subarray(nums, 0, len(nums) - 1)
(0, 2, 19)
>>> nums = [0, 0]
>>> max_subarray(nums, 0, len(nums) - 1)
(0, 0, 0)
>>> nums = [-1.0, 0.0, 1.0]
>>> max_subarray(nums, 0, len(nums) - 1)
(2, 2, 1.0)
>>> nums = [-2, -3, -1, -4, -6]
>>> max_subarray(nums, 0, len(nums) - 1)
(2, 2, -1)
>>> max_subarray([], 0, 0)
(None, None, 0)
"""
if not arr:
return None, None, 0
if low == high:
return low, high, arr[low]
mid = (low + high) // 2
left_low, left_high, left_sum = max_subarray(arr, low, mid)
right_low, right_high, right_sum = max_subarray(arr, mid + 1, high)
cross_left, cross_right, cross_sum = max_cross_sum(arr, low, mid, high)
if left_sum >= right_sum and left_sum >= cross_sum:
return left_low, left_high, left_sum
elif right_sum >= left_sum and right_sum >= cross_sum:
return right_low, right_high, right_sum
return cross_left, cross_right, cross_sum
def max_cross_sum(
arr: Sequence[float], low: int, mid: int, high: int
) -> tuple[int, int, float]:
left_sum, max_left = float("-inf"), -1
right_sum, max_right = float("-inf"), -1
summ: int | float = 0
for i in range(mid, low - 1, -1):
summ += arr[i]
if summ > left_sum:
left_sum = summ
max_left = i
summ = 0
for i in range(mid + 1, high + 1):
summ += arr[i]
if summ > right_sum:
right_sum = summ
max_right = i
return max_left, max_right, (left_sum + right_sum)
def time_max_subarray(input_size: int) -> float:
arr = [randint(1, input_size) for _ in range(input_size)]
start = time.time()
max_subarray(arr, 0, input_size - 1)
end = time.time()
return end - start
def plot_runtimes() -> None:
input_sizes = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000]
runtimes = [time_max_subarray(input_size) for input_size in input_sizes]
print("No of Inputs\t\tTime Taken")
for input_size, runtime in zip(input_sizes, runtimes):
print(input_size, "\t\t", runtime)
plt.plot(input_sizes, runtimes)
plt.xlabel("Number of Inputs")
plt.ylabel("Time taken in seconds")
plt.show()
if __name__ == "__main__":
"""
A random simulation of this algorithm.
"""
from doctest import testmod
testmod()

View File

@ -1,78 +0,0 @@
"""
Given a array of length n, max_subarray_sum() finds
the maximum of sum of contiguous sub-array using divide and conquer method.
Time complexity : O(n log n)
Ref : INTRODUCTION TO ALGORITHMS THIRD EDITION
(section : 4, sub-section : 4.1, page : 70)
"""
def max_sum_from_start(array):
"""This function finds the maximum contiguous sum of array from 0 index
Parameters :
array (list[int]) : given array
Returns :
max_sum (int) : maximum contiguous sum of array from 0 index
"""
array_sum = 0
max_sum = float("-inf")
for num in array:
array_sum += num
if array_sum > max_sum:
max_sum = array_sum
return max_sum
def max_cross_array_sum(array, left, mid, right):
"""This function finds the maximum contiguous sum of left and right arrays
Parameters :
array, left, mid, right (list[int], int, int, int)
Returns :
(int) : maximum of sum of contiguous sum of left and right arrays
"""
max_sum_of_left = max_sum_from_start(array[left : mid + 1][::-1])
max_sum_of_right = max_sum_from_start(array[mid + 1 : right + 1])
return max_sum_of_left + max_sum_of_right
def max_subarray_sum(array, left, right):
"""Maximum contiguous sub-array sum, using divide and conquer method
Parameters :
array, left, right (list[int], int, int) :
given array, current left index and current right index
Returns :
int : maximum of sum of contiguous sub-array
"""
# base case: array has only one element
if left == right:
return array[right]
# Recursion
mid = (left + right) // 2
left_half_sum = max_subarray_sum(array, left, mid)
right_half_sum = max_subarray_sum(array, mid + 1, right)
cross_sum = max_cross_array_sum(array, left, mid, right)
return max(left_half_sum, right_half_sum, cross_sum)
if __name__ == "__main__":
array = [-2, -5, 6, -2, -3, 1, 5, -6]
array_length = len(array)
print(
"Maximum sum of contiguous subarray:",
max_subarray_sum(array, 0, array_length - 1),
)

View File

@ -1,93 +0,0 @@
"""
author : Mayank Kumar Jha (mk9440)
"""
from __future__ import annotations
def find_max_sub_array(a, low, high):
if low == high:
return low, high, a[low]
else:
mid = (low + high) // 2
left_low, left_high, left_sum = find_max_sub_array(a, low, mid)
right_low, right_high, right_sum = find_max_sub_array(a, mid + 1, high)
cross_left, cross_right, cross_sum = find_max_cross_sum(a, low, mid, high)
if left_sum >= right_sum and left_sum >= cross_sum:
return left_low, left_high, left_sum
elif right_sum >= left_sum and right_sum >= cross_sum:
return right_low, right_high, right_sum
else:
return cross_left, cross_right, cross_sum
def find_max_cross_sum(a, low, mid, high):
left_sum, max_left = -999999999, -1
right_sum, max_right = -999999999, -1
summ = 0
for i in range(mid, low - 1, -1):
summ += a[i]
if summ > left_sum:
left_sum = summ
max_left = i
summ = 0
for i in range(mid + 1, high + 1):
summ += a[i]
if summ > right_sum:
right_sum = summ
max_right = i
return max_left, max_right, (left_sum + right_sum)
def max_sub_array(nums: list[int]) -> int:
"""
Finds the contiguous subarray which has the largest sum and return its sum.
>>> max_sub_array([-2, 1, -3, 4, -1, 2, 1, -5, 4])
6
An empty (sub)array has sum 0.
>>> max_sub_array([])
0
If all elements are negative, the largest subarray would be the empty array,
having the sum 0.
>>> max_sub_array([-1, -2, -3])
0
>>> max_sub_array([5, -2, -3])
5
>>> max_sub_array([31, -41, 59, 26, -53, 58, 97, -93, -23, 84])
187
"""
best = 0
current = 0
for i in nums:
current += i
current = max(current, 0)
best = max(best, current)
return best
if __name__ == "__main__":
"""
A random simulation of this algorithm.
"""
import time
from random import randint
from matplotlib import pyplot as plt
inputs = [10, 100, 1000, 10000, 50000, 100000, 200000, 300000, 400000, 500000]
tim = []
for i in inputs:
li = [randint(1, i) for j in range(i)]
strt = time.time()
(find_max_sub_array(li, 0, len(li) - 1))
end = time.time()
tim.append(end - strt)
print("No of Inputs Time Taken")
for i in range(len(inputs)):
print(inputs[i], "\t\t", tim[i])
plt.plot(inputs, tim)
plt.xlabel("Number of Inputs")
plt.ylabel("Time taken in seconds ")
plt.show()

View File

@ -0,0 +1,60 @@
"""
The maximum subarray sum problem is the task of finding the maximum sum that can be
obtained from a contiguous subarray within a given array of numbers. For example, given
the array [-2, 1, -3, 4, -1, 2, 1, -5, 4], the contiguous subarray with the maximum sum
is [4, -1, 2, 1], so the maximum subarray sum is 6.
Kadane's algorithm is a simple dynamic programming algorithm that solves the maximum
subarray sum problem in O(n) time and O(1) space.
Reference: https://en.wikipedia.org/wiki/Maximum_subarray_problem
"""
from collections.abc import Sequence
def max_subarray_sum(
arr: Sequence[float], allow_empty_subarrays: bool = False
) -> float:
"""
Solves the maximum subarray sum problem using Kadane's algorithm.
:param arr: the given array of numbers
:param allow_empty_subarrays: if True, then the algorithm considers empty subarrays
>>> max_subarray_sum([2, 8, 9])
19
>>> max_subarray_sum([0, 0])
0
>>> max_subarray_sum([-1.0, 0.0, 1.0])
1.0
>>> max_subarray_sum([1, 2, 3, 4, -2])
10
>>> max_subarray_sum([-2, 1, -3, 4, -1, 2, 1, -5, 4])
6
>>> max_subarray_sum([2, 3, -9, 8, -2])
8
>>> max_subarray_sum([-2, -3, -1, -4, -6])
-1
>>> max_subarray_sum([-2, -3, -1, -4, -6], allow_empty_subarrays=True)
0
>>> max_subarray_sum([])
0
"""
if not arr:
return 0
max_sum = 0 if allow_empty_subarrays else float("-inf")
curr_sum = 0.0
for num in arr:
curr_sum = max(0 if allow_empty_subarrays else num, curr_sum + num)
max_sum = max(max_sum, curr_sum)
return max_sum
if __name__ == "__main__":
from doctest import testmod
testmod()
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(f"{max_subarray_sum(nums) = }")

View File

@ -1,20 +0,0 @@
def max_subarray_sum(nums: list) -> int:
"""
>>> max_subarray_sum([6 , 9, -1, 3, -7, -5, 10])
17
"""
if not nums:
return 0
n = len(nums)
res, s, s_pre = nums[0], nums[0], nums[0]
for i in range(1, n):
s = max(nums[i], s_pre + nums[i])
s_pre = s
res = max(res, s)
return res
if __name__ == "__main__":
nums = [6, 9, -1, 3, -7, -5, 10]
print(max_subarray_sum(nums))

View File

@ -4,7 +4,7 @@ from __future__ import annotations
def simple_interest( def simple_interest(
principal: float, daily_interest_rate: float, days_between_payments: int principal: float, daily_interest_rate: float, days_between_payments: float
) -> float: ) -> float:
""" """
>>> simple_interest(18000.0, 0.06, 3) >>> simple_interest(18000.0, 0.06, 3)
@ -42,7 +42,7 @@ def simple_interest(
def compound_interest( def compound_interest(
principal: float, principal: float,
nominal_annual_interest_rate_percentage: float, nominal_annual_interest_rate_percentage: float,
number_of_compounding_periods: int, number_of_compounding_periods: float,
) -> float: ) -> float:
""" """
>>> compound_interest(10000.0, 0.05, 3) >>> compound_interest(10000.0, 0.05, 3)
@ -77,6 +77,43 @@ def compound_interest(
) )
def apr_interest(
principal: float,
nominal_annual_percentage_rate: float,
number_of_years: float,
) -> float:
"""
>>> apr_interest(10000.0, 0.05, 3)
1618.223072263547
>>> apr_interest(10000.0, 0.05, 1)
512.6749646744732
>>> apr_interest(0.5, 0.05, 3)
0.08091115361317736
>>> apr_interest(10000.0, 0.06, -4)
Traceback (most recent call last):
...
ValueError: number_of_years must be > 0
>>> apr_interest(10000.0, -3.5, 3.0)
Traceback (most recent call last):
...
ValueError: nominal_annual_percentage_rate must be >= 0
>>> apr_interest(-5500.0, 0.01, 5)
Traceback (most recent call last):
...
ValueError: principal must be > 0
"""
if number_of_years <= 0:
raise ValueError("number_of_years must be > 0")
if nominal_annual_percentage_rate < 0:
raise ValueError("nominal_annual_percentage_rate must be >= 0")
if principal <= 0:
raise ValueError("principal must be > 0")
return compound_interest(
principal, nominal_annual_percentage_rate / 365, number_of_years * 365
)
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest

View File

@ -82,3 +82,4 @@ if __name__ == "__main__":
vertices = [(-175, -125), (0, 175), (175, -125)] # vertices of triangle vertices = [(-175, -125), (0, 175), (175, -125)] # vertices of triangle
triangle(vertices[0], vertices[1], vertices[2], int(sys.argv[1])) triangle(vertices[0], vertices[1], vertices[2], int(sys.argv[1]))
turtle.Screen().exitonclick()

View File

@ -0,0 +1,89 @@
"""
This script implements the Dijkstra algorithm on a binary grid.
The grid consists of 0s and 1s, where 1 represents
a walkable node and 0 represents an obstacle.
The algorithm finds the shortest path from a start node to a destination node.
Diagonal movement can be allowed or disallowed.
"""
from heapq import heappop, heappush
import numpy as np
def dijkstra(
grid: np.ndarray,
source: tuple[int, int],
destination: tuple[int, int],
allow_diagonal: bool,
) -> tuple[float | int, list[tuple[int, int]]]:
"""
Implements Dijkstra's algorithm on a binary grid.
Args:
grid (np.ndarray): A 2D numpy array representing the grid.
1 represents a walkable node and 0 represents an obstacle.
source (Tuple[int, int]): A tuple representing the start node.
destination (Tuple[int, int]): A tuple representing the
destination node.
allow_diagonal (bool): A boolean determining whether
diagonal movements are allowed.
Returns:
Tuple[Union[float, int], List[Tuple[int, int]]]:
The shortest distance from the start node to the destination node
and the shortest path as a list of nodes.
>>> dijkstra(np.array([[1, 1, 1], [0, 1, 0], [0, 1, 1]]), (0, 0), (2, 2), False)
(4.0, [(0, 0), (0, 1), (1, 1), (2, 1), (2, 2)])
>>> dijkstra(np.array([[1, 1, 1], [0, 1, 0], [0, 1, 1]]), (0, 0), (2, 2), True)
(2.0, [(0, 0), (1, 1), (2, 2)])
>>> dijkstra(np.array([[1, 1, 1], [0, 0, 1], [0, 1, 1]]), (0, 0), (2, 2), False)
(4.0, [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)])
"""
rows, cols = grid.shape
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]
if allow_diagonal:
dx += [-1, -1, 1, 1]
dy += [-1, 1, -1, 1]
queue, visited = [(0, source)], set()
matrix = np.full((rows, cols), np.inf)
matrix[source] = 0
predecessors = np.empty((rows, cols), dtype=object)
predecessors[source] = None
while queue:
(dist, (x, y)) = heappop(queue)
if (x, y) in visited:
continue
visited.add((x, y))
if (x, y) == destination:
path = []
while (x, y) != source:
path.append((x, y))
x, y = predecessors[x, y]
path.append(source) # add the source manually
path.reverse()
return matrix[destination], path
for i in range(len(dx)):
nx, ny = x + dx[i], y + dy[i]
if 0 <= nx < rows and 0 <= ny < cols:
next_node = grid[nx][ny]
if next_node == 1 and matrix[nx, ny] > dist + 1:
heappush(queue, (dist + 1, (nx, ny)))
matrix[nx, ny] = dist + 1
predecessors[nx, ny] = (x, y)
return np.inf, []
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -39,7 +39,7 @@ class DirectedGraph:
stack = [] stack = []
visited = [] visited = []
if s == -2: if s == -2:
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
ss = s ss = s
@ -87,7 +87,7 @@ class DirectedGraph:
d = deque() d = deque()
visited = [] visited = []
if s == -2: if s == -2:
s = list(self.graph)[0] s = next(iter(self.graph))
d.append(s) d.append(s)
visited.append(s) visited.append(s)
while d: while d:
@ -114,7 +114,7 @@ class DirectedGraph:
stack = [] stack = []
visited = [] visited = []
if s == -2: if s == -2:
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
ss = s ss = s
@ -146,7 +146,7 @@ class DirectedGraph:
def cycle_nodes(self): def cycle_nodes(self):
stack = [] stack = []
visited = [] visited = []
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
parent = -2 parent = -2
@ -199,7 +199,7 @@ class DirectedGraph:
def has_cycle(self): def has_cycle(self):
stack = [] stack = []
visited = [] visited = []
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
parent = -2 parent = -2
@ -305,7 +305,7 @@ class Graph:
stack = [] stack = []
visited = [] visited = []
if s == -2: if s == -2:
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
ss = s ss = s
@ -353,7 +353,7 @@ class Graph:
d = deque() d = deque()
visited = [] visited = []
if s == -2: if s == -2:
s = list(self.graph)[0] s = next(iter(self.graph))
d.append(s) d.append(s)
visited.append(s) visited.append(s)
while d: while d:
@ -371,7 +371,7 @@ class Graph:
def cycle_nodes(self): def cycle_nodes(self):
stack = [] stack = []
visited = [] visited = []
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
parent = -2 parent = -2
@ -424,7 +424,7 @@ class Graph:
def has_cycle(self): def has_cycle(self):
stack = [] stack = []
visited = [] visited = []
s = list(self.graph)[0] s = next(iter(self.graph))
stack.append(s) stack.append(s)
visited.append(s) visited.append(s)
parent = -2 parent = -2

View File

@ -113,7 +113,7 @@ class PushRelabelExecutor(MaximumFlowAlgorithmExecutor):
vertices_list = [ vertices_list = [
i i
for i in range(self.verticies_count) for i in range(self.verticies_count)
if i != self.source_index and i != self.sink_index if i not in {self.source_index, self.sink_index}
] ]
# move through list # move through list

View File

@ -20,7 +20,7 @@ def check_circuit_or_path(graph, max_node):
odd_degree_nodes = 0 odd_degree_nodes = 0
odd_node = -1 odd_node = -1
for i in range(max_node): for i in range(max_node):
if i not in graph.keys(): if i not in graph:
continue continue
if len(graph[i]) % 2 == 1: if len(graph[i]) % 2 == 1:
odd_degree_nodes += 1 odd_degree_nodes += 1

View File

@ -0,0 +1,589 @@
#!/usr/bin/env python3
"""
Author: Vikram Nithyanandam
Description:
The following implementation is a robust unweighted Graph data structure
implemented using an adjacency list. This vertices and edges of this graph can be
effectively initialized and modified while storing your chosen generic
value in each vertex.
Adjacency List: https://en.wikipedia.org/wiki/Adjacency_list
Potential Future Ideas:
- Add a flag to set edge weights on and set edge weights
- Make edge weights and vertex values customizable to store whatever the client wants
- Support multigraph functionality if the client wants it
"""
from __future__ import annotations
import random
import unittest
from pprint import pformat
from typing import Generic, TypeVar
T = TypeVar("T")
class GraphAdjacencyList(Generic[T]):
def __init__(
self, vertices: list[T], edges: list[list[T]], directed: bool = True
) -> None:
"""
Parameters:
- vertices: (list[T]) The list of vertex names the client wants to
pass in. Default is empty.
- edges: (list[list[T]]) The list of edges the client wants to
pass in. Each edge is a 2-element list. Default is empty.
- directed: (bool) Indicates if graph is directed or undirected.
Default is True.
"""
self.adj_list: dict[T, list[T]] = {} # dictionary of lists of T
self.directed = directed
# Falsey checks
edges = edges or []
vertices = vertices or []
for vertex in vertices:
self.add_vertex(vertex)
for edge in edges:
if len(edge) != 2:
msg = f"Invalid input: {edge} is the wrong length."
raise ValueError(msg)
self.add_edge(edge[0], edge[1])
def add_vertex(self, vertex: T) -> None:
"""
Adds a vertex to the graph. If the given vertex already exists,
a ValueError will be thrown.
"""
if self.contains_vertex(vertex):
msg = f"Incorrect input: {vertex} is already in the graph."
raise ValueError(msg)
self.adj_list[vertex] = []
def add_edge(self, source_vertex: T, destination_vertex: T) -> None:
"""
Creates an edge from source vertex to destination vertex. If any
given vertex doesn't exist or the edge already exists, a ValueError
will be thrown.
"""
if not (
self.contains_vertex(source_vertex)
and self.contains_vertex(destination_vertex)
):
msg = (
f"Incorrect input: Either {source_vertex} or "
f"{destination_vertex} does not exist"
)
raise ValueError(msg)
if self.contains_edge(source_vertex, destination_vertex):
msg = (
"Incorrect input: The edge already exists between "
f"{source_vertex} and {destination_vertex}"
)
raise ValueError(msg)
# add the destination vertex to the list associated with the source vertex
# and vice versa if not directed
self.adj_list[source_vertex].append(destination_vertex)
if not self.directed:
self.adj_list[destination_vertex].append(source_vertex)
def remove_vertex(self, vertex: T) -> None:
"""
Removes the given vertex from the graph and deletes all incoming and
outgoing edges from the given vertex as well. If the given vertex
does not exist, a ValueError will be thrown.
"""
if not self.contains_vertex(vertex):
msg = f"Incorrect input: {vertex} does not exist in this graph."
raise ValueError(msg)
if not self.directed:
# If not directed, find all neighboring vertices and delete all references
# of edges connecting to the given vertex
for neighbor in self.adj_list[vertex]:
self.adj_list[neighbor].remove(vertex)
else:
# If directed, search all neighbors of all vertices and delete all
# references of edges connecting to the given vertex
for edge_list in self.adj_list.values():
if vertex in edge_list:
edge_list.remove(vertex)
# Finally, delete the given vertex and all of its outgoing edge references
self.adj_list.pop(vertex)
def remove_edge(self, source_vertex: T, destination_vertex: T) -> None:
"""
Removes the edge between the two vertices. If any given vertex
doesn't exist or the edge does not exist, a ValueError will be thrown.
"""
if not (
self.contains_vertex(source_vertex)
and self.contains_vertex(destination_vertex)
):
msg = (
f"Incorrect input: Either {source_vertex} or "
f"{destination_vertex} does not exist"
)
raise ValueError(msg)
if not self.contains_edge(source_vertex, destination_vertex):
msg = (
"Incorrect input: The edge does NOT exist between "
f"{source_vertex} and {destination_vertex}"
)
raise ValueError(msg)
# remove the destination vertex from the list associated with the source
# vertex and vice versa if not directed
self.adj_list[source_vertex].remove(destination_vertex)
if not self.directed:
self.adj_list[destination_vertex].remove(source_vertex)
def contains_vertex(self, vertex: T) -> bool:
"""
Returns True if the graph contains the vertex, False otherwise.
"""
return vertex in self.adj_list
def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool:
"""
Returns True if the graph contains the edge from the source_vertex to the
destination_vertex, False otherwise. If any given vertex doesn't exist, a
ValueError will be thrown.
"""
if not (
self.contains_vertex(source_vertex)
and self.contains_vertex(destination_vertex)
):
msg = (
f"Incorrect input: Either {source_vertex} "
f"or {destination_vertex} does not exist."
)
raise ValueError(msg)
return destination_vertex in self.adj_list[source_vertex]
def clear_graph(self) -> None:
"""
Clears all vertices and edges.
"""
self.adj_list = {}
def __repr__(self) -> str:
return pformat(self.adj_list)
class TestGraphAdjacencyList(unittest.TestCase):
def __assert_graph_edge_exists_check(
self,
undirected_graph: GraphAdjacencyList,
directed_graph: GraphAdjacencyList,
edge: list[int],
) -> None:
self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1]))
self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0]))
self.assertTrue(directed_graph.contains_edge(edge[0], edge[1]))
def __assert_graph_edge_does_not_exist_check(
self,
undirected_graph: GraphAdjacencyList,
directed_graph: GraphAdjacencyList,
edge: list[int],
) -> None:
self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1]))
self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0]))
self.assertFalse(directed_graph.contains_edge(edge[0], edge[1]))
def __assert_graph_vertex_exists_check(
self,
undirected_graph: GraphAdjacencyList,
directed_graph: GraphAdjacencyList,
vertex: int,
) -> None:
self.assertTrue(undirected_graph.contains_vertex(vertex))
self.assertTrue(directed_graph.contains_vertex(vertex))
def __assert_graph_vertex_does_not_exist_check(
self,
undirected_graph: GraphAdjacencyList,
directed_graph: GraphAdjacencyList,
vertex: int,
) -> None:
self.assertFalse(undirected_graph.contains_vertex(vertex))
self.assertFalse(directed_graph.contains_vertex(vertex))
def __generate_random_edges(
self, vertices: list[int], edge_pick_count: int
) -> list[list[int]]:
self.assertTrue(edge_pick_count <= len(vertices))
random_source_vertices: list[int] = random.sample(
vertices[0 : int(len(vertices) / 2)], edge_pick_count
)
random_destination_vertices: list[int] = random.sample(
vertices[int(len(vertices) / 2) :], edge_pick_count
)
random_edges: list[list[int]] = []
for source in random_source_vertices:
for dest in random_destination_vertices:
random_edges.append([source, dest])
return random_edges
def __generate_graphs(
self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int
) -> tuple[GraphAdjacencyList, GraphAdjacencyList, list[int], list[list[int]]]:
if max_val - min_val + 1 < vertex_count:
raise ValueError(
"Will result in duplicate vertices. Either increase range "
"between min_val and max_val or decrease vertex count."
)
# generate graph input
random_vertices: list[int] = random.sample(
range(min_val, max_val + 1), vertex_count
)
random_edges: list[list[int]] = self.__generate_random_edges(
random_vertices, edge_pick_count
)
# build graphs
undirected_graph = GraphAdjacencyList(
vertices=random_vertices, edges=random_edges, directed=False
)
directed_graph = GraphAdjacencyList(
vertices=random_vertices, edges=random_edges, directed=True
)
return undirected_graph, directed_graph, random_vertices, random_edges
def test_init_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
# test graph initialization with vertices and edges
for num in random_vertices:
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, num
)
for edge in random_edges:
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
self.assertFalse(undirected_graph.directed)
self.assertTrue(directed_graph.directed)
def test_contains_vertex(self) -> None:
random_vertices: list[int] = random.sample(range(101), 20)
# Build graphs WITHOUT edges
undirected_graph = GraphAdjacencyList(
vertices=random_vertices, edges=[], directed=False
)
directed_graph = GraphAdjacencyList(
vertices=random_vertices, edges=[], directed=True
)
# Test contains_vertex
for num in range(101):
self.assertEqual(
num in random_vertices, undirected_graph.contains_vertex(num)
)
self.assertEqual(
num in random_vertices, directed_graph.contains_vertex(num)
)
def test_add_vertices(self) -> None:
random_vertices: list[int] = random.sample(range(101), 20)
# build empty graphs
undirected_graph: GraphAdjacencyList = GraphAdjacencyList(
vertices=[], edges=[], directed=False
)
directed_graph: GraphAdjacencyList = GraphAdjacencyList(
vertices=[], edges=[], directed=True
)
# run add_vertex
for num in random_vertices:
undirected_graph.add_vertex(num)
for num in random_vertices:
directed_graph.add_vertex(num)
# test add_vertex worked
for num in random_vertices:
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, num
)
def test_remove_vertices(self) -> None:
random_vertices: list[int] = random.sample(range(101), 20)
# build graphs WITHOUT edges
undirected_graph = GraphAdjacencyList(
vertices=random_vertices, edges=[], directed=False
)
directed_graph = GraphAdjacencyList(
vertices=random_vertices, edges=[], directed=True
)
# test remove_vertex worked
for num in random_vertices:
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, num
)
undirected_graph.remove_vertex(num)
directed_graph.remove_vertex(num)
self.__assert_graph_vertex_does_not_exist_check(
undirected_graph, directed_graph, num
)
def test_add_and_remove_vertices_repeatedly(self) -> None:
random_vertices1: list[int] = random.sample(range(51), 20)
random_vertices2: list[int] = random.sample(range(51, 101), 20)
# build graphs WITHOUT edges
undirected_graph = GraphAdjacencyList(
vertices=random_vertices1, edges=[], directed=False
)
directed_graph = GraphAdjacencyList(
vertices=random_vertices1, edges=[], directed=True
)
# test adding and removing vertices
for i, _ in enumerate(random_vertices1):
undirected_graph.add_vertex(random_vertices2[i])
directed_graph.add_vertex(random_vertices2[i])
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, random_vertices2[i]
)
undirected_graph.remove_vertex(random_vertices1[i])
directed_graph.remove_vertex(random_vertices1[i])
self.__assert_graph_vertex_does_not_exist_check(
undirected_graph, directed_graph, random_vertices1[i]
)
# remove all vertices
for i, _ in enumerate(random_vertices1):
undirected_graph.remove_vertex(random_vertices2[i])
directed_graph.remove_vertex(random_vertices2[i])
self.__assert_graph_vertex_does_not_exist_check(
undirected_graph, directed_graph, random_vertices2[i]
)
def test_contains_edge(self) -> None:
# generate graphs and graph input
vertex_count = 20
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(vertex_count, 0, 100, 4)
# generate all possible edges for testing
all_possible_edges: list[list[int]] = []
for i in range(vertex_count - 1):
for j in range(i + 1, vertex_count):
all_possible_edges.append([random_vertices[i], random_vertices[j]])
all_possible_edges.append([random_vertices[j], random_vertices[i]])
# test contains_edge function
for edge in all_possible_edges:
if edge in random_edges:
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
elif [edge[1], edge[0]] in random_edges:
# since this edge exists for undirected but the reverse
# may not exist for directed
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, [edge[1], edge[0]]
)
else:
self.__assert_graph_edge_does_not_exist_check(
undirected_graph, directed_graph, edge
)
def test_add_edge(self) -> None:
# generate graph input
random_vertices: list[int] = random.sample(range(101), 15)
random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4)
# build graphs WITHOUT edges
undirected_graph = GraphAdjacencyList(
vertices=random_vertices, edges=[], directed=False
)
directed_graph = GraphAdjacencyList(
vertices=random_vertices, edges=[], directed=True
)
# run and test add_edge
for edge in random_edges:
undirected_graph.add_edge(edge[0], edge[1])
directed_graph.add_edge(edge[0], edge[1])
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
def test_remove_edge(self) -> None:
# generate graph input and graphs
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
# run and test remove_edge
for edge in random_edges:
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
undirected_graph.remove_edge(edge[0], edge[1])
directed_graph.remove_edge(edge[0], edge[1])
self.__assert_graph_edge_does_not_exist_check(
undirected_graph, directed_graph, edge
)
def test_add_and_remove_edges_repeatedly(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
# make some more edge options!
more_random_edges: list[list[int]] = []
while len(more_random_edges) != len(random_edges):
edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4)
for edge in edges:
if len(more_random_edges) == len(random_edges):
break
elif edge not in more_random_edges and edge not in random_edges:
more_random_edges.append(edge)
for i, _ in enumerate(random_edges):
undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1])
directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1])
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, more_random_edges[i]
)
undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1])
directed_graph.remove_edge(random_edges[i][0], random_edges[i][1])
self.__assert_graph_edge_does_not_exist_check(
undirected_graph, directed_graph, random_edges[i]
)
def test_add_vertex_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for vertex in random_vertices:
with self.assertRaises(ValueError):
undirected_graph.add_vertex(vertex)
with self.assertRaises(ValueError):
directed_graph.add_vertex(vertex)
def test_remove_vertex_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for i in range(101):
if i not in random_vertices:
with self.assertRaises(ValueError):
undirected_graph.remove_vertex(i)
with self.assertRaises(ValueError):
directed_graph.remove_vertex(i)
def test_add_edge_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for edge in random_edges:
with self.assertRaises(ValueError):
undirected_graph.add_edge(edge[0], edge[1])
with self.assertRaises(ValueError):
directed_graph.add_edge(edge[0], edge[1])
def test_remove_edge_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
more_random_edges: list[list[int]] = []
while len(more_random_edges) != len(random_edges):
edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4)
for edge in edges:
if len(more_random_edges) == len(random_edges):
break
elif edge not in more_random_edges and edge not in random_edges:
more_random_edges.append(edge)
for edge in more_random_edges:
with self.assertRaises(ValueError):
undirected_graph.remove_edge(edge[0], edge[1])
with self.assertRaises(ValueError):
directed_graph.remove_edge(edge[0], edge[1])
def test_contains_edge_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for vertex in random_vertices:
with self.assertRaises(ValueError):
undirected_graph.contains_edge(vertex, 102)
with self.assertRaises(ValueError):
directed_graph.contains_edge(vertex, 102)
with self.assertRaises(ValueError):
undirected_graph.contains_edge(103, 102)
with self.assertRaises(ValueError):
directed_graph.contains_edge(103, 102)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1,608 @@
#!/usr/bin/env python3
"""
Author: Vikram Nithyanandam
Description:
The following implementation is a robust unweighted Graph data structure
implemented using an adjacency matrix. This vertices and edges of this graph can be
effectively initialized and modified while storing your chosen generic
value in each vertex.
Adjacency Matrix: https://mathworld.wolfram.com/AdjacencyMatrix.html
Potential Future Ideas:
- Add a flag to set edge weights on and set edge weights
- Make edge weights and vertex values customizable to store whatever the client wants
- Support multigraph functionality if the client wants it
"""
from __future__ import annotations
import random
import unittest
from pprint import pformat
from typing import Generic, TypeVar
T = TypeVar("T")
class GraphAdjacencyMatrix(Generic[T]):
def __init__(
self, vertices: list[T], edges: list[list[T]], directed: bool = True
) -> None:
"""
Parameters:
- vertices: (list[T]) The list of vertex names the client wants to
pass in. Default is empty.
- edges: (list[list[T]]) The list of edges the client wants to
pass in. Each edge is a 2-element list. Default is empty.
- directed: (bool) Indicates if graph is directed or undirected.
Default is True.
"""
self.directed = directed
self.vertex_to_index: dict[T, int] = {}
self.adj_matrix: list[list[int]] = []
# Falsey checks
edges = edges or []
vertices = vertices or []
for vertex in vertices:
self.add_vertex(vertex)
for edge in edges:
if len(edge) != 2:
msg = f"Invalid input: {edge} must have length 2."
raise ValueError(msg)
self.add_edge(edge[0], edge[1])
def add_edge(self, source_vertex: T, destination_vertex: T) -> None:
"""
Creates an edge from source vertex to destination vertex. If any
given vertex doesn't exist or the edge already exists, a ValueError
will be thrown.
"""
if not (
self.contains_vertex(source_vertex)
and self.contains_vertex(destination_vertex)
):
msg = (
f"Incorrect input: Either {source_vertex} or "
f"{destination_vertex} does not exist"
)
raise ValueError(msg)
if self.contains_edge(source_vertex, destination_vertex):
msg = (
"Incorrect input: The edge already exists between "
f"{source_vertex} and {destination_vertex}"
)
raise ValueError(msg)
# Get the indices of the corresponding vertices and set their edge value to 1.
u: int = self.vertex_to_index[source_vertex]
v: int = self.vertex_to_index[destination_vertex]
self.adj_matrix[u][v] = 1
if not self.directed:
self.adj_matrix[v][u] = 1
def remove_edge(self, source_vertex: T, destination_vertex: T) -> None:
"""
Removes the edge between the two vertices. If any given vertex
doesn't exist or the edge does not exist, a ValueError will be thrown.
"""
if not (
self.contains_vertex(source_vertex)
and self.contains_vertex(destination_vertex)
):
msg = (
f"Incorrect input: Either {source_vertex} or "
f"{destination_vertex} does not exist"
)
raise ValueError(msg)
if not self.contains_edge(source_vertex, destination_vertex):
msg = (
"Incorrect input: The edge does NOT exist between "
f"{source_vertex} and {destination_vertex}"
)
raise ValueError(msg)
# Get the indices of the corresponding vertices and set their edge value to 0.
u: int = self.vertex_to_index[source_vertex]
v: int = self.vertex_to_index[destination_vertex]
self.adj_matrix[u][v] = 0
if not self.directed:
self.adj_matrix[v][u] = 0
def add_vertex(self, vertex: T) -> None:
"""
Adds a vertex to the graph. If the given vertex already exists,
a ValueError will be thrown.
"""
if self.contains_vertex(vertex):
msg = f"Incorrect input: {vertex} already exists in this graph."
raise ValueError(msg)
# build column for vertex
for row in self.adj_matrix:
row.append(0)
# build row for vertex and update other data structures
self.adj_matrix.append([0] * (len(self.adj_matrix) + 1))
self.vertex_to_index[vertex] = len(self.adj_matrix) - 1
def remove_vertex(self, vertex: T) -> None:
"""
Removes the given vertex from the graph and deletes all incoming and
outgoing edges from the given vertex as well. If the given vertex
does not exist, a ValueError will be thrown.
"""
if not self.contains_vertex(vertex):
msg = f"Incorrect input: {vertex} does not exist in this graph."
raise ValueError(msg)
# first slide up the rows by deleting the row corresponding to
# the vertex being deleted.
start_index = self.vertex_to_index[vertex]
self.adj_matrix.pop(start_index)
# next, slide the columns to the left by deleting the values in
# the column corresponding to the vertex being deleted
for lst in self.adj_matrix:
lst.pop(start_index)
# final clean up
self.vertex_to_index.pop(vertex)
# decrement indices for vertices shifted by the deleted vertex in the adj matrix
for vertex in self.vertex_to_index:
if self.vertex_to_index[vertex] >= start_index:
self.vertex_to_index[vertex] = self.vertex_to_index[vertex] - 1
def contains_vertex(self, vertex: T) -> bool:
"""
Returns True if the graph contains the vertex, False otherwise.
"""
return vertex in self.vertex_to_index
def contains_edge(self, source_vertex: T, destination_vertex: T) -> bool:
"""
Returns True if the graph contains the edge from the source_vertex to the
destination_vertex, False otherwise. If any given vertex doesn't exist, a
ValueError will be thrown.
"""
if not (
self.contains_vertex(source_vertex)
and self.contains_vertex(destination_vertex)
):
msg = (
f"Incorrect input: Either {source_vertex} "
f"or {destination_vertex} does not exist."
)
raise ValueError(msg)
u = self.vertex_to_index[source_vertex]
v = self.vertex_to_index[destination_vertex]
return self.adj_matrix[u][v] == 1
def clear_graph(self) -> None:
"""
Clears all vertices and edges.
"""
self.vertex_to_index = {}
self.adj_matrix = []
def __repr__(self) -> str:
first = "Adj Matrix:\n" + pformat(self.adj_matrix)
second = "\nVertex to index mapping:\n" + pformat(self.vertex_to_index)
return first + second
class TestGraphMatrix(unittest.TestCase):
def __assert_graph_edge_exists_check(
self,
undirected_graph: GraphAdjacencyMatrix,
directed_graph: GraphAdjacencyMatrix,
edge: list[int],
) -> None:
self.assertTrue(undirected_graph.contains_edge(edge[0], edge[1]))
self.assertTrue(undirected_graph.contains_edge(edge[1], edge[0]))
self.assertTrue(directed_graph.contains_edge(edge[0], edge[1]))
def __assert_graph_edge_does_not_exist_check(
self,
undirected_graph: GraphAdjacencyMatrix,
directed_graph: GraphAdjacencyMatrix,
edge: list[int],
) -> None:
self.assertFalse(undirected_graph.contains_edge(edge[0], edge[1]))
self.assertFalse(undirected_graph.contains_edge(edge[1], edge[0]))
self.assertFalse(directed_graph.contains_edge(edge[0], edge[1]))
def __assert_graph_vertex_exists_check(
self,
undirected_graph: GraphAdjacencyMatrix,
directed_graph: GraphAdjacencyMatrix,
vertex: int,
) -> None:
self.assertTrue(undirected_graph.contains_vertex(vertex))
self.assertTrue(directed_graph.contains_vertex(vertex))
def __assert_graph_vertex_does_not_exist_check(
self,
undirected_graph: GraphAdjacencyMatrix,
directed_graph: GraphAdjacencyMatrix,
vertex: int,
) -> None:
self.assertFalse(undirected_graph.contains_vertex(vertex))
self.assertFalse(directed_graph.contains_vertex(vertex))
def __generate_random_edges(
self, vertices: list[int], edge_pick_count: int
) -> list[list[int]]:
self.assertTrue(edge_pick_count <= len(vertices))
random_source_vertices: list[int] = random.sample(
vertices[0 : int(len(vertices) / 2)], edge_pick_count
)
random_destination_vertices: list[int] = random.sample(
vertices[int(len(vertices) / 2) :], edge_pick_count
)
random_edges: list[list[int]] = []
for source in random_source_vertices:
for dest in random_destination_vertices:
random_edges.append([source, dest])
return random_edges
def __generate_graphs(
self, vertex_count: int, min_val: int, max_val: int, edge_pick_count: int
) -> tuple[GraphAdjacencyMatrix, GraphAdjacencyMatrix, list[int], list[list[int]]]:
if max_val - min_val + 1 < vertex_count:
raise ValueError(
"Will result in duplicate vertices. Either increase "
"range between min_val and max_val or decrease vertex count"
)
# generate graph input
random_vertices: list[int] = random.sample(
range(min_val, max_val + 1), vertex_count
)
random_edges: list[list[int]] = self.__generate_random_edges(
random_vertices, edge_pick_count
)
# build graphs
undirected_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=random_edges, directed=False
)
directed_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=random_edges, directed=True
)
return undirected_graph, directed_graph, random_vertices, random_edges
def test_init_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
# test graph initialization with vertices and edges
for num in random_vertices:
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, num
)
for edge in random_edges:
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
self.assertFalse(undirected_graph.directed)
self.assertTrue(directed_graph.directed)
def test_contains_vertex(self) -> None:
random_vertices: list[int] = random.sample(range(101), 20)
# Build graphs WITHOUT edges
undirected_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=[], directed=False
)
directed_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=[], directed=True
)
# Test contains_vertex
for num in range(101):
self.assertEqual(
num in random_vertices, undirected_graph.contains_vertex(num)
)
self.assertEqual(
num in random_vertices, directed_graph.contains_vertex(num)
)
def test_add_vertices(self) -> None:
random_vertices: list[int] = random.sample(range(101), 20)
# build empty graphs
undirected_graph: GraphAdjacencyMatrix = GraphAdjacencyMatrix(
vertices=[], edges=[], directed=False
)
directed_graph: GraphAdjacencyMatrix = GraphAdjacencyMatrix(
vertices=[], edges=[], directed=True
)
# run add_vertex
for num in random_vertices:
undirected_graph.add_vertex(num)
for num in random_vertices:
directed_graph.add_vertex(num)
# test add_vertex worked
for num in random_vertices:
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, num
)
def test_remove_vertices(self) -> None:
random_vertices: list[int] = random.sample(range(101), 20)
# build graphs WITHOUT edges
undirected_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=[], directed=False
)
directed_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=[], directed=True
)
# test remove_vertex worked
for num in random_vertices:
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, num
)
undirected_graph.remove_vertex(num)
directed_graph.remove_vertex(num)
self.__assert_graph_vertex_does_not_exist_check(
undirected_graph, directed_graph, num
)
def test_add_and_remove_vertices_repeatedly(self) -> None:
random_vertices1: list[int] = random.sample(range(51), 20)
random_vertices2: list[int] = random.sample(range(51, 101), 20)
# build graphs WITHOUT edges
undirected_graph = GraphAdjacencyMatrix(
vertices=random_vertices1, edges=[], directed=False
)
directed_graph = GraphAdjacencyMatrix(
vertices=random_vertices1, edges=[], directed=True
)
# test adding and removing vertices
for i, _ in enumerate(random_vertices1):
undirected_graph.add_vertex(random_vertices2[i])
directed_graph.add_vertex(random_vertices2[i])
self.__assert_graph_vertex_exists_check(
undirected_graph, directed_graph, random_vertices2[i]
)
undirected_graph.remove_vertex(random_vertices1[i])
directed_graph.remove_vertex(random_vertices1[i])
self.__assert_graph_vertex_does_not_exist_check(
undirected_graph, directed_graph, random_vertices1[i]
)
# remove all vertices
for i, _ in enumerate(random_vertices1):
undirected_graph.remove_vertex(random_vertices2[i])
directed_graph.remove_vertex(random_vertices2[i])
self.__assert_graph_vertex_does_not_exist_check(
undirected_graph, directed_graph, random_vertices2[i]
)
def test_contains_edge(self) -> None:
# generate graphs and graph input
vertex_count = 20
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(vertex_count, 0, 100, 4)
# generate all possible edges for testing
all_possible_edges: list[list[int]] = []
for i in range(vertex_count - 1):
for j in range(i + 1, vertex_count):
all_possible_edges.append([random_vertices[i], random_vertices[j]])
all_possible_edges.append([random_vertices[j], random_vertices[i]])
# test contains_edge function
for edge in all_possible_edges:
if edge in random_edges:
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
elif [edge[1], edge[0]] in random_edges:
# since this edge exists for undirected but the reverse may
# not exist for directed
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, [edge[1], edge[0]]
)
else:
self.__assert_graph_edge_does_not_exist_check(
undirected_graph, directed_graph, edge
)
def test_add_edge(self) -> None:
# generate graph input
random_vertices: list[int] = random.sample(range(101), 15)
random_edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4)
# build graphs WITHOUT edges
undirected_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=[], directed=False
)
directed_graph = GraphAdjacencyMatrix(
vertices=random_vertices, edges=[], directed=True
)
# run and test add_edge
for edge in random_edges:
undirected_graph.add_edge(edge[0], edge[1])
directed_graph.add_edge(edge[0], edge[1])
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
def test_remove_edge(self) -> None:
# generate graph input and graphs
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
# run and test remove_edge
for edge in random_edges:
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, edge
)
undirected_graph.remove_edge(edge[0], edge[1])
directed_graph.remove_edge(edge[0], edge[1])
self.__assert_graph_edge_does_not_exist_check(
undirected_graph, directed_graph, edge
)
def test_add_and_remove_edges_repeatedly(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
# make some more edge options!
more_random_edges: list[list[int]] = []
while len(more_random_edges) != len(random_edges):
edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4)
for edge in edges:
if len(more_random_edges) == len(random_edges):
break
elif edge not in more_random_edges and edge not in random_edges:
more_random_edges.append(edge)
for i, _ in enumerate(random_edges):
undirected_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1])
directed_graph.add_edge(more_random_edges[i][0], more_random_edges[i][1])
self.__assert_graph_edge_exists_check(
undirected_graph, directed_graph, more_random_edges[i]
)
undirected_graph.remove_edge(random_edges[i][0], random_edges[i][1])
directed_graph.remove_edge(random_edges[i][0], random_edges[i][1])
self.__assert_graph_edge_does_not_exist_check(
undirected_graph, directed_graph, random_edges[i]
)
def test_add_vertex_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for vertex in random_vertices:
with self.assertRaises(ValueError):
undirected_graph.add_vertex(vertex)
with self.assertRaises(ValueError):
directed_graph.add_vertex(vertex)
def test_remove_vertex_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for i in range(101):
if i not in random_vertices:
with self.assertRaises(ValueError):
undirected_graph.remove_vertex(i)
with self.assertRaises(ValueError):
directed_graph.remove_vertex(i)
def test_add_edge_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for edge in random_edges:
with self.assertRaises(ValueError):
undirected_graph.add_edge(edge[0], edge[1])
with self.assertRaises(ValueError):
directed_graph.add_edge(edge[0], edge[1])
def test_remove_edge_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
more_random_edges: list[list[int]] = []
while len(more_random_edges) != len(random_edges):
edges: list[list[int]] = self.__generate_random_edges(random_vertices, 4)
for edge in edges:
if len(more_random_edges) == len(random_edges):
break
elif edge not in more_random_edges and edge not in random_edges:
more_random_edges.append(edge)
for edge in more_random_edges:
with self.assertRaises(ValueError):
undirected_graph.remove_edge(edge[0], edge[1])
with self.assertRaises(ValueError):
directed_graph.remove_edge(edge[0], edge[1])
def test_contains_edge_exception_check(self) -> None:
(
undirected_graph,
directed_graph,
random_vertices,
random_edges,
) = self.__generate_graphs(20, 0, 100, 4)
for vertex in random_vertices:
with self.assertRaises(ValueError):
undirected_graph.contains_edge(vertex, 102)
with self.assertRaises(ValueError):
directed_graph.contains_edge(vertex, 102)
with self.assertRaises(ValueError):
undirected_graph.contains_edge(103, 102)
with self.assertRaises(ValueError):
directed_graph.contains_edge(103, 102)
if __name__ == "__main__":
unittest.main()

View File

@ -1,24 +0,0 @@
class Graph:
def __init__(self, vertex):
self.vertex = vertex
self.graph = [[0] * vertex for i in range(vertex)]
def add_edge(self, u, v):
self.graph[u - 1][v - 1] = 1
self.graph[v - 1][u - 1] = 1
def show(self):
for i in self.graph:
for j in i:
print(j, end=" ")
print(" ")
g = Graph(100)
g.add_edge(1, 4)
g.add_edge(4, 2)
g.add_edge(4, 5)
g.add_edge(2, 5)
g.add_edge(5, 3)
g.show()

0
graphs/tests/__init__.py Normal file
View File

View File

@ -0,0 +1,89 @@
"""
Calculate the rank of a matrix.
See: https://en.wikipedia.org/wiki/Rank_(linear_algebra)
"""
def rank_of_matrix(matrix: list[list[int | float]]) -> int:
"""
Finds the rank of a matrix.
Args:
matrix: The matrix as a list of lists.
Returns:
The rank of the matrix.
Example:
>>> matrix1 = [[1, 2, 3],
... [4, 5, 6],
... [7, 8, 9]]
>>> rank_of_matrix(matrix1)
2
>>> matrix2 = [[1, 0, 0],
... [0, 1, 0],
... [0, 0, 0]]
>>> rank_of_matrix(matrix2)
2
>>> matrix3 = [[1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12]]
>>> rank_of_matrix(matrix3)
2
>>> rank_of_matrix([[2,3,-1,-1],
... [1,-1,-2,4],
... [3,1,3,-2],
... [6,3,0,-7]])
4
>>> rank_of_matrix([[2,1,-3,-6],
... [3,-3,1,2],
... [1,1,1,2]])
3
>>> rank_of_matrix([[2,-1,0],
... [1,3,4],
... [4,1,-3]])
3
>>> rank_of_matrix([[3,2,1],
... [-6,-4,-2]])
1
>>> rank_of_matrix([[],[]])
0
>>> rank_of_matrix([[1]])
1
>>> rank_of_matrix([[]])
0
"""
rows = len(matrix)
columns = len(matrix[0])
rank = min(rows, columns)
for row in range(rank):
# Check if diagonal element is not zero
if matrix[row][row] != 0:
# Eliminate all the elements below the diagonal
for col in range(row + 1, rows):
multiplier = matrix[col][row] / matrix[row][row]
for i in range(row, columns):
matrix[col][i] -= multiplier * matrix[row][i]
else:
# Find a non-zero diagonal element to swap rows
reduce = True
for i in range(row + 1, rows):
if matrix[i][row] != 0:
matrix[row], matrix[i] = matrix[i], matrix[row]
reduce = False
break
if reduce:
rank -= 1
for i in range(rows):
matrix[i][row] = matrix[i][rank]
# Reduce the row pointer by one to stay on the same row
row -= 1
return rank
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,311 @@
"""
Python implementation of the simplex algorithm for solving linear programs in
tabular form with
- `>=`, `<=`, and `=` constraints and
- each variable `x1, x2, ...>= 0`.
See https://gist.github.com/imengus/f9619a568f7da5bc74eaf20169a24d98 for how to
convert linear programs to simplex tableaus, and the steps taken in the simplex
algorithm.
Resources:
https://en.wikipedia.org/wiki/Simplex_algorithm
https://tinyurl.com/simplex4beginners
"""
from typing import Any
import numpy as np
class Tableau:
"""Operate on simplex tableaus
>>> t = Tableau(np.array([[-1,-1,0,0,-1],[1,3,1,0,4],[3,1,0,1,4.]]), 2)
Traceback (most recent call last):
...
ValueError: RHS must be > 0
"""
def __init__(self, tableau: np.ndarray, n_vars: int) -> None:
# Check if RHS is negative
if np.any(tableau[:, -1], where=tableau[:, -1] < 0):
raise ValueError("RHS must be > 0")
self.tableau = tableau
self.n_rows, _ = tableau.shape
# Number of decision variables x1, x2, x3...
self.n_vars = n_vars
# Number of artificial variables to be minimised
self.n_art_vars = len(np.where(tableau[self.n_vars : -1] == -1)[0])
# 2 if there are >= or == constraints (nonstandard), 1 otherwise (std)
self.n_stages = (self.n_art_vars > 0) + 1
# Number of slack variables added to make inequalities into equalities
self.n_slack = self.n_rows - self.n_stages
# Objectives for each stage
self.objectives = ["max"]
# In two stage simplex, first minimise then maximise
if self.n_art_vars:
self.objectives.append("min")
self.col_titles = [""]
# Index of current pivot row and column
self.row_idx = None
self.col_idx = None
# Does objective row only contain (non)-negative values?
self.stop_iter = False
@staticmethod
def generate_col_titles(*args: int) -> list[str]:
"""Generate column titles for tableau of specific dimensions
>>> Tableau.generate_col_titles(2, 3, 1)
['x1', 'x2', 's1', 's2', 's3', 'a1', 'RHS']
>>> Tableau.generate_col_titles()
Traceback (most recent call last):
...
ValueError: Must provide n_vars, n_slack, and n_art_vars
>>> Tableau.generate_col_titles(-2, 3, 1)
Traceback (most recent call last):
...
ValueError: All arguments must be non-negative integers
"""
if len(args) != 3:
raise ValueError("Must provide n_vars, n_slack, and n_art_vars")
if not all(x >= 0 and isinstance(x, int) for x in args):
raise ValueError("All arguments must be non-negative integers")
# decision | slack | artificial
string_starts = ["x", "s", "a"]
titles = []
for i in range(3):
for j in range(args[i]):
titles.append(string_starts[i] + str(j + 1))
titles.append("RHS")
return titles
def find_pivot(self, tableau: np.ndarray) -> tuple[Any, Any]:
"""Finds the pivot row and column.
>>> t = Tableau(np.array([[-2,1,0,0,0], [3,1,1,0,6], [1,2,0,1,7.]]), 2)
>>> t.find_pivot(t.tableau)
(1, 0)
"""
objective = self.objectives[-1]
# Find entries of highest magnitude in objective rows
sign = (objective == "min") - (objective == "max")
col_idx = np.argmax(sign * tableau[0, : self.n_vars])
# Choice is only valid if below 0 for maximise, and above for minimise
if sign * self.tableau[0, col_idx] <= 0:
self.stop_iter = True
return 0, 0
# Pivot row is chosen as having the lowest quotient when elements of
# the pivot column divide the right-hand side
# Slice excluding the objective rows
s = slice(self.n_stages, self.n_rows)
# RHS
dividend = tableau[s, -1]
# Elements of pivot column within slice
divisor = tableau[s, col_idx]
# Array filled with nans
nans = np.full(self.n_rows - self.n_stages, np.nan)
# If element in pivot column is greater than zeron_stages, return
# quotient or nan otherwise
quotients = np.divide(dividend, divisor, out=nans, where=divisor > 0)
# Arg of minimum quotient excluding the nan values. n_stages is added
# to compensate for earlier exclusion of objective columns
row_idx = np.nanargmin(quotients) + self.n_stages
return row_idx, col_idx
def pivot(self, tableau: np.ndarray, row_idx: int, col_idx: int) -> np.ndarray:
"""Pivots on value on the intersection of pivot row and column.
>>> t = Tableau(np.array([[-2,-3,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]), 2)
>>> t.pivot(t.tableau, 1, 0).tolist()
... # doctest: +NORMALIZE_WHITESPACE
[[0.0, 3.0, 2.0, 0.0, 8.0],
[1.0, 3.0, 1.0, 0.0, 4.0],
[0.0, -8.0, -3.0, 1.0, -8.0]]
"""
# Avoid changes to original tableau
piv_row = tableau[row_idx].copy()
piv_val = piv_row[col_idx]
# Entry becomes 1
piv_row *= 1 / piv_val
# Variable in pivot column becomes basic, ie the only non-zero entry
for idx, coeff in enumerate(tableau[:, col_idx]):
tableau[idx] += -coeff * piv_row
tableau[row_idx] = piv_row
return tableau
def change_stage(self, tableau: np.ndarray) -> np.ndarray:
"""Exits first phase of the two-stage method by deleting artificial
rows and columns, or completes the algorithm if exiting the standard
case.
>>> t = Tableau(np.array([
... [3, 3, -1, -1, 0, 0, 4],
... [2, 1, 0, 0, 0, 0, 0.],
... [1, 2, -1, 0, 1, 0, 2],
... [2, 1, 0, -1, 0, 1, 2]
... ]), 2)
>>> t.change_stage(t.tableau).tolist()
... # doctest: +NORMALIZE_WHITESPACE
[[2.0, 1.0, 0.0, 0.0, 0.0, 0.0],
[1.0, 2.0, -1.0, 0.0, 1.0, 2.0],
[2.0, 1.0, 0.0, -1.0, 0.0, 2.0]]
"""
# Objective of original objective row remains
self.objectives.pop()
if not self.objectives:
return tableau
# Slice containing ids for artificial columns
s = slice(-self.n_art_vars - 1, -1)
# Delete the artificial variable columns
tableau = np.delete(tableau, s, axis=1)
# Delete the objective row of the first stage
tableau = np.delete(tableau, 0, axis=0)
self.n_stages = 1
self.n_rows -= 1
self.n_art_vars = 0
self.stop_iter = False
return tableau
def run_simplex(self) -> dict[Any, Any]:
"""Operate on tableau until objective function cannot be
improved further.
# Standard linear program:
Max: x1 + x2
ST: x1 + 3x2 <= 4
3x1 + x2 <= 4
>>> Tableau(np.array([[-1,-1,0,0,0],[1,3,1,0,4],[3,1,0,1,4.]]),
... 2).run_simplex()
{'P': 2.0, 'x1': 1.0, 'x2': 1.0}
# Optimal tableau input:
>>> Tableau(np.array([
... [0, 0, 0.25, 0.25, 2],
... [0, 1, 0.375, -0.125, 1],
... [1, 0, -0.125, 0.375, 1]
... ]), 2).run_simplex()
{'P': 2.0, 'x1': 1.0, 'x2': 1.0}
# Non-standard: >= constraints
Max: 2x1 + 3x2 + x3
ST: x1 + x2 + x3 <= 40
2x1 + x2 - x3 >= 10
- x2 + x3 >= 10
>>> Tableau(np.array([
... [2, 0, 0, 0, -1, -1, 0, 0, 20],
... [-2, -3, -1, 0, 0, 0, 0, 0, 0],
... [1, 1, 1, 1, 0, 0, 0, 0, 40],
... [2, 1, -1, 0, -1, 0, 1, 0, 10],
... [0, -1, 1, 0, 0, -1, 0, 1, 10.]
... ]), 3).run_simplex()
{'P': 70.0, 'x1': 10.0, 'x2': 10.0, 'x3': 20.0}
# Non standard: minimisation and equalities
Min: x1 + x2
ST: 2x1 + x2 = 12
6x1 + 5x2 = 40
>>> Tableau(np.array([
... [8, 6, 0, -1, 0, -1, 0, 0, 52],
... [1, 1, 0, 0, 0, 0, 0, 0, 0],
... [2, 1, 1, 0, 0, 0, 0, 0, 12],
... [2, 1, 0, -1, 0, 0, 1, 0, 12],
... [6, 5, 0, 0, 1, 0, 0, 0, 40],
... [6, 5, 0, 0, 0, -1, 0, 1, 40.]
... ]), 2).run_simplex()
{'P': 7.0, 'x1': 5.0, 'x2': 2.0}
"""
# Stop simplex algorithm from cycling.
for _ in range(100):
# Completion of each stage removes an objective. If both stages
# are complete, then no objectives are left
if not self.objectives:
self.col_titles = self.generate_col_titles(
self.n_vars, self.n_slack, self.n_art_vars
)
# Find the values of each variable at optimal solution
return self.interpret_tableau(self.tableau, self.col_titles)
row_idx, col_idx = self.find_pivot(self.tableau)
# If there are no more negative values in objective row
if self.stop_iter:
# Delete artificial variable columns and rows. Update attributes
self.tableau = self.change_stage(self.tableau)
else:
self.tableau = self.pivot(self.tableau, row_idx, col_idx)
return {}
def interpret_tableau(
self, tableau: np.ndarray, col_titles: list[str]
) -> dict[str, float]:
"""Given the final tableau, add the corresponding values of the basic
decision variables to the `output_dict`
>>> tableau = np.array([
... [0,0,0.875,0.375,5],
... [0,1,0.375,-0.125,1],
... [1,0,-0.125,0.375,1]
... ])
>>> t = Tableau(tableau, 2)
>>> t.interpret_tableau(tableau, ["x1", "x2", "s1", "s2", "RHS"])
{'P': 5.0, 'x1': 1.0, 'x2': 1.0}
"""
# P = RHS of final tableau
output_dict = {"P": abs(tableau[0, -1])}
for i in range(self.n_vars):
# Gives ids of nonzero entries in the ith column
nonzero = np.nonzero(tableau[:, i])
n_nonzero = len(nonzero[0])
# First entry in the nonzero ids
nonzero_rowidx = nonzero[0][0]
nonzero_val = tableau[nonzero_rowidx, i]
# If there is only one nonzero value in column, which is one
if n_nonzero == nonzero_val == 1:
rhs_val = tableau[nonzero_rowidx, -1]
output_dict[col_titles[i]] = rhs_val
# Check for basic variables
for title in col_titles:
# Don't add RHS or slack variables to output dict
if title[0] not in "R-s-a":
output_dict.setdefault(title, 0)
return output_dict
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -1,44 +0,0 @@
import pandas as pd
from matplotlib import pyplot as plt
from sklearn.linear_model import LinearRegression
# Splitting the dataset into the Training set and Test set
from sklearn.model_selection import train_test_split
# Fitting Polynomial Regression to the dataset
from sklearn.preprocessing import PolynomialFeatures
# Importing the dataset
dataset = pd.read_csv(
"https://s3.us-west-2.amazonaws.com/public.gamelab.fun/dataset/"
"position_salaries.csv"
)
X = dataset.iloc[:, 1:2].values
y = dataset.iloc[:, 2].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
poly_reg = PolynomialFeatures(degree=4)
X_poly = poly_reg.fit_transform(X)
pol_reg = LinearRegression()
pol_reg.fit(X_poly, y)
# Visualizing the Polymonial Regression results
def viz_polymonial():
plt.scatter(X, y, color="red")
plt.plot(X, pol_reg.predict(poly_reg.fit_transform(X)), color="blue")
plt.title("Truth or Bluff (Linear Regression)")
plt.xlabel("Position level")
plt.ylabel("Salary")
plt.show()
if __name__ == "__main__":
viz_polymonial()
# Predicting a new result with Polymonial Regression
pol_reg.predict(poly_reg.fit_transform([[5.5]]))
# output should be 132148.43750003

View File

@ -0,0 +1,213 @@
"""
Polynomial regression is a type of regression analysis that models the relationship
between a predictor x and the response y as an mth-degree polynomial:
y = β₀ + β₁x + β₂x² + ... + βₘxᵐ + ε
By treating x, , ..., xᵐ as distinct variables, we see that polynomial regression is a
special case of multiple linear regression. Therefore, we can use ordinary least squares
(OLS) estimation to estimate the vector of model parameters β = (β₀, β₁, β₂, ..., βₘ)
for polynomial regression:
β = (XᵀX)¹Xᵀy = Xy
where X is the design matrix, y is the response vector, and X denotes the MoorePenrose
pseudoinverse of X. In the case of polynomial regression, the design matrix is
|1 x₁ x₁² x₁ᵐ|
X = |1 x₂ x₂² x₂ᵐ|
| |
|1 xₙ xₙ² xₙᵐ|
In OLS estimation, inverting XᵀX to compute X can be very numerically unstable. This
implementation sidesteps this need to invert XᵀX by computing X using singular value
decomposition (SVD):
β = Uᵀy
where UΣVᵀ is an SVD of X.
References:
- https://en.wikipedia.org/wiki/Polynomial_regression
- https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse
- https://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
- https://en.wikipedia.org/wiki/Singular_value_decomposition
"""
import matplotlib.pyplot as plt
import numpy as np
class PolynomialRegression:
__slots__ = "degree", "params"
def __init__(self, degree: int) -> None:
"""
@raises ValueError: if the polynomial degree is negative
"""
if degree < 0:
raise ValueError("Polynomial degree must be non-negative")
self.degree = degree
self.params = None
@staticmethod
def _design_matrix(data: np.ndarray, degree: int) -> np.ndarray:
"""
Constructs a polynomial regression design matrix for the given input data. For
input data x = (x₁, x₂, ..., xₙ) and polynomial degree m, the design matrix is
the Vandermonde matrix
|1 x₁ x₁² x₁ᵐ|
X = |1 x₂ x₂² x₂ᵐ|
| |
|1 xₙ xₙ² xₙᵐ|
Reference: https://en.wikipedia.org/wiki/Vandermonde_matrix
@param data: the input predictor values x, either for model fitting or for
prediction
@param degree: the polynomial degree m
@returns: the Vandermonde matrix X (see above)
@raises ValueError: if input data is not N x 1
>>> x = np.array([0, 1, 2])
>>> PolynomialRegression._design_matrix(x, degree=0)
array([[1],
[1],
[1]])
>>> PolynomialRegression._design_matrix(x, degree=1)
array([[1, 0],
[1, 1],
[1, 2]])
>>> PolynomialRegression._design_matrix(x, degree=2)
array([[1, 0, 0],
[1, 1, 1],
[1, 2, 4]])
>>> PolynomialRegression._design_matrix(x, degree=3)
array([[1, 0, 0, 0],
[1, 1, 1, 1],
[1, 2, 4, 8]])
>>> PolynomialRegression._design_matrix(np.array([[0, 0], [0 , 0]]), degree=3)
Traceback (most recent call last):
...
ValueError: Data must have dimensions N x 1
"""
rows, *remaining = data.shape
if remaining:
raise ValueError("Data must have dimensions N x 1")
return np.vander(data, N=degree + 1, increasing=True)
def fit(self, x_train: np.ndarray, y_train: np.ndarray) -> None:
"""
Computes the polynomial regression model parameters using ordinary least squares
(OLS) estimation:
β = (XᵀX)¹Xᵀy = Xy
where X denotes the MoorePenrose pseudoinverse of the design matrix X. This
function computes X using singular value decomposition (SVD).
References:
- https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse
- https://en.wikipedia.org/wiki/Singular_value_decomposition
- https://en.wikipedia.org/wiki/Multicollinearity
@param x_train: the predictor values x for model fitting
@param y_train: the response values y for model fitting
@raises ArithmeticError: if X isn't full rank, then XᵀX is singular and β
doesn't exist
>>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>> y = x**3 - 2 * x**2 + 3 * x - 5
>>> poly_reg = PolynomialRegression(degree=3)
>>> poly_reg.fit(x, y)
>>> poly_reg.params
array([-5., 3., -2., 1.])
>>> poly_reg = PolynomialRegression(degree=20)
>>> poly_reg.fit(x, y)
Traceback (most recent call last):
...
ArithmeticError: Design matrix is not full rank, can't compute coefficients
Make sure errors don't grow too large:
>>> coefs = np.array([-250, 50, -2, 36, 20, -12, 10, 2, -1, -15, 1])
>>> y = PolynomialRegression._design_matrix(x, len(coefs) - 1) @ coefs
>>> poly_reg = PolynomialRegression(degree=len(coefs) - 1)
>>> poly_reg.fit(x, y)
>>> np.allclose(poly_reg.params, coefs, atol=10e-3)
True
"""
X = PolynomialRegression._design_matrix(x_train, self.degree) # noqa: N806
_, cols = X.shape
if np.linalg.matrix_rank(X) < cols:
raise ArithmeticError(
"Design matrix is not full rank, can't compute coefficients"
)
# np.linalg.pinv() computes the MoorePenrose pseudoinverse using SVD
self.params = np.linalg.pinv(X) @ y_train
def predict(self, data: np.ndarray) -> np.ndarray:
"""
Computes the predicted response values y for the given input data by
constructing the design matrix X and evaluating y = .
@param data: the predictor values x for prediction
@returns: the predicted response values y =
@raises ArithmeticError: if this function is called before the model
parameters are fit
>>> x = np.array([0, 1, 2, 3, 4])
>>> y = x**3 - 2 * x**2 + 3 * x - 5
>>> poly_reg = PolynomialRegression(degree=3)
>>> poly_reg.fit(x, y)
>>> poly_reg.predict(np.array([-1]))
array([-11.])
>>> poly_reg.predict(np.array([-2]))
array([-27.])
>>> poly_reg.predict(np.array([6]))
array([157.])
>>> PolynomialRegression(degree=3).predict(x)
Traceback (most recent call last):
...
ArithmeticError: Predictor hasn't been fit yet
"""
if self.params is None:
raise ArithmeticError("Predictor hasn't been fit yet")
return PolynomialRegression._design_matrix(data, self.degree) @ self.params
def main() -> None:
"""
Fit a polynomial regression model to predict fuel efficiency using seaborn's mpg
dataset
>>> pass # Placeholder, function is only for demo purposes
"""
import seaborn as sns
mpg_data = sns.load_dataset("mpg")
poly_reg = PolynomialRegression(degree=2)
poly_reg.fit(mpg_data.weight, mpg_data.mpg)
weight_sorted = np.sort(mpg_data.weight)
predictions = poly_reg.predict(weight_sorted)
plt.scatter(mpg_data.weight, mpg_data.mpg, color="gray", alpha=0.5)
plt.plot(weight_sorted, predictions, color="red", linewidth=3)
plt.title("Predicting Fuel Efficiency Using Polynomial Regression")
plt.xlabel("Weight (lbs)")
plt.ylabel("Fuel Efficiency (mpg)")
plt.show()
if __name__ == "__main__":
import doctest
doctest.testmod()
main()

View File

@ -1,151 +0,0 @@
from __future__ import annotations
def n31(a: int) -> tuple[list[int], int]:
"""
Returns the Collatz sequence and its length of any positive integer.
>>> n31(4)
([4, 2, 1], 3)
"""
if not isinstance(a, int):
msg = f"Must be int, not {type(a).__name__}"
raise TypeError(msg)
if a < 1:
msg = f"Given integer must be positive, not {a}"
raise ValueError(msg)
path = [a]
while a != 1:
if a % 2 == 0:
a //= 2
else:
a = 3 * a + 1
path.append(a)
return path, len(path)
def test_n31():
"""
>>> test_n31()
"""
assert n31(4) == ([4, 2, 1], 3)
assert n31(11) == ([11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1], 15)
assert n31(31) == (
[
31,
94,
47,
142,
71,
214,
107,
322,
161,
484,
242,
121,
364,
182,
91,
274,
137,
412,
206,
103,
310,
155,
466,
233,
700,
350,
175,
526,
263,
790,
395,
1186,
593,
1780,
890,
445,
1336,
668,
334,
167,
502,
251,
754,
377,
1132,
566,
283,
850,
425,
1276,
638,
319,
958,
479,
1438,
719,
2158,
1079,
3238,
1619,
4858,
2429,
7288,
3644,
1822,
911,
2734,
1367,
4102,
2051,
6154,
3077,
9232,
4616,
2308,
1154,
577,
1732,
866,
433,
1300,
650,
325,
976,
488,
244,
122,
61,
184,
92,
46,
23,
70,
35,
106,
53,
160,
80,
40,
20,
10,
5,
16,
8,
4,
2,
1,
],
107,
)
if __name__ == "__main__":
num = 4
path, length = n31(num)
print(f"The Collatz sequence of {num} took {length} steps. \nPath: {path}")

View File

@ -1,43 +1,66 @@
"""
The Collatz conjecture is a famous unsolved problem in mathematics. Given a starting
positive integer, define the following sequence:
- If the current term n is even, then the next term is n/2.
- If the current term n is odd, then the next term is 3n + 1.
The conjecture claims that this sequence will always reach 1 for any starting number.
Other names for this problem include the 3n + 1 problem, the Ulam conjecture, Kakutani's
problem, the Thwaites conjecture, Hasse's algorithm, the Syracuse problem, and the
hailstone sequence.
Reference: https://en.wikipedia.org/wiki/Collatz_conjecture
"""
from __future__ import annotations from __future__ import annotations
from collections.abc import Generator
def collatz_sequence(n: int) -> list[int]:
def collatz_sequence(n: int) -> Generator[int, None, None]:
""" """
Collatz conjecture: start with any positive integer n. The next term is Generate the Collatz sequence starting at n.
obtained as follows: >>> tuple(collatz_sequence(2.1))
If n term is even, the next term is: n / 2 .
If n is odd, the next term is: 3 * n + 1.
The conjecture states the sequence will always reach 1 for any starting value n.
Example:
>>> collatz_sequence(2.1)
Traceback (most recent call last): Traceback (most recent call last):
... ...
Exception: Sequence only defined for natural numbers Exception: Sequence only defined for positive integers
>>> collatz_sequence(0) >>> tuple(collatz_sequence(0))
Traceback (most recent call last): Traceback (most recent call last):
... ...
Exception: Sequence only defined for natural numbers Exception: Sequence only defined for positive integers
>>> collatz_sequence(43) # doctest: +NORMALIZE_WHITESPACE >>> tuple(collatz_sequence(4))
[43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, (4, 2, 1)
22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] >>> tuple(collatz_sequence(11))
(11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1)
>>> tuple(collatz_sequence(31)) # doctest: +NORMALIZE_WHITESPACE
(31, 94, 47, 142, 71, 214, 107, 322, 161, 484, 242, 121, 364, 182, 91, 274, 137,
412, 206, 103, 310, 155, 466, 233, 700, 350, 175, 526, 263, 790, 395, 1186, 593,
1780, 890, 445, 1336, 668, 334, 167, 502, 251, 754, 377, 1132, 566, 283, 850, 425,
1276, 638, 319, 958, 479, 1438, 719, 2158, 1079, 3238, 1619, 4858, 2429, 7288, 3644,
1822, 911, 2734, 1367, 4102, 2051, 6154, 3077, 9232, 4616, 2308, 1154, 577, 1732,
866, 433, 1300, 650, 325, 976, 488, 244, 122, 61, 184, 92, 46, 23, 70, 35, 106, 53,
160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1)
>>> tuple(collatz_sequence(43)) # doctest: +NORMALIZE_WHITESPACE
(43, 130, 65, 196, 98, 49, 148, 74, 37, 112, 56, 28, 14, 7, 22, 11, 34, 17, 52, 26,
13, 40, 20, 10, 5, 16, 8, 4, 2, 1)
""" """
if not isinstance(n, int) or n < 1: if not isinstance(n, int) or n < 1:
raise Exception("Sequence only defined for natural numbers") raise Exception("Sequence only defined for positive integers")
sequence = [n] yield n
while n != 1: while n != 1:
n = 3 * n + 1 if n & 1 else n // 2 if n % 2 == 0:
sequence.append(n) n //= 2
return sequence else:
n = 3 * n + 1
yield n
def main(): def main():
n = 43 n = int(input("Your number: "))
sequence = collatz_sequence(n) sequence = tuple(collatz_sequence(n))
print(sequence) print(sequence)
print(f"collatz sequence from {n} took {len(sequence)} steps.") print(f"Collatz sequence from {n} took {len(sequence)} steps.")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -55,7 +55,7 @@ def factorial_recursive(n: int) -> int:
raise ValueError("factorial() only accepts integral values") raise ValueError("factorial() only accepts integral values")
if n < 0: if n < 0:
raise ValueError("factorial() not defined for negative values") raise ValueError("factorial() not defined for negative values")
return 1 if n == 0 or n == 1 else n * factorial(n - 1) return 1 if n in {0, 1} else n * factorial(n - 1)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,63 +0,0 @@
"""
Kadane's algorithm to get maximum subarray sum
https://medium.com/@rsinghal757/kadanes-algorithm-dynamic-programming-how-and-why-does-it-work-3fd8849ed73d
https://en.wikipedia.org/wiki/Maximum_subarray_problem
"""
test_data: tuple = ([-2, -8, -9], [2, 8, 9], [-1, 0, 1], [0, 0], [])
def negative_exist(arr: list) -> int:
"""
>>> negative_exist([-2,-8,-9])
-2
>>> [negative_exist(arr) for arr in test_data]
[-2, 0, 0, 0, 0]
"""
arr = arr or [0]
max_number = arr[0]
for i in arr:
if i >= 0:
return 0
elif max_number <= i:
max_number = i
return max_number
def kadanes(arr: list) -> int:
"""
If negative_exist() returns 0 than this function will execute
else it will return the value return by negative_exist function
For example: arr = [2, 3, -9, 8, -2]
Initially we set value of max_sum to 0 and max_till_element to 0 than when
max_sum is less than max_till particular element it will assign that value to
max_sum and when value of max_till_sum is less than 0 it will assign 0 to i
and after that whole process, return the max_sum
So the output for above arr is 8
>>> kadanes([2, 3, -9, 8, -2])
8
>>> [kadanes(arr) for arr in test_data]
[-2, 19, 1, 0, 0]
"""
max_sum = negative_exist(arr)
if max_sum < 0:
return max_sum
max_sum = 0
max_till_element = 0
for i in arr:
max_till_element += i
max_sum = max(max_sum, max_till_element)
max_till_element = max(max_till_element, 0)
return max_sum
if __name__ == "__main__":
try:
print("Enter integer values sepatated by spaces")
arr = [int(x) for x in input().split()]
print(f"Maximum subarray sum of {arr} is {kadanes(arr)}")
except ValueError:
print("Please enter integer values.")

View File

@ -1,21 +0,0 @@
from sys import maxsize
def max_sub_array_sum(a: list, size: int = 0):
"""
>>> max_sub_array_sum([-13, -3, -25, -20, -3, -16, -23, -12, -5, -22, -15, -4, -7])
-3
"""
size = size or len(a)
max_so_far = -maxsize - 1
max_ending_here = 0
for i in range(0, size):
max_ending_here = max_ending_here + a[i]
max_so_far = max(max_so_far, max_ending_here)
max_ending_here = max(max_ending_here, 0)
return max_so_far
if __name__ == "__main__":
a = [-13, -3, -25, -20, 1, -16, -23, -12, -5, -22, -15, -4, -7]
print(("Maximum contiguous sum is", max_sub_array_sum(a, len(a))))

View File

@ -67,7 +67,7 @@ def benchmark():
class TestLeastCommonMultiple(unittest.TestCase): class TestLeastCommonMultiple(unittest.TestCase):
test_inputs = [ test_inputs = (
(10, 20), (10, 20),
(13, 15), (13, 15),
(4, 31), (4, 31),
@ -77,8 +77,8 @@ class TestLeastCommonMultiple(unittest.TestCase):
(12, 25), (12, 25),
(10, 25), (10, 25),
(6, 9), (6, 9),
] )
expected_results = [20, 195, 124, 210, 1462, 60, 300, 50, 18] expected_results = (20, 195, 124, 210, 1462, 60, 300, 50, 18)
def test_lcm_function(self): def test_lcm_function(self):
for i, (first_num, second_num) in enumerate(self.test_inputs): for i, (first_num, second_num) in enumerate(self.test_inputs):

View File

@ -154,7 +154,7 @@ def prime_factorization(number):
quotient = number quotient = number
if number == 0 or number == 1: if number in {0, 1}:
ans.append(number) ans.append(number)
# if 'number' not prime then builds the prime factorization of 'number' # if 'number' not prime then builds the prime factorization of 'number'

View File

@ -17,7 +17,7 @@ This script is inspired by a corresponding research paper.
import numpy as np import numpy as np
def sigmoid(vector: np.array) -> np.array: def sigmoid(vector: np.ndarray) -> np.ndarray:
""" """
Mathematical function sigmoid takes a vector x of K real numbers as input and Mathematical function sigmoid takes a vector x of K real numbers as input and
returns 1/ (1 + e^-x). returns 1/ (1 + e^-x).
@ -29,17 +29,15 @@ def sigmoid(vector: np.array) -> np.array:
return 1 / (1 + np.exp(-vector)) return 1 / (1 + np.exp(-vector))
def sigmoid_linear_unit(vector: np.array) -> np.array: def sigmoid_linear_unit(vector: np.ndarray) -> np.ndarray:
""" """
Implements the Sigmoid Linear Unit (SiLU) or swish function Implements the Sigmoid Linear Unit (SiLU) or swish function
Parameters: Parameters:
vector (np.array): A numpy array consisting of real vector (np.ndarray): A numpy array consisting of real values
values.
Returns: Returns:
swish_vec (np.array): The input numpy array, after applying swish_vec (np.ndarray): The input numpy array, after applying swish
swish.
Examples: Examples:
>>> sigmoid_linear_unit(np.array([-1.0, 1.0, 2.0])) >>> sigmoid_linear_unit(np.array([-1.0, 1.0, 2.0]))

View File

@ -0,0 +1,142 @@
"""
https://en.wikipedia.org/wiki/Augmented_matrix
This algorithm solves simultaneous linear equations of the form
λa + λb + λc + λd + ... = γ as [λ, λ, λ, λ, ..., γ]
Where λ & γ are individual coefficients, the no. of equations = no. of coefficients - 1
Note in order to work there must exist 1 equation where all instances of λ and γ != 0
"""
def simplify(current_set: list[list]) -> list[list]:
"""
>>> simplify([[1, 2, 3], [4, 5, 6]])
[[1.0, 2.0, 3.0], [0.0, 0.75, 1.5]]
>>> simplify([[5, 2, 5], [5, 1, 10]])
[[1.0, 0.4, 1.0], [0.0, 0.2, -1.0]]
"""
# Divide each row by magnitude of first term --> creates 'unit' matrix
duplicate_set = current_set.copy()
for row_index, row in enumerate(duplicate_set):
magnitude = row[0]
for column_index, column in enumerate(row):
if magnitude == 0:
current_set[row_index][column_index] = column
continue
current_set[row_index][column_index] = column / magnitude
# Subtract to cancel term
first_row = current_set[0]
final_set = [first_row]
current_set = current_set[1::]
for row in current_set:
temp_row = []
# If first term is 0, it is already in form we want, so we preserve it
if row[0] == 0:
final_set.append(row)
continue
for column_index in range(len(row)):
temp_row.append(first_row[column_index] - row[column_index])
final_set.append(temp_row)
# Create next recursion iteration set
if len(final_set[0]) != 3:
current_first_row = final_set[0]
current_first_column = []
next_iteration = []
for row in final_set[1::]:
current_first_column.append(row[0])
next_iteration.append(row[1::])
resultant = simplify(next_iteration)
for i in range(len(resultant)):
resultant[i].insert(0, current_first_column[i])
resultant.insert(0, current_first_row)
final_set = resultant
return final_set
def solve_simultaneous(equations: list[list]) -> list:
"""
>>> solve_simultaneous([[1, 2, 3],[4, 5, 6]])
[-1.0, 2.0]
>>> solve_simultaneous([[0, -3, 1, 7],[3, 2, -1, 11],[5, 1, -2, 12]])
[6.4, 1.2, 10.6]
>>> solve_simultaneous([])
Traceback (most recent call last):
...
IndexError: solve_simultaneous() requires n lists of length n+1
>>> solve_simultaneous([[1, 2, 3],[1, 2]])
Traceback (most recent call last):
...
IndexError: solve_simultaneous() requires n lists of length n+1
>>> solve_simultaneous([[1, 2, 3],["a", 7, 8]])
Traceback (most recent call last):
...
ValueError: solve_simultaneous() requires lists of integers
>>> solve_simultaneous([[0, 2, 3],[4, 0, 6]])
Traceback (most recent call last):
...
ValueError: solve_simultaneous() requires at least 1 full equation
"""
if len(equations) == 0:
raise IndexError("solve_simultaneous() requires n lists of length n+1")
_length = len(equations) + 1
if any(len(item) != _length for item in equations):
raise IndexError("solve_simultaneous() requires n lists of length n+1")
for row in equations:
if any(not isinstance(column, (int, float)) for column in row):
raise ValueError("solve_simultaneous() requires lists of integers")
if len(equations) == 1:
return [equations[0][-1] / equations[0][0]]
data_set = equations.copy()
if any(0 in row for row in data_set):
temp_data = data_set.copy()
full_row = []
for row_index, row in enumerate(temp_data):
if 0 not in row:
full_row = data_set.pop(row_index)
break
if not full_row:
raise ValueError("solve_simultaneous() requires at least 1 full equation")
data_set.insert(0, full_row)
useable_form = data_set.copy()
simplified = simplify(useable_form)
simplified = simplified[::-1]
solutions: list = []
for row in simplified:
current_solution = row[-1]
if not solutions:
if row[-2] == 0:
solutions.append(0)
continue
solutions.append(current_solution / row[-2])
continue
temp_row = row.copy()[: len(row) - 1 :]
while temp_row[0] == 0:
temp_row.pop(0)
if len(temp_row) == 0:
solutions.append(0)
continue
temp_row = temp_row[1::]
temp_row = temp_row[::-1]
for column_index, column in enumerate(temp_row):
current_solution -= column * solutions[column_index]
solutions.append(current_solution)
final = []
for item in solutions:
final.append(float(round(item, 5)))
return final[::-1]
if __name__ == "__main__":
import doctest
doctest.testmod()
eq = [
[2, 1, 1, 1, 1, 4],
[1, 2, 1, 1, 1, 5],
[1, 1, 2, 1, 1, 6],
[1, 1, 1, 2, 1, 7],
[1, 1, 1, 1, 2, 8],
]
print(solve_simultaneous(eq))
print(solve_simultaneous([[4, 2]]))

View File

@ -0,0 +1,151 @@
"""
Given an matrix of numbers in which all rows and all columns are sorted in decreasing
order, return the number of negative numbers in grid.
Reference: https://leetcode.com/problems/count-negative-numbers-in-a-sorted-matrix
"""
def generate_large_matrix() -> list[list[int]]:
"""
>>> generate_large_matrix() # doctest: +ELLIPSIS
[[1000, ..., -999], [999, ..., -1001], ..., [2, ..., -1998]]
"""
return [list(range(1000 - i, -1000 - i, -1)) for i in range(1000)]
grid = generate_large_matrix()
test_grids = (
[[4, 3, 2, -1], [3, 2, 1, -1], [1, 1, -1, -2], [-1, -1, -2, -3]],
[[3, 2], [1, 0]],
[[7, 7, 6]],
[[7, 7, 6], [-1, -2, -3]],
grid,
)
def validate_grid(grid: list[list[int]]) -> None:
"""
Validate that the rows and columns of the grid is sorted in decreasing order.
>>> for grid in test_grids:
... validate_grid(grid)
"""
assert all(row == sorted(row, reverse=True) for row in grid)
assert all(list(col) == sorted(col, reverse=True) for col in zip(*grid))
def find_negative_index(array: list[int]) -> int:
"""
Find the smallest negative index
>>> find_negative_index([0,0,0,0])
4
>>> find_negative_index([4,3,2,-1])
3
>>> find_negative_index([1,0,-1,-10])
2
>>> find_negative_index([0,0,0,-1])
3
>>> find_negative_index([11,8,7,-3,-5,-9])
3
>>> find_negative_index([-1,-1,-2,-3])
0
>>> find_negative_index([5,1,0])
3
>>> find_negative_index([-5,-5,-5])
0
>>> find_negative_index([0])
1
>>> find_negative_index([])
0
"""
left = 0
right = len(array) - 1
# Edge cases such as no values or all numbers are negative.
if not array or array[0] < 0:
return 0
while right + 1 > left:
mid = (left + right) // 2
num = array[mid]
# Num must be negative and the index must be greater than or equal to 0.
if num < 0 and array[mid - 1] >= 0:
return mid
if num >= 0:
left = mid + 1
else:
right = mid - 1
# No negative numbers so return the last index of the array + 1 which is the length.
return len(array)
def count_negatives_binary_search(grid: list[list[int]]) -> int:
"""
An O(m logn) solution that uses binary search in order to find the boundary between
positive and negative numbers
>>> [count_negatives_binary_search(grid) for grid in test_grids]
[8, 0, 0, 3, 1498500]
"""
total = 0
bound = len(grid[0])
for i in range(len(grid)):
bound = find_negative_index(grid[i][:bound])
total += bound
return (len(grid) * len(grid[0])) - total
def count_negatives_brute_force(grid: list[list[int]]) -> int:
"""
This solution is O(n^2) because it iterates through every column and row.
>>> [count_negatives_brute_force(grid) for grid in test_grids]
[8, 0, 0, 3, 1498500]
"""
return len([number for row in grid for number in row if number < 0])
def count_negatives_brute_force_with_break(grid: list[list[int]]) -> int:
"""
Similar to the brute force solution above but uses break in order to reduce the
number of iterations.
>>> [count_negatives_brute_force_with_break(grid) for grid in test_grids]
[8, 0, 0, 3, 1498500]
"""
total = 0
for row in grid:
for i, number in enumerate(row):
if number < 0:
total += len(row) - i
break
return total
def benchmark() -> None:
"""Benchmark our functions next to each other"""
from timeit import timeit
print("Running benchmarks")
setup = (
"from __main__ import count_negatives_binary_search, "
"count_negatives_brute_force, count_negatives_brute_force_with_break, grid"
)
for func in (
"count_negatives_binary_search", # took 0.7727 seconds
"count_negatives_brute_force_with_break", # took 4.6505 seconds
"count_negatives_brute_force", # took 12.8160 seconds
):
time = timeit(f"{func}(grid=grid)", setup=setup, number=500)
print(f"{func}() took {time:0.4f} seconds")
if __name__ == "__main__":
import doctest
doctest.testmod()
benchmark()

View File

@ -263,9 +263,7 @@ def _maybe_download(filename, work_directory, source_url):
return filepath return filepath
@deprecated( @deprecated(None, "Please use alternatives such as: tensorflow_datasets.load('mnist')")
None, "Please use alternatives such as:" " tensorflow_datasets.load('mnist')"
)
def read_data_sets( def read_data_sets(
train_dir, train_dir,
fake_data=False, fake_data=False,

View File

@ -253,7 +253,7 @@ def find_unit_clauses(
unit_symbols = [] unit_symbols = []
for clause in clauses: for clause in clauses:
if len(clause) == 1: if len(clause) == 1:
unit_symbols.append(list(clause.literals.keys())[0]) unit_symbols.append(next(iter(clause.literals.keys())))
else: else:
f_count, n_count = 0, 0 f_count, n_count = 0, 0
for literal, value in clause.literals.items(): for literal, value in clause.literals.items():

View File

@ -1,32 +0,0 @@
from collections.abc import Sequence
def max_subarray_sum(nums: Sequence[int]) -> int:
"""Return the maximum possible sum amongst all non - empty subarrays.
Raises:
ValueError: when nums is empty.
>>> max_subarray_sum([1,2,3,4,-2])
10
>>> max_subarray_sum([-2,1,-3,4,-1,2,1,-5,4])
6
"""
if not nums:
raise ValueError("Input sequence should not be empty")
curr_max = ans = nums[0]
nums_len = len(nums)
for i in range(1, nums_len):
num = nums[i]
curr_max = max(curr_max + num, num)
ans = max(curr_max, ans)
return ans
if __name__ == "__main__":
n = int(input("Enter number of elements : ").strip())
array = list(map(int, input("\nEnter the numbers : ").strip().split()))[:n]
print(max_subarray_sum(array))

View File

@ -0,0 +1,180 @@
"""
A number container system that uses binary search to delete and insert values into
arrays with O(log n) write times and O(1) read times.
This container system holds integers at indexes.
Further explained in this leetcode problem
> https://leetcode.com/problems/minimum-cost-tree-from-leaf-values
"""
class NumberContainer:
def __init__(self) -> None:
# numbermap keys are the number and its values are lists of indexes sorted
# in ascending order
self.numbermap: dict[int, list[int]] = {}
# indexmap keys are an index and it's values are the number at that index
self.indexmap: dict[int, int] = {}
def binary_search_delete(self, array: list | str | range, item: int) -> list[int]:
"""
Removes the item from the sorted array and returns
the new array.
>>> NumberContainer().binary_search_delete([1,2,3], 2)
[1, 3]
>>> NumberContainer().binary_search_delete([0, 0, 0], 0)
[0, 0]
>>> NumberContainer().binary_search_delete([-1, -1, -1], -1)
[-1, -1]
>>> NumberContainer().binary_search_delete([-1, 0], 0)
[-1]
>>> NumberContainer().binary_search_delete([-1, 0], -1)
[0]
>>> NumberContainer().binary_search_delete(range(7), 3)
[0, 1, 2, 4, 5, 6]
>>> NumberContainer().binary_search_delete([1.1, 2.2, 3.3], 2.2)
[1.1, 3.3]
>>> NumberContainer().binary_search_delete("abcde", "c")
['a', 'b', 'd', 'e']
>>> NumberContainer().binary_search_delete([0, -1, 2, 4], 0)
Traceback (most recent call last):
...
ValueError: Either the item is not in the array or the array was unsorted
>>> NumberContainer().binary_search_delete([2, 0, 4, -1, 11], -1)
Traceback (most recent call last):
...
ValueError: Either the item is not in the array or the array was unsorted
>>> NumberContainer().binary_search_delete(125, 1)
Traceback (most recent call last):
...
TypeError: binary_search_delete() only accepts either a list, range or str
"""
if isinstance(array, (range, str)):
array = list(array)
elif not isinstance(array, list):
raise TypeError(
"binary_search_delete() only accepts either a list, range or str"
)
low = 0
high = len(array) - 1
while low <= high:
mid = (low + high) // 2
if array[mid] == item:
array.pop(mid)
return array
elif array[mid] < item:
low = mid + 1
else:
high = mid - 1
raise ValueError(
"Either the item is not in the array or the array was unsorted"
)
def binary_search_insert(self, array: list | str | range, index: int) -> list[int]:
"""
Inserts the index into the sorted array
at the correct position.
>>> NumberContainer().binary_search_insert([1,2,3], 2)
[1, 2, 2, 3]
>>> NumberContainer().binary_search_insert([0,1,3], 2)
[0, 1, 2, 3]
>>> NumberContainer().binary_search_insert([-5, -3, 0, 0, 11, 103], 51)
[-5, -3, 0, 0, 11, 51, 103]
>>> NumberContainer().binary_search_insert([-5, -3, 0, 0, 11, 100, 103], 101)
[-5, -3, 0, 0, 11, 100, 101, 103]
>>> NumberContainer().binary_search_insert(range(10), 4)
[0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9]
>>> NumberContainer().binary_search_insert("abd", "c")
['a', 'b', 'c', 'd']
>>> NumberContainer().binary_search_insert(131, 23)
Traceback (most recent call last):
...
TypeError: binary_search_insert() only accepts either a list, range or str
"""
if isinstance(array, (range, str)):
array = list(array)
elif not isinstance(array, list):
raise TypeError(
"binary_search_insert() only accepts either a list, range or str"
)
low = 0
high = len(array) - 1
while low <= high:
mid = (low + high) // 2
if array[mid] == index:
# If the item already exists in the array,
# insert it after the existing item
array.insert(mid + 1, index)
return array
elif array[mid] < index:
low = mid + 1
else:
high = mid - 1
# If the item doesn't exist in the array, insert it at the appropriate position
array.insert(low, index)
return array
def change(self, index: int, number: int) -> None:
"""
Changes (sets) the index as number
>>> cont = NumberContainer()
>>> cont.change(0, 10)
>>> cont.change(0, 20)
>>> cont.change(-13, 20)
>>> cont.change(-100030, 20032903290)
"""
# Remove previous index
if index in self.indexmap:
n = self.indexmap[index]
if len(self.numbermap[n]) == 1:
del self.numbermap[n]
else:
self.numbermap[n] = self.binary_search_delete(self.numbermap[n], index)
# Set new index
self.indexmap[index] = number
# Number not seen before or empty so insert number value
if number not in self.numbermap:
self.numbermap[number] = [index]
# Here we need to perform a binary search insertion in order to insert
# The item in the correct place
else:
self.numbermap[number] = self.binary_search_insert(
self.numbermap[number], index
)
def find(self, number: int) -> int:
"""
Returns the smallest index where the number is.
>>> cont = NumberContainer()
>>> cont.find(10)
-1
>>> cont.change(0, 10)
>>> cont.find(10)
0
>>> cont.change(0, 20)
>>> cont.find(10)
-1
>>> cont.find(20)
0
"""
# Simply return the 0th index (smallest) of the indexes found (or -1)
return self.numbermap.get(number, [-1])[0]
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,52 @@
"""
Title : Calculate altitude using Pressure
Description :
The below algorithm approximates the altitude using Barometric formula
"""
def get_altitude_at_pressure(pressure: float) -> float:
"""
This method calculates the altitude from Pressure wrt to
Sea level pressure as reference .Pressure is in Pascals
https://en.wikipedia.org/wiki/Pressure_altitude
https://community.bosch-sensortec.com/t5/Question-and-answers/How-to-calculate-the-altitude-from-the-pressure-sensor-data/qaq-p/5702
H = 44330 * [1 - (P/p0)^(1/5.255) ]
Where :
H = altitude (m)
P = measured pressure
p0 = reference pressure at sea level 101325 Pa
Examples:
>>> get_altitude_at_pressure(pressure=100_000)
105.47836610778828
>>> get_altitude_at_pressure(pressure=101_325)
0.0
>>> get_altitude_at_pressure(pressure=80_000)
1855.873388064995
>>> get_altitude_at_pressure(pressure=201_325)
Traceback (most recent call last):
...
ValueError: Value Higher than Pressure at Sea Level !
>>> get_altitude_at_pressure(pressure=-80_000)
Traceback (most recent call last):
...
ValueError: Atmospheric Pressure can not be negative !
"""
if pressure > 101325:
raise ValueError("Value Higher than Pressure at Sea Level !")
if pressure < 0:
raise ValueError("Atmospheric Pressure can not be negative !")
return 44_330 * (1 - (pressure / 101_325) ** (1 / 5.5255))
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -0,0 +1,178 @@
from math import pow, sqrt
from scipy.constants import G, c, pi
"""
These two functions will return the radii of impact for a target object
of mass M and radius R as well as it's effective cross sectional area σ(sigma).
That is to say any projectile with velocity v passing within σ, will impact the
target object with mass M. The derivation of which is given at the bottom
of this file.
The derivation shows that a projectile does not need to aim directly at the target
body in order to hit it, as R_capture>R_target. Astronomers refer to the effective
cross section for capture as σ=π*R_capture**2.
This algorithm does not account for an N-body problem.
"""
def capture_radii(
target_body_radius: float, target_body_mass: float, projectile_velocity: float
) -> float:
"""
Input Params:
-------------
target_body_radius: Radius of the central body SI units: meters | m
target_body_mass: Mass of the central body SI units: kilograms | kg
projectile_velocity: Velocity of object moving toward central body
SI units: meters/second | m/s
Returns:
--------
>>> capture_radii(6.957e8, 1.99e30, 25000.0)
17209590691.0
>>> capture_radii(-6.957e8, 1.99e30, 25000.0)
Traceback (most recent call last):
...
ValueError: Radius cannot be less than 0
>>> capture_radii(6.957e8, -1.99e30, 25000.0)
Traceback (most recent call last):
...
ValueError: Mass cannot be less than 0
>>> capture_radii(6.957e8, 1.99e30, c+1)
Traceback (most recent call last):
...
ValueError: Cannot go beyond speed of light
Returned SI units:
------------------
meters | m
"""
if target_body_mass < 0:
raise ValueError("Mass cannot be less than 0")
if target_body_radius < 0:
raise ValueError("Radius cannot be less than 0")
if projectile_velocity > c:
raise ValueError("Cannot go beyond speed of light")
escape_velocity_squared = (2 * G * target_body_mass) / target_body_radius
capture_radius = target_body_radius * sqrt(
1 + escape_velocity_squared / pow(projectile_velocity, 2)
)
return round(capture_radius, 0)
def capture_area(capture_radius: float) -> float:
"""
Input Param:
------------
capture_radius: The radius of orbital capture and impact for a central body of
mass M and a projectile moving towards it with velocity v
SI units: meters | m
Returns:
--------
>>> capture_area(17209590691)
9.304455331329126e+20
>>> capture_area(-1)
Traceback (most recent call last):
...
ValueError: Cannot have a capture radius less than 0
Returned SI units:
------------------
meters*meters | m**2
"""
if capture_radius < 0:
raise ValueError("Cannot have a capture radius less than 0")
sigma = pi * pow(capture_radius, 2)
return round(sigma, 0)
if __name__ == "__main__":
from doctest import testmod
testmod()
"""
Derivation:
Let: Mt=target mass, Rt=target radius, v=projectile_velocity,
r_0=radius of projectile at instant 0 to CM of target
v_p=v at closest approach,
r_p=radius from projectile to target CM at closest approach,
R_capture= radius of impact for projectile with velocity v
(1)At time=0 the projectile's energy falling from infinity| E=K+U=0.5*m*(v**2)+0
E_initial=0.5*m*(v**2)
(2)at time=0 the angular momentum of the projectile relative to CM target|
L_initial=m*r_0*v*sin(Θ)->m*r_0*v*(R_capture/r_0)->m*v*R_capture
L_i=m*v*R_capture
(3)The energy of the projectile at closest approach will be its kinetic energy
at closest approach plus gravitational potential energy(-(GMm)/R)|
E_p=K_p+U_p->E_p=0.5*m*(v_p**2)-(G*Mt*m)/r_p
E_p=0.0.5*m*(v_p**2)-(G*Mt*m)/r_p
(4)The angular momentum of the projectile relative to the target at closest
approach will be L_p=m*r_p*v_p*sin(Θ), however relative to the target Θ=90°
sin(90°)=1|
L_p=m*r_p*v_p
(5)Using conservation of angular momentum and energy, we can write a quadratic
equation that solves for r_p|
(a)
Ei=Ep-> 0.5*m*(v**2)=0.5*m*(v_p**2)-(G*Mt*m)/r_p-> v**2=v_p**2-(2*G*Mt)/r_p
(b)
Li=Lp-> m*v*R_capture=m*r_p*v_p-> v*R_capture=r_p*v_p-> v_p=(v*R_capture)/r_p
(c) b plugs int a|
v**2=((v*R_capture)/r_p)**2-(2*G*Mt)/r_p->
v**2-(v**2)*(R_c**2)/(r_p**2)+(2*G*Mt)/r_p=0->
(v**2)*(r_p**2)+2*G*Mt*r_p-(v**2)*(R_c**2)=0
(d) Using the quadratic formula, we'll solve for r_p then rearrange to solve to
R_capture
r_p=(-2*G*Mt ± sqrt(4*G^2*Mt^2+ 4(v^4*R_c^2)))/(2*v^2)->
r_p=(-G*Mt ± sqrt(G^2*Mt+v^4*R_c^2))/v^2->
r_p<0 is something we can ignore, as it has no physical meaning for our purposes.->
r_p=(-G*Mt)/v^2 + sqrt(G^2*Mt^2/v^4 + R_c^2)
(e)We are trying to solve for R_c. We are looking for impact, so we want r_p=Rt
Rt + G*Mt/v^2 = sqrt(G^2*Mt^2/v^4 + R_c^2)->
(Rt + G*Mt/v^2)^2 = G^2*Mt^2/v^4 + R_c^2->
Rt^2 + 2*G*Mt*Rt/v^2 + G^2*Mt^2/v^4 = G^2*Mt^2/v^4 + R_c^2->
Rt**2 + 2*G*Mt*Rt/v**2 = R_c**2->
Rt**2 * (1 + 2*G*Mt/Rt *1/v**2) = R_c**2->
escape velocity = sqrt(2GM/R)= v_escape**2=2GM/R->
Rt**2 * (1 + v_esc**2/v**2) = R_c**2->
(6)
R_capture = Rt * sqrt(1 + v_esc**2/v**2)
Source: Problem Set 3 #8 c.Fall_2017|Honors Astronomy|Professor Rachel Bezanson
Source #2: http://www.nssc.ac.cn/wxzygx/weixin/201607/P020160718380095698873.pdf
8.8 Planetary Rendezvous: Pg.368
"""

View File

@ -60,7 +60,7 @@ def newtons_second_law_of_motion(mass: float, acceleration: float) -> float:
>>> newtons_second_law_of_motion(2.0, 1) >>> newtons_second_law_of_motion(2.0, 1)
2.0 2.0
""" """
force = float() force = 0.0
try: try:
force = mass * acceleration force = mass * acceleration
except Exception: except Exception:

52
physics/speed_of_sound.py Normal file
View File

@ -0,0 +1,52 @@
"""
Title : Calculating the speed of sound
Description :
The speed of sound (c) is the speed that a sound wave travels
per unit time (m/s). During propagation, the sound wave propagates
through an elastic medium. Its SI unit is meter per second (m/s).
Only longitudinal waves can propagate in liquids and gas other then
solid where they also travel in transverse wave. The following Algo-
rithem calculates the speed of sound in fluid depanding on the bulk
module and the density of the fluid.
Equation for calculating speed od sound in fluid:
c_fluid = (K_s*p)**0.5
c_fluid: speed of sound in fluid
K_s: isentropic bulk modulus
p: density of fluid
Source : https://en.wikipedia.org/wiki/Speed_of_sound
"""
def speed_of_sound_in_a_fluid(density: float, bulk_modulus: float) -> float:
"""
This method calculates the speed of sound in fluid -
This is calculated from the other two provided values
Examples:
Example 1 --> Water 20°C: bulk_moduls= 2.15MPa, density=998kg/
Example 2 --> Murcery 20°: bulk_moduls= 28.5MPa, density=13600kg/
>>> speed_of_sound_in_a_fluid(bulk_modulus=2.15*10**9, density=998)
1467.7563207952705
>>> speed_of_sound_in_a_fluid(bulk_modulus=28.5*10**9, density=13600)
1447.614670861731
"""
if density <= 0:
raise ValueError("Impossible fluid density")
if bulk_modulus <= 0:
raise ValueError("Impossible bulk modulus")
return (bulk_modulus / density) ** 0.5
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@ -28,12 +28,16 @@ def solution() -> int:
31875000 31875000
""" """
return [ return next(
a * b * (1000 - a - b) iter(
for a in range(1, 999) [
for b in range(a, 999) a * b * (1000 - a - b)
if (a * a + b * b == (1000 - a - b) ** 2) for a in range(1, 999)
][0] for b in range(a, 999)
if (a * a + b * b == (1000 - a - b) ** 2)
]
)
)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -47,18 +47,18 @@ import os
class PokerHand: class PokerHand:
"""Create an object representing a Poker Hand based on an input of a """Create an object representing a Poker Hand based on an input of a
string which represents the best 5 card combination from the player's hand string which represents the best 5-card combination from the player's hand
and board cards. and board cards.
Attributes: (read-only) Attributes: (read-only)
hand: string representing the hand consisting of five cards hand: a string representing the hand consisting of five cards
Methods: Methods:
compare_with(opponent): takes in player's hand (self) and compare_with(opponent): takes in player's hand (self) and
opponent's hand (opponent) and compares both hands according to opponent's hand (opponent) and compares both hands according to
the rules of Texas Hold'em. the rules of Texas Hold'em.
Returns one of 3 strings (Win, Loss, Tie) based on whether Returns one of 3 strings (Win, Loss, Tie) based on whether
player's hand is better than opponent's hand. player's hand is better than the opponent's hand.
hand_name(): Returns a string made up of two parts: hand name hand_name(): Returns a string made up of two parts: hand name
and high card. and high card.
@ -66,11 +66,11 @@ class PokerHand:
Supported operators: Supported operators:
Rich comparison operators: <, >, <=, >=, ==, != Rich comparison operators: <, >, <=, >=, ==, !=
Supported builtin methods and functions: Supported built-in methods and functions:
list.sort(), sorted() list.sort(), sorted()
""" """
_HAND_NAME = [ _HAND_NAME = (
"High card", "High card",
"One pair", "One pair",
"Two pairs", "Two pairs",
@ -81,10 +81,10 @@ class PokerHand:
"Four of a kind", "Four of a kind",
"Straight flush", "Straight flush",
"Royal flush", "Royal flush",
] )
_CARD_NAME = [ _CARD_NAME = (
"", # placeholder as lists are zero indexed "", # placeholder as tuples are zero-indexed
"One", "One",
"Two", "Two",
"Three", "Three",
@ -99,7 +99,7 @@ class PokerHand:
"Queen", "Queen",
"King", "King",
"Ace", "Ace",
] )
def __init__(self, hand: str) -> None: def __init__(self, hand: str) -> None:
""" """

View File

@ -1,21 +1,3 @@
[tool.pytest.ini_options]
markers = [
"mat_ops: mark a test as utilizing matrix operations.",
]
addopts = [
"--durations=10",
"--doctest-modules",
"--showlocals",
]
[tool.coverage.report]
omit = [".env/*"]
sort = "Cover"
[tool.codespell]
ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar"
skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt"
[tool.ruff] [tool.ruff]
ignore = [ # `ruff rule S101` for a description of that rule ignore = [ # `ruff rule S101` for a description of that rule
"ARG001", # Unused function argument `amount` -- FIX ME? "ARG001", # Unused function argument `amount` -- FIX ME?
@ -67,6 +49,7 @@ select = [ # https://beta.ruff.rs/docs/rules
"ICN", # flake8-import-conventions "ICN", # flake8-import-conventions
"INP", # flake8-no-pep420 "INP", # flake8-no-pep420
"INT", # flake8-gettext "INT", # flake8-gettext
"ISC", # flake8-implicit-str-concat
"N", # pep8-naming "N", # pep8-naming
"NPY", # NumPy-specific rules "NPY", # NumPy-specific rules
"PGH", # pygrep-hooks "PGH", # pygrep-hooks
@ -90,7 +73,6 @@ select = [ # https://beta.ruff.rs/docs/rules
# "DJ", # flake8-django # "DJ", # flake8-django
# "ERA", # eradicate -- DO NOT FIX # "ERA", # eradicate -- DO NOT FIX
# "FBT", # flake8-boolean-trap # FIX ME # "FBT", # flake8-boolean-trap # FIX ME
# "ISC", # flake8-implicit-str-concat # FIX ME
# "PD", # pandas-vet # "PD", # pandas-vet
# "PT", # flake8-pytest-style # "PT", # flake8-pytest-style
# "PTH", # flake8-use-pathlib # FIX ME # "PTH", # flake8-use-pathlib # FIX ME
@ -121,6 +103,7 @@ max-complexity = 17 # default: 10
"machine_learning/linear_discriminant_analysis.py" = ["ARG005"] "machine_learning/linear_discriminant_analysis.py" = ["ARG005"]
"machine_learning/sequential_minimum_optimization.py" = ["SIM115"] "machine_learning/sequential_minimum_optimization.py" = ["SIM115"]
"matrix/sherman_morrison.py" = ["SIM103", "SIM114"] "matrix/sherman_morrison.py" = ["SIM103", "SIM114"]
"other/l*u_cache.py" = ["RUF012"]
"physics/newtons_second_law_of_motion.py" = ["BLE001"] "physics/newtons_second_law_of_motion.py" = ["BLE001"]
"project_euler/problem_099/sol1.py" = ["SIM115"] "project_euler/problem_099/sol1.py" = ["SIM115"]
"sorts/external_sort.py" = ["SIM115"] "sorts/external_sort.py" = ["SIM115"]
@ -131,3 +114,21 @@ max-args = 10 # default: 5
max-branches = 20 # default: 12 max-branches = 20 # default: 12
max-returns = 8 # default: 6 max-returns = 8 # default: 6
max-statements = 88 # default: 50 max-statements = 88 # default: 50
[tool.pytest.ini_options]
markers = [
"mat_ops: mark a test as utilizing matrix operations.",
]
addopts = [
"--durations=10",
"--doctest-modules",
"--showlocals",
]
[tool.coverage.report]
omit = [".env/*"]
sort = "Cover"
[tool.codespell]
ignore-words-list = "3rt,ans,crate,damon,fo,followings,hist,iff,kwanza,mater,secant,som,sur,tim,zar"
skip = "./.*,*.json,ciphers/prehistoric_men.txt,project_euler/problem_022/p022_names.txt,pyproject.toml,strings/dictionary.txt,strings/words.txt"

View File

@ -64,10 +64,10 @@ def bb84(key_len: int = 8, seed: int | None = None) -> str:
key: The key generated using BB84 protocol. key: The key generated using BB84 protocol.
>>> bb84(16, seed=0) >>> bb84(16, seed=0)
'1101101100010000' '0111110111010010'
>>> bb84(8, seed=0) >>> bb84(8, seed=0)
'01011011' '10110001'
""" """
# Set up the random number generator. # Set up the random number generator.
rng = np.random.default_rng(seed=seed) rng = np.random.default_rng(seed=seed)

View File

@ -107,7 +107,7 @@ def ripple_adder(
res = qiskit.execute(circuit, backend, shots=1).result() res = qiskit.execute(circuit, backend, shots=1).result()
# The result is in binary. Convert it back to int # The result is in binary. Convert it back to int
return int(list(res.get_counts())[0], 2) return int(next(iter(res.get_counts())), 2)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -9,6 +9,7 @@ pandas
pillow pillow
projectq projectq
qiskit qiskit
qiskit-aer
requests requests
rich rich
scikit-fuzzy scikit-fuzzy

View File

@ -15,7 +15,7 @@ def linear_search(sequence: list, target: int) -> int:
:param sequence: a collection with comparable items (as sorted items not required :param sequence: a collection with comparable items (as sorted items not required
in Linear Search) in Linear Search)
:param target: item value to search :param target: item value to search
:return: index of found item or None if item is not found :return: index of found item or -1 if item is not found
Examples: Examples:
>>> linear_search([0, 5, 7, 10, 15], 0) >>> linear_search([0, 5, 7, 10, 15], 0)

View File

@ -1,4 +1,7 @@
def bubble_sort(collection): from typing import Any
def bubble_sort(collection: list[Any]) -> list[Any]:
"""Pure implementation of bubble sort algorithm in Python """Pure implementation of bubble sort algorithm in Python
:param collection: some mutable ordered collection with heterogeneous :param collection: some mutable ordered collection with heterogeneous
@ -28,9 +31,9 @@ def bubble_sort(collection):
True True
""" """
length = len(collection) length = len(collection)
for i in range(length - 1): for i in reversed(range(length)):
swapped = False swapped = False
for j in range(length - 1 - i): for j in range(i):
if collection[j] > collection[j + 1]: if collection[j] > collection[j + 1]:
swapped = True swapped = True
collection[j], collection[j + 1] = collection[j + 1], collection[j] collection[j], collection[j + 1] = collection[j + 1], collection[j]

View File

@ -22,9 +22,7 @@ def is_sri_lankan_phone_number(phone: str) -> bool:
False False
""" """
pattern = re.compile( pattern = re.compile(r"^(?:0|94|\+94|0{2}94)7(0|1|2|4|5|6|7|8)(-| |)\d{7}$")
r"^(?:0|94|\+94|0{2}94)" r"7(0|1|2|4|5|6|7|8)" r"(-| |)" r"\d{7}$"
)
return bool(re.search(pattern, phone)) return bool(re.search(pattern, phone))

View File

@ -61,7 +61,7 @@ def assemble_transformation(ops: list[list[str]], i: int, j: int) -> list[str]:
if i == 0 and j == 0: if i == 0 and j == 0:
return [] return []
else: else:
if ops[i][j][0] == "C" or ops[i][j][0] == "R": if ops[i][j][0] in {"C", "R"}:
seq = assemble_transformation(ops, i - 1, j - 1) seq = assemble_transformation(ops, i - 1, j - 1)
seq.append(ops[i][j]) seq.append(ops[i][j])
return seq return seq

View File

@ -90,9 +90,7 @@ def convert(number: int) -> str:
else: else:
addition = "" addition = ""
if counter in placevalue: if counter in placevalue:
if current == 0 and ((temp_num % 100) // 10) == 0: if current != 0 and ((temp_num % 100) // 10) != 0:
addition = ""
else:
addition = placevalue[counter] addition = placevalue[counter]
if ((temp_num % 100) // 10) == 1: if ((temp_num % 100) // 10) == 1:
words = teens[current] + addition + words words = teens[current] + addition + words

View File

@ -8,13 +8,7 @@ import os
import requests import requests
URL_BASE = "https://www.amdoren.com/api/currency.php" URL_BASE = "https://www.amdoren.com/api/currency.php"
TESTING = os.getenv("CI", "")
API_KEY = os.getenv("AMDOREN_API_KEY", "")
if not API_KEY and not TESTING:
raise KeyError(
"API key must be provided in the 'AMDOREN_API_KEY' environment variable."
)
# Currency and their description # Currency and their description
list_of_currencies = """ list_of_currencies = """
@ -175,20 +169,31 @@ ZMW Zambian Kwacha
def convert_currency( def convert_currency(
from_: str = "USD", to: str = "INR", amount: float = 1.0, api_key: str = API_KEY from_: str = "USD", to: str = "INR", amount: float = 1.0, api_key: str = ""
) -> str: ) -> str:
"""https://www.amdoren.com/currency-api/""" """https://www.amdoren.com/currency-api/"""
# Instead of manually generating parameters
params = locals() params = locals()
# from is a reserved keyword
params["from"] = params.pop("from_") params["from"] = params.pop("from_")
res = requests.get(URL_BASE, params=params).json() res = requests.get(URL_BASE, params=params).json()
return str(res["amount"]) if res["error"] == 0 else res["error_message"] return str(res["amount"]) if res["error"] == 0 else res["error_message"]
if __name__ == "__main__": if __name__ == "__main__":
TESTING = os.getenv("CI", "")
API_KEY = os.getenv("AMDOREN_API_KEY", "")
if not API_KEY and not TESTING:
raise KeyError(
"API key must be provided in the 'AMDOREN_API_KEY' environment variable."
)
print( print(
convert_currency( convert_currency(
input("Enter from currency: ").strip(), input("Enter from currency: ").strip(),
input("Enter to currency: ").strip(), input("Enter to currency: ").strip(),
float(input("Enter the amount: ").strip()), float(input("Enter the amount: ").strip()),
API_KEY,
) )
) )

View File

@ -22,6 +22,5 @@ def world_covid19_stats(url: str = "https://www.worldometers.info/coronavirus")
if __name__ == "__main__": if __name__ == "__main__":
print("\033[1m" + "COVID-19 Status of the World" + "\033[0m\n") print("\033[1m COVID-19 Status of the World \033[0m\n")
for key, value in world_covid19_stats().items(): print("\n".join(f"{key}\n{value}" for key, value in world_covid19_stats().items()))
print(f"{key}\n{value}\n")