mirror of
https://github.com/TheAlgorithms/Python.git
synced 2025-01-30 06:03:42 +00:00
Merge branch 'TheAlgorithms:master' into naive-bayes-text-classifier
This commit is contained in:
commit
9433dce018
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -7,7 +7,7 @@
|
|||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
|
||||
/.* @cclauss @dhruvmanila
|
||||
/.* @cclauss
|
||||
|
||||
# /arithmetic_analysis/
|
||||
|
||||
|
|
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
|
@ -6,6 +6,7 @@ body:
|
|||
attributes:
|
||||
value: >
|
||||
Before requesting please search [existing issues](https://github.com/TheAlgorithms/Python/labels/enhancement).
|
||||
Do not create issues to implement new algorithms as these will be closed.
|
||||
Usage questions such as "How do I...?" belong on the
|
||||
[Discord](https://discord.gg/c7MnfGFGa6) and will be closed.
|
||||
|
||||
|
@ -13,7 +14,6 @@ body:
|
|||
attributes:
|
||||
label: "Feature description"
|
||||
description: >
|
||||
This could be new algorithms, data structures or improving any existing
|
||||
implementations.
|
||||
This could include new topics or improving any existing implementations.
|
||||
validations:
|
||||
required: true
|
||||
|
|
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
|
@ -4,6 +4,7 @@
|
|||
|
||||
* [ ] Add an algorithm?
|
||||
* [ ] Fix a bug or typo in an existing algorithm?
|
||||
* [ ] Add or change doctests? -- Note: Please avoid changing both code and tests in a single pull request.
|
||||
* [ ] Documentation change?
|
||||
|
||||
### Checklist:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-toml
|
||||
|
@ -16,24 +16,24 @@ repos:
|
|||
- id: auto-walrus
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.0.292
|
||||
rev: v0.1.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.9.1
|
||||
rev: 23.11.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.5
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "1.2.0"
|
||||
rev: "1.5.1"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
@ -46,12 +46,12 @@ repos:
|
|||
pass_filenames: false
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.14
|
||||
rev: v0.15
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.5.1
|
||||
rev: v1.7.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
args:
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
|
||||
## Before contributing
|
||||
|
||||
Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before sending your pull requests, make sure that you __read the whole guidelines__. If you have any doubt on the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community in [Gitter](https://gitter.im/TheAlgorithms/community).
|
||||
Welcome to [TheAlgorithms/Python](https://github.com/TheAlgorithms/Python)! Before submitting your pull requests, please ensure that you __read the whole guidelines__. If you have any doubts about the contributing guide, please feel free to [state it clearly in an issue](https://github.com/TheAlgorithms/Python/issues/new) or ask the community on [Gitter](https://gitter.im/TheAlgorithms/community).
|
||||
|
||||
## Contributing
|
||||
|
||||
### Contributor
|
||||
|
||||
We are very happy that you are considering implementing algorithms and data structures for others! This repository is referenced and used by learners from all over the globe. Being one of our contributors, you agree and confirm that:
|
||||
We are delighted that you are considering implementing algorithms and data structures for others! This repository is referenced and used by learners from all over the globe. By being one of our contributors, you agree and confirm that:
|
||||
|
||||
- You did your work - no plagiarism allowed
|
||||
- You did your work - no plagiarism allowed.
|
||||
- Any plagiarized work will not be merged.
|
||||
- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged
|
||||
- Your submitted work fulfils or mostly fulfils our styles and standards
|
||||
- Your work will be distributed under [MIT License](LICENSE.md) once your pull request is merged.
|
||||
- Your submitted work fulfills or mostly fulfills our styles and standards.
|
||||
|
||||
__New implementation__ is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity but __identical implementation__ of an existing implementation is not allowed. Please check whether the solution is already implemented or not before submitting your pull request.
|
||||
__New implementation__ is welcome! For example, new solutions for a problem, different representations for a graph data structure or algorithm designs with different complexity, but __identical implementation__ of an existing implementation is not allowed. Please check whether the solution is already implemented or not before submitting your pull request.
|
||||
|
||||
__Improving comments__ and __writing proper tests__ are also highly welcome.
|
||||
|
||||
|
@ -23,10 +23,14 @@ __Improving comments__ and __writing proper tests__ are also highly welcome.
|
|||
|
||||
We appreciate any contribution, from fixing a grammar mistake in a comment to implementing complex algorithms. Please read this section if you are contributing your work.
|
||||
|
||||
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 to read through the GitHub Actions output to understand the failure. If you do not understand, please leave a comment on your submission page and a community member will try to help.
|
||||
|
||||
#### Issues
|
||||
|
||||
If you are interested in resolving an [open issue](https://github.com/TheAlgorithms/Python/issues), simply make a pull request with your proposed fix. __We do not assign issues in this repo__ so please do not ask for permission to work on an issue.
|
||||
|
||||
__Do not__ create an issue to contribute an algorithm. Please submit a pull request instead.
|
||||
|
||||
Please help us keep our issue list small by adding `Fixes #{$ISSUE_NUMBER}` to the description of pull requests that resolve open issues.
|
||||
For example, if your pull request fixes issue #10, then please add the following to its description:
|
||||
```
|
||||
|
@ -54,7 +58,7 @@ Algorithms should:
|
|||
* contain doctests that test both valid and erroneous input values
|
||||
* return all calculation results instead of printing or plotting them
|
||||
|
||||
Algorithms in this repo should not be how-to examples for existing Python packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing Python packages but each algorithm in this repo should add unique value.
|
||||
Algorithms in this repo should not be how-to examples for existing Python packages. Instead, they should perform internal calculations or manipulations to convert input values into different output values. Those calculations or manipulations can use data types, classes, or functions of existing Python packages but each algorithm in this repo should add unique value.
|
||||
|
||||
#### Pre-commit plugin
|
||||
Use [pre-commit](https://pre-commit.com/#installation) to automatically format your code to match our coding style:
|
||||
|
@ -73,7 +77,7 @@ pre-commit run --all-files --show-diff-on-failure
|
|||
|
||||
We want your work to be readable by others; therefore, we encourage you to note the following:
|
||||
|
||||
- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
|
||||
- Please write in Python 3.12+. For instance: `print()` is a function in Python 3 so `print "Hello"` will *not* work but `print("Hello")` will.
|
||||
- Please focus hard on the naming of functions, classes, and variables. Help your reader by using __descriptive names__ that can help you to remove redundant comments.
|
||||
- Single letter variable names are *old school* so please avoid them unless their life only spans a few lines.
|
||||
- Expand acronyms because `gcd()` is hard to understand but `greatest_common_divisor()` is not.
|
||||
|
@ -141,7 +145,7 @@ We want your work to be readable by others; therefore, we encourage you to note
|
|||
python3 -m doctest -v my_submission.py
|
||||
```
|
||||
|
||||
The use of the Python builtin `input()` function is __not__ encouraged:
|
||||
The use of the Python built-in `input()` function is __not__ encouraged:
|
||||
|
||||
```python
|
||||
input('Enter your input:')
|
||||
|
|
219
DIRECTORY.md
219
DIRECTORY.md
|
@ -1,17 +1,4 @@
|
|||
|
||||
## Arithmetic Analysis
|
||||
* [Bisection](arithmetic_analysis/bisection.py)
|
||||
* [Gaussian Elimination](arithmetic_analysis/gaussian_elimination.py)
|
||||
* [In Static Equilibrium](arithmetic_analysis/in_static_equilibrium.py)
|
||||
* [Intersection](arithmetic_analysis/intersection.py)
|
||||
* [Jacobi Iteration Method](arithmetic_analysis/jacobi_iteration_method.py)
|
||||
* [Lu Decomposition](arithmetic_analysis/lu_decomposition.py)
|
||||
* [Newton Forward Interpolation](arithmetic_analysis/newton_forward_interpolation.py)
|
||||
* [Newton Method](arithmetic_analysis/newton_method.py)
|
||||
* [Newton Raphson](arithmetic_analysis/newton_raphson.py)
|
||||
* [Newton Raphson New](arithmetic_analysis/newton_raphson_new.py)
|
||||
* [Secant Method](arithmetic_analysis/secant_method.py)
|
||||
|
||||
## Audio Filters
|
||||
* [Butterworth Filter](audio_filters/butterworth_filter.py)
|
||||
* [Iir Filter](audio_filters/iir_filter.py)
|
||||
|
@ -23,8 +10,11 @@
|
|||
* [All Subsequences](backtracking/all_subsequences.py)
|
||||
* [Coloring](backtracking/coloring.py)
|
||||
* [Combination Sum](backtracking/combination_sum.py)
|
||||
* [Crossword Puzzle Solver](backtracking/crossword_puzzle_solver.py)
|
||||
* [Generate Parentheses](backtracking/generate_parentheses.py)
|
||||
* [Hamiltonian Cycle](backtracking/hamiltonian_cycle.py)
|
||||
* [Knight Tour](backtracking/knight_tour.py)
|
||||
* [Match Word Pattern](backtracking/match_word_pattern.py)
|
||||
* [Minimax](backtracking/minimax.py)
|
||||
* [N Queens](backtracking/n_queens.py)
|
||||
* [N Queens Math](backtracking/n_queens_math.py)
|
||||
|
@ -36,6 +26,7 @@
|
|||
|
||||
## Bit Manipulation
|
||||
* [Binary And Operator](bit_manipulation/binary_and_operator.py)
|
||||
* [Binary Coded Decimal](bit_manipulation/binary_coded_decimal.py)
|
||||
* [Binary Count Setbits](bit_manipulation/binary_count_setbits.py)
|
||||
* [Binary Count Trailing Zeros](bit_manipulation/binary_count_trailing_zeros.py)
|
||||
* [Binary Or Operator](bit_manipulation/binary_or_operator.py)
|
||||
|
@ -45,24 +36,31 @@
|
|||
* [Bitwise Addition Recursive](bit_manipulation/bitwise_addition_recursive.py)
|
||||
* [Count 1S Brian Kernighan Method](bit_manipulation/count_1s_brian_kernighan_method.py)
|
||||
* [Count Number Of One Bits](bit_manipulation/count_number_of_one_bits.py)
|
||||
* [Excess 3 Code](bit_manipulation/excess_3_code.py)
|
||||
* [Find Previous Power Of Two](bit_manipulation/find_previous_power_of_two.py)
|
||||
* [Gray Code Sequence](bit_manipulation/gray_code_sequence.py)
|
||||
* [Highest Set Bit](bit_manipulation/highest_set_bit.py)
|
||||
* [Index Of Rightmost Set Bit](bit_manipulation/index_of_rightmost_set_bit.py)
|
||||
* [Is Even](bit_manipulation/is_even.py)
|
||||
* [Is Power Of Two](bit_manipulation/is_power_of_two.py)
|
||||
* [Largest Pow Of Two Le Num](bit_manipulation/largest_pow_of_two_le_num.py)
|
||||
* [Missing Number](bit_manipulation/missing_number.py)
|
||||
* [Numbers Different Signs](bit_manipulation/numbers_different_signs.py)
|
||||
* [Power Of 4](bit_manipulation/power_of_4.py)
|
||||
* [Reverse Bits](bit_manipulation/reverse_bits.py)
|
||||
* [Single Bit Manipulation Operations](bit_manipulation/single_bit_manipulation_operations.py)
|
||||
* [Swap All Odd And Even Bits](bit_manipulation/swap_all_odd_and_even_bits.py)
|
||||
|
||||
## Blockchain
|
||||
* [Chinese Remainder Theorem](blockchain/chinese_remainder_theorem.py)
|
||||
* [Diophantine Equation](blockchain/diophantine_equation.py)
|
||||
* [Modular Division](blockchain/modular_division.py)
|
||||
|
||||
## Boolean Algebra
|
||||
* [And Gate](boolean_algebra/and_gate.py)
|
||||
* [Imply Gate](boolean_algebra/imply_gate.py)
|
||||
* [Karnaugh Map Simplification](boolean_algebra/karnaugh_map_simplification.py)
|
||||
* [Multiplexer](boolean_algebra/multiplexer.py)
|
||||
* [Nand Gate](boolean_algebra/nand_gate.py)
|
||||
* [Nimply Gate](boolean_algebra/nimply_gate.py)
|
||||
* [Nor Gate](boolean_algebra/nor_gate.py)
|
||||
* [Not Gate](boolean_algebra/not_gate.py)
|
||||
* [Or Gate](boolean_algebra/or_gate.py)
|
||||
|
@ -99,11 +97,13 @@
|
|||
* [Diffie Hellman](ciphers/diffie_hellman.py)
|
||||
* [Elgamal Key Generator](ciphers/elgamal_key_generator.py)
|
||||
* [Enigma Machine2](ciphers/enigma_machine2.py)
|
||||
* [Fractionated Morse Cipher](ciphers/fractionated_morse_cipher.py)
|
||||
* [Hill Cipher](ciphers/hill_cipher.py)
|
||||
* [Mixed Keyword Cypher](ciphers/mixed_keyword_cypher.py)
|
||||
* [Mono Alphabetic Ciphers](ciphers/mono_alphabetic_ciphers.py)
|
||||
* [Morse Code](ciphers/morse_code.py)
|
||||
* [Onepad Cipher](ciphers/onepad_cipher.py)
|
||||
* [Permutation Cipher](ciphers/permutation_cipher.py)
|
||||
* [Playfair Cipher](ciphers/playfair_cipher.py)
|
||||
* [Polybius](ciphers/polybius.py)
|
||||
* [Porta Cipher](ciphers/porta_cipher.py)
|
||||
|
@ -113,12 +113,14 @@
|
|||
* [Rsa Cipher](ciphers/rsa_cipher.py)
|
||||
* [Rsa Factorization](ciphers/rsa_factorization.py)
|
||||
* [Rsa Key Generator](ciphers/rsa_key_generator.py)
|
||||
* [Running Key Cipher](ciphers/running_key_cipher.py)
|
||||
* [Shuffled Shift Cipher](ciphers/shuffled_shift_cipher.py)
|
||||
* [Simple Keyword Cypher](ciphers/simple_keyword_cypher.py)
|
||||
* [Simple Substitution Cipher](ciphers/simple_substitution_cipher.py)
|
||||
* [Trafid Cipher](ciphers/trafid_cipher.py)
|
||||
* [Transposition Cipher](ciphers/transposition_cipher.py)
|
||||
* [Transposition Cipher Encrypt Decrypt File](ciphers/transposition_cipher_encrypt_decrypt_file.py)
|
||||
* [Trifid Cipher](ciphers/trifid_cipher.py)
|
||||
* [Vernam Cipher](ciphers/vernam_cipher.py)
|
||||
* [Vigenere Cipher](ciphers/vigenere_cipher.py)
|
||||
* [Xor Cipher](ciphers/xor_cipher.py)
|
||||
|
||||
|
@ -154,25 +156,38 @@
|
|||
* [Excel Title To Column](conversions/excel_title_to_column.py)
|
||||
* [Hex To Bin](conversions/hex_to_bin.py)
|
||||
* [Hexadecimal To Decimal](conversions/hexadecimal_to_decimal.py)
|
||||
* [Ipv4 Conversion](conversions/ipv4_conversion.py)
|
||||
* [Length Conversion](conversions/length_conversion.py)
|
||||
* [Molecular Chemistry](conversions/molecular_chemistry.py)
|
||||
* [Octal To Binary](conversions/octal_to_binary.py)
|
||||
* [Octal To Decimal](conversions/octal_to_decimal.py)
|
||||
* [Octal To Hexadecimal](conversions/octal_to_hexadecimal.py)
|
||||
* [Prefix Conversions](conversions/prefix_conversions.py)
|
||||
* [Prefix Conversions String](conversions/prefix_conversions_string.py)
|
||||
* [Pressure Conversions](conversions/pressure_conversions.py)
|
||||
* [Rgb Cmyk Conversion](conversions/rgb_cmyk_conversion.py)
|
||||
* [Rgb Hsv Conversion](conversions/rgb_hsv_conversion.py)
|
||||
* [Roman Numerals](conversions/roman_numerals.py)
|
||||
* [Speed Conversions](conversions/speed_conversions.py)
|
||||
* [Temperature Conversions](conversions/temperature_conversions.py)
|
||||
* [Time Conversions](conversions/time_conversions.py)
|
||||
* [Volume Conversions](conversions/volume_conversions.py)
|
||||
* [Weight Conversion](conversions/weight_conversion.py)
|
||||
|
||||
## Data Structures
|
||||
* Arrays
|
||||
* [Equilibrium Index In Array](data_structures/arrays/equilibrium_index_in_array.py)
|
||||
* [Find Triplets With 0 Sum](data_structures/arrays/find_triplets_with_0_sum.py)
|
||||
* [Index 2D Array In 1D](data_structures/arrays/index_2d_array_in_1d.py)
|
||||
* [Kth Largest Element](data_structures/arrays/kth_largest_element.py)
|
||||
* [Median Two Array](data_structures/arrays/median_two_array.py)
|
||||
* [Monotonic Array](data_structures/arrays/monotonic_array.py)
|
||||
* [Pairs With Given Sum](data_structures/arrays/pairs_with_given_sum.py)
|
||||
* [Permutations](data_structures/arrays/permutations.py)
|
||||
* [Prefix Sum](data_structures/arrays/prefix_sum.py)
|
||||
* [Product Sum](data_structures/arrays/product_sum.py)
|
||||
* [Sparse Table](data_structures/arrays/sparse_table.py)
|
||||
* [Sudoku Solver](data_structures/arrays/sudoku_solver.py)
|
||||
* Binary Tree
|
||||
* [Avl Tree](data_structures/binary_tree/avl_tree.py)
|
||||
* [Basic Binary Tree](data_structures/binary_tree/basic_binary_tree.py)
|
||||
|
@ -182,20 +197,27 @@
|
|||
* [Binary Tree Node Sum](data_structures/binary_tree/binary_tree_node_sum.py)
|
||||
* [Binary Tree Path Sum](data_structures/binary_tree/binary_tree_path_sum.py)
|
||||
* [Binary Tree Traversals](data_structures/binary_tree/binary_tree_traversals.py)
|
||||
* [Diameter Of Binary Tree](data_structures/binary_tree/diameter_of_binary_tree.py)
|
||||
* [Diff Views Of Binary Tree](data_structures/binary_tree/diff_views_of_binary_tree.py)
|
||||
* [Distribute Coins](data_structures/binary_tree/distribute_coins.py)
|
||||
* [Fenwick Tree](data_structures/binary_tree/fenwick_tree.py)
|
||||
* [Flatten Binarytree To Linkedlist](data_structures/binary_tree/flatten_binarytree_to_linkedlist.py)
|
||||
* [Floor And Ceiling](data_structures/binary_tree/floor_and_ceiling.py)
|
||||
* [Inorder Tree Traversal 2022](data_structures/binary_tree/inorder_tree_traversal_2022.py)
|
||||
* [Is Bst](data_structures/binary_tree/is_bst.py)
|
||||
* [Is Sorted](data_structures/binary_tree/is_sorted.py)
|
||||
* [Is Sum Tree](data_structures/binary_tree/is_sum_tree.py)
|
||||
* [Lazy Segment Tree](data_structures/binary_tree/lazy_segment_tree.py)
|
||||
* [Lowest Common Ancestor](data_structures/binary_tree/lowest_common_ancestor.py)
|
||||
* [Maximum Fenwick Tree](data_structures/binary_tree/maximum_fenwick_tree.py)
|
||||
* [Merge Two Binary Trees](data_structures/binary_tree/merge_two_binary_trees.py)
|
||||
* [Mirror Binary Tree](data_structures/binary_tree/mirror_binary_tree.py)
|
||||
* [Non Recursive Segment Tree](data_structures/binary_tree/non_recursive_segment_tree.py)
|
||||
* [Number Of Possible Binary Trees](data_structures/binary_tree/number_of_possible_binary_trees.py)
|
||||
* [Red Black Tree](data_structures/binary_tree/red_black_tree.py)
|
||||
* [Segment Tree](data_structures/binary_tree/segment_tree.py)
|
||||
* [Segment Tree Other](data_structures/binary_tree/segment_tree_other.py)
|
||||
* [Serialize Deserialize Binary Tree](data_structures/binary_tree/serialize_deserialize_binary_tree.py)
|
||||
* [Symmetric Tree](data_structures/binary_tree/symmetric_tree.py)
|
||||
* [Treap](data_structures/binary_tree/treap.py)
|
||||
* [Wavelet Tree](data_structures/binary_tree/wavelet_tree.py)
|
||||
* Disjoint Set
|
||||
|
@ -225,6 +247,7 @@
|
|||
* [Deque Doubly](data_structures/linked_list/deque_doubly.py)
|
||||
* [Doubly Linked List](data_structures/linked_list/doubly_linked_list.py)
|
||||
* [Doubly Linked List Two](data_structures/linked_list/doubly_linked_list_two.py)
|
||||
* [Floyds Cycle Detection](data_structures/linked_list/floyds_cycle_detection.py)
|
||||
* [From Sequence](data_structures/linked_list/from_sequence.py)
|
||||
* [Has Loop](data_structures/linked_list/has_loop.py)
|
||||
* [Is Palindrome](data_structures/linked_list/is_palindrome.py)
|
||||
|
@ -254,6 +277,7 @@
|
|||
* [Postfix Evaluation](data_structures/stacks/postfix_evaluation.py)
|
||||
* [Prefix Evaluation](data_structures/stacks/prefix_evaluation.py)
|
||||
* [Stack](data_structures/stacks/stack.py)
|
||||
* [Stack Using Two Queues](data_structures/stacks/stack_using_two_queues.py)
|
||||
* [Stack With Doubly Linked List](data_structures/stacks/stack_with_doubly_linked_list.py)
|
||||
* [Stack With Singly Linked List](data_structures/stacks/stack_with_singly_linked_list.py)
|
||||
* [Stock Span Problem](data_structures/stacks/stock_span_problem.py)
|
||||
|
@ -274,6 +298,7 @@
|
|||
* [Convolve](digital_image_processing/filters/convolve.py)
|
||||
* [Gabor Filter](digital_image_processing/filters/gabor_filter.py)
|
||||
* [Gaussian Filter](digital_image_processing/filters/gaussian_filter.py)
|
||||
* [Laplacian Filter](digital_image_processing/filters/laplacian_filter.py)
|
||||
* [Local Binary Pattern](digital_image_processing/filters/local_binary_pattern.py)
|
||||
* [Median Filter](digital_image_processing/filters/median_filter.py)
|
||||
* [Sobel Filter](digital_image_processing/filters/sobel_filter.py)
|
||||
|
@ -320,11 +345,13 @@
|
|||
* [Integer Partition](dynamic_programming/integer_partition.py)
|
||||
* [Iterating Through Submasks](dynamic_programming/iterating_through_submasks.py)
|
||||
* [Knapsack](dynamic_programming/knapsack.py)
|
||||
* [Largest Divisible Subset](dynamic_programming/largest_divisible_subset.py)
|
||||
* [Longest Common Subsequence](dynamic_programming/longest_common_subsequence.py)
|
||||
* [Longest Common Substring](dynamic_programming/longest_common_substring.py)
|
||||
* [Longest Increasing Subsequence](dynamic_programming/longest_increasing_subsequence.py)
|
||||
* [Longest Increasing Subsequence O(Nlogn)](dynamic_programming/longest_increasing_subsequence_o(nlogn).py)
|
||||
* [Longest Sub Array](dynamic_programming/longest_sub_array.py)
|
||||
* [Longest Palindromic Subsequence](dynamic_programming/longest_palindromic_subsequence.py)
|
||||
* [Matrix Chain Multiplication](dynamic_programming/matrix_chain_multiplication.py)
|
||||
* [Matrix Chain Order](dynamic_programming/matrix_chain_order.py)
|
||||
* [Max Non Adjacent Sum](dynamic_programming/max_non_adjacent_sum.py)
|
||||
* [Max Product Subarray](dynamic_programming/max_product_subarray.py)
|
||||
|
@ -344,24 +371,32 @@
|
|||
* [Smith Waterman](dynamic_programming/smith_waterman.py)
|
||||
* [Subset Generation](dynamic_programming/subset_generation.py)
|
||||
* [Sum Of Subset](dynamic_programming/sum_of_subset.py)
|
||||
* [Trapped Water](dynamic_programming/trapped_water.py)
|
||||
* [Tribonacci](dynamic_programming/tribonacci.py)
|
||||
* [Viterbi](dynamic_programming/viterbi.py)
|
||||
* [Wildcard Matching](dynamic_programming/wildcard_matching.py)
|
||||
* [Word Break](dynamic_programming/word_break.py)
|
||||
|
||||
## Electronics
|
||||
* [Apparent Power](electronics/apparent_power.py)
|
||||
* [Builtin Voltage](electronics/builtin_voltage.py)
|
||||
* [Capacitor Equivalence](electronics/capacitor_equivalence.py)
|
||||
* [Carrier Concentration](electronics/carrier_concentration.py)
|
||||
* [Charging Capacitor](electronics/charging_capacitor.py)
|
||||
* [Charging Inductor](electronics/charging_inductor.py)
|
||||
* [Circular Convolution](electronics/circular_convolution.py)
|
||||
* [Coulombs Law](electronics/coulombs_law.py)
|
||||
* [Electric Conductivity](electronics/electric_conductivity.py)
|
||||
* [Electric Power](electronics/electric_power.py)
|
||||
* [Electrical Impedance](electronics/electrical_impedance.py)
|
||||
* [Ic 555 Timer](electronics/ic_555_timer.py)
|
||||
* [Ind Reactance](electronics/ind_reactance.py)
|
||||
* [Ohms Law](electronics/ohms_law.py)
|
||||
* [Real And Reactive Power](electronics/real_and_reactive_power.py)
|
||||
* [Resistor Color Code](electronics/resistor_color_code.py)
|
||||
* [Resistor Equivalence](electronics/resistor_equivalence.py)
|
||||
* [Resonant Frequency](electronics/resonant_frequency.py)
|
||||
* [Wheatstone Bridge](electronics/wheatstone_bridge.py)
|
||||
|
||||
## File Transfer
|
||||
* [Receive File](file_transfer/receive_file.py)
|
||||
|
@ -371,9 +406,11 @@
|
|||
|
||||
## Financial
|
||||
* [Equated Monthly Installments](financial/equated_monthly_installments.py)
|
||||
* [Exponential Moving Average](financial/exponential_moving_average.py)
|
||||
* [Interest](financial/interest.py)
|
||||
* [Present Value](financial/present_value.py)
|
||||
* [Price Plus Tax](financial/price_plus_tax.py)
|
||||
* [Simple Moving Average](financial/simple_moving_average.py)
|
||||
|
||||
## Fractals
|
||||
* [Julia Sets](fractals/julia_sets.py)
|
||||
|
@ -381,6 +418,9 @@
|
|||
* [Mandelbrot](fractals/mandelbrot.py)
|
||||
* [Sierpinski Triangle](fractals/sierpinski_triangle.py)
|
||||
|
||||
## Fuzzy Logic
|
||||
* [Fuzzy Operations](fuzzy_logic/fuzzy_operations.py)
|
||||
|
||||
## Genetic Algorithm
|
||||
* [Basic String](genetic_algorithm/basic_string.py)
|
||||
|
||||
|
@ -388,12 +428,16 @@
|
|||
* [Haversine Distance](geodesy/haversine_distance.py)
|
||||
* [Lamberts Ellipsoidal Distance](geodesy/lamberts_ellipsoidal_distance.py)
|
||||
|
||||
## Geometry
|
||||
* [Geometry](geometry/geometry.py)
|
||||
|
||||
## Graphics
|
||||
* [Bezier Curve](graphics/bezier_curve.py)
|
||||
* [Vector3 For 2D Rendering](graphics/vector3_for_2d_rendering.py)
|
||||
|
||||
## Graphs
|
||||
* [A Star](graphs/a_star.py)
|
||||
* [Ant Colony Optimization Algorithms](graphs/ant_colony_optimization_algorithms.py)
|
||||
* [Articulation Points](graphs/articulation_points.py)
|
||||
* [Basic Graphs](graphs/basic_graphs.py)
|
||||
* [Bellman Ford](graphs/bellman_ford.py)
|
||||
|
@ -406,10 +450,10 @@
|
|||
* [Breadth First Search Shortest Path](graphs/breadth_first_search_shortest_path.py)
|
||||
* [Breadth First Search Shortest Path 2](graphs/breadth_first_search_shortest_path_2.py)
|
||||
* [Breadth First Search Zero One Shortest Path](graphs/breadth_first_search_zero_one_shortest_path.py)
|
||||
* [Check Bipartite Graph Bfs](graphs/check_bipartite_graph_bfs.py)
|
||||
* [Check Bipartite Graph Dfs](graphs/check_bipartite_graph_dfs.py)
|
||||
* [Check Bipatrite](graphs/check_bipatrite.py)
|
||||
* [Check Cycle](graphs/check_cycle.py)
|
||||
* [Connected Components](graphs/connected_components.py)
|
||||
* [Deep Clone Graph](graphs/deep_clone_graph.py)
|
||||
* [Depth First Search](graphs/depth_first_search.py)
|
||||
* [Depth First Search 2](graphs/depth_first_search_2.py)
|
||||
* [Dijkstra](graphs/dijkstra.py)
|
||||
|
@ -455,8 +499,12 @@
|
|||
* [Test Min Spanning Tree Prim](graphs/tests/test_min_spanning_tree_prim.py)
|
||||
|
||||
## Greedy Methods
|
||||
* [Best Time To Buy And Sell Stock](greedy_methods/best_time_to_buy_and_sell_stock.py)
|
||||
* [Fractional Cover Problem](greedy_methods/fractional_cover_problem.py)
|
||||
* [Fractional Knapsack](greedy_methods/fractional_knapsack.py)
|
||||
* [Fractional Knapsack 2](greedy_methods/fractional_knapsack_2.py)
|
||||
* [Gas Station](greedy_methods/gas_station.py)
|
||||
* [Minimum Coin Change](greedy_methods/minimum_coin_change.py)
|
||||
* [Minimum Waiting Time](greedy_methods/minimum_waiting_time.py)
|
||||
* [Optimal Merge Pattern](greedy_methods/optimal_merge_pattern.py)
|
||||
|
||||
|
@ -466,6 +514,7 @@
|
|||
* [Djb2](hashes/djb2.py)
|
||||
* [Elf](hashes/elf.py)
|
||||
* [Enigma Machine](hashes/enigma_machine.py)
|
||||
* [Fletcher16](hashes/fletcher16.py)
|
||||
* [Hamming Code](hashes/hamming_code.py)
|
||||
* [Luhn](hashes/luhn.py)
|
||||
* [Md5](hashes/md5.py)
|
||||
|
@ -482,8 +531,13 @@
|
|||
* [Test Knapsack](knapsack/tests/test_knapsack.py)
|
||||
|
||||
## Linear Algebra
|
||||
* [Gaussian Elimination](linear_algebra/gaussian_elimination.py)
|
||||
* [Jacobi Iteration Method](linear_algebra/jacobi_iteration_method.py)
|
||||
* [Lu Decomposition](linear_algebra/lu_decomposition.py)
|
||||
* Src
|
||||
* [Conjugate Gradient](linear_algebra/src/conjugate_gradient.py)
|
||||
* Gaussian Elimination Pivoting
|
||||
* [Gaussian Elimination Pivoting](linear_algebra/src/gaussian_elimination_pivoting/gaussian_elimination_pivoting.py)
|
||||
* [Lib](linear_algebra/src/lib.py)
|
||||
* [Polynom For Points](linear_algebra/src/polynom_for_points.py)
|
||||
* [Power Iteration](linear_algebra/src/power_iteration.py)
|
||||
|
@ -497,12 +551,16 @@
|
|||
* [Simplex](linear_programming/simplex.py)
|
||||
|
||||
## Machine Learning
|
||||
* [Apriori Algorithm](machine_learning/apriori_algorithm.py)
|
||||
* [Astar](machine_learning/astar.py)
|
||||
* [Automatic Differentiation](machine_learning/automatic_differentiation.py)
|
||||
* [Data Transformations](machine_learning/data_transformations.py)
|
||||
* [Decision Tree](machine_learning/decision_tree.py)
|
||||
* [Dimensionality Reduction](machine_learning/dimensionality_reduction.py)
|
||||
* Forecasting
|
||||
* [Run](machine_learning/forecasting/run.py)
|
||||
* [Frequent Pattern Growth](machine_learning/frequent_pattern_growth.py)
|
||||
* [Gradient Boosting Classifier](machine_learning/gradient_boosting_classifier.py)
|
||||
* [Gradient Descent](machine_learning/gradient_descent.py)
|
||||
* [K Means Clust](machine_learning/k_means_clust.py)
|
||||
* [K Nearest Neighbours](machine_learning/k_nearest_neighbours.py)
|
||||
|
@ -511,6 +569,7 @@
|
|||
* Local Weighted Learning
|
||||
* [Local Weighted Learning](machine_learning/local_weighted_learning/local_weighted_learning.py)
|
||||
* [Logistic Regression](machine_learning/logistic_regression.py)
|
||||
* [Loss Functions](machine_learning/loss_functions.py)
|
||||
* [Mfcc](machine_learning/mfcc.py)
|
||||
* [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
|
||||
* [Polynomial Regression](machine_learning/polynomial_regression.py)
|
||||
|
@ -531,25 +590,21 @@
|
|||
* [Arc Length](maths/arc_length.py)
|
||||
* [Area](maths/area.py)
|
||||
* [Area Under Curve](maths/area_under_curve.py)
|
||||
* [Armstrong Numbers](maths/armstrong_numbers.py)
|
||||
* [Automorphic Number](maths/automorphic_number.py)
|
||||
* [Average Absolute Deviation](maths/average_absolute_deviation.py)
|
||||
* [Average Mean](maths/average_mean.py)
|
||||
* [Average Median](maths/average_median.py)
|
||||
* [Average Mode](maths/average_mode.py)
|
||||
* [Bailey Borwein Plouffe](maths/bailey_borwein_plouffe.py)
|
||||
* [Base Neg2 Conversion](maths/base_neg2_conversion.py)
|
||||
* [Basic Maths](maths/basic_maths.py)
|
||||
* [Binary Exp Mod](maths/binary_exp_mod.py)
|
||||
* [Binary Exponentiation](maths/binary_exponentiation.py)
|
||||
* [Binary Exponentiation 2](maths/binary_exponentiation_2.py)
|
||||
* [Binary Exponentiation 3](maths/binary_exponentiation_3.py)
|
||||
* [Binary Multiplication](maths/binary_multiplication.py)
|
||||
* [Binomial Coefficient](maths/binomial_coefficient.py)
|
||||
* [Binomial Distribution](maths/binomial_distribution.py)
|
||||
* [Bisection](maths/bisection.py)
|
||||
* [Carmichael Number](maths/carmichael_number.py)
|
||||
* [Catalan Number](maths/catalan_number.py)
|
||||
* [Ceil](maths/ceil.py)
|
||||
* [Chebyshev Distance](maths/chebyshev_distance.py)
|
||||
* [Check Polygon](maths/check_polygon.py)
|
||||
* [Chinese Remainder Theorem](maths/chinese_remainder_theorem.py)
|
||||
* [Chudnovsky Algorithm](maths/chudnovsky_algorithm.py)
|
||||
* [Collatz Sequence](maths/collatz_sequence.py)
|
||||
* [Combinations](maths/combinations.py)
|
||||
|
@ -557,8 +612,7 @@
|
|||
* [Decimal Isolate](maths/decimal_isolate.py)
|
||||
* [Decimal To Fraction](maths/decimal_to_fraction.py)
|
||||
* [Dodecahedron](maths/dodecahedron.py)
|
||||
* [Double Factorial Iterative](maths/double_factorial_iterative.py)
|
||||
* [Double Factorial Recursive](maths/double_factorial_recursive.py)
|
||||
* [Double Factorial](maths/double_factorial.py)
|
||||
* [Dual Number Automatic Differentiation](maths/dual_number_automatic_differentiation.py)
|
||||
* [Entropy](maths/entropy.py)
|
||||
* [Euclidean Distance](maths/euclidean_distance.py)
|
||||
|
@ -568,31 +622,29 @@
|
|||
* [Extended Euclidean Algorithm](maths/extended_euclidean_algorithm.py)
|
||||
* [Factorial](maths/factorial.py)
|
||||
* [Factors](maths/factors.py)
|
||||
* [Fast Inverse Sqrt](maths/fast_inverse_sqrt.py)
|
||||
* [Fermat Little Theorem](maths/fermat_little_theorem.py)
|
||||
* [Fibonacci](maths/fibonacci.py)
|
||||
* [Find Max](maths/find_max.py)
|
||||
* [Find Min](maths/find_min.py)
|
||||
* [Floor](maths/floor.py)
|
||||
* [Gamma](maths/gamma.py)
|
||||
* [Gamma Recursive](maths/gamma_recursive.py)
|
||||
* [Gaussian](maths/gaussian.py)
|
||||
* [Gaussian Error Linear Unit](maths/gaussian_error_linear_unit.py)
|
||||
* [Gcd Of N Numbers](maths/gcd_of_n_numbers.py)
|
||||
* [Germain Primes](maths/germain_primes.py)
|
||||
* [Greatest Common Divisor](maths/greatest_common_divisor.py)
|
||||
* [Greedy Coin Change](maths/greedy_coin_change.py)
|
||||
* [Hamming Numbers](maths/hamming_numbers.py)
|
||||
* [Hardy Ramanujanalgo](maths/hardy_ramanujanalgo.py)
|
||||
* [Harshad Numbers](maths/harshad_numbers.py)
|
||||
* [Hexagonal Number](maths/hexagonal_number.py)
|
||||
* [Integration By Simpson Approx](maths/integration_by_simpson_approx.py)
|
||||
* [Integer Square Root](maths/integer_square_root.py)
|
||||
* [Interquartile Range](maths/interquartile_range.py)
|
||||
* [Is Int Palindrome](maths/is_int_palindrome.py)
|
||||
* [Is Ip V4 Address Valid](maths/is_ip_v4_address_valid.py)
|
||||
* [Is Square Free](maths/is_square_free.py)
|
||||
* [Jaccard Similarity](maths/jaccard_similarity.py)
|
||||
* [Joint Probability Distribution](maths/joint_probability_distribution.py)
|
||||
* [Josephus Problem](maths/josephus_problem.py)
|
||||
* [Juggler Sequence](maths/juggler_sequence.py)
|
||||
* [Karatsuba](maths/karatsuba.py)
|
||||
* [Krishnamurthy Number](maths/krishnamurthy_number.py)
|
||||
* [Kth Lexicographic Permutation](maths/kth_lexicographic_permutation.py)
|
||||
* [Largest Of Very Large Numbers](maths/largest_of_very_large_numbers.py)
|
||||
* [Least Common Multiple](maths/least_common_multiple.py)
|
||||
|
@ -605,14 +657,29 @@
|
|||
* [Matrix Exponentiation](maths/matrix_exponentiation.py)
|
||||
* [Max Sum Sliding Window](maths/max_sum_sliding_window.py)
|
||||
* [Median Of Two Arrays](maths/median_of_two_arrays.py)
|
||||
* [Minkowski Distance](maths/minkowski_distance.py)
|
||||
* [Mobius Function](maths/mobius_function.py)
|
||||
* [Modular Division](maths/modular_division.py)
|
||||
* [Modular Exponential](maths/modular_exponential.py)
|
||||
* [Monte Carlo](maths/monte_carlo.py)
|
||||
* [Monte Carlo Dice](maths/monte_carlo_dice.py)
|
||||
* [Nevilles Method](maths/nevilles_method.py)
|
||||
* [Newton Raphson](maths/newton_raphson.py)
|
||||
* [Number Of Digits](maths/number_of_digits.py)
|
||||
* [Numerical Integration](maths/numerical_integration.py)
|
||||
* Numerical Analysis
|
||||
* [Adams Bashforth](maths/numerical_analysis/adams_bashforth.py)
|
||||
* [Bisection](maths/numerical_analysis/bisection.py)
|
||||
* [Bisection 2](maths/numerical_analysis/bisection_2.py)
|
||||
* [Integration By Simpson Approx](maths/numerical_analysis/integration_by_simpson_approx.py)
|
||||
* [Intersection](maths/numerical_analysis/intersection.py)
|
||||
* [Nevilles Method](maths/numerical_analysis/nevilles_method.py)
|
||||
* [Newton Forward Interpolation](maths/numerical_analysis/newton_forward_interpolation.py)
|
||||
* [Newton Raphson](maths/numerical_analysis/newton_raphson.py)
|
||||
* [Numerical Integration](maths/numerical_analysis/numerical_integration.py)
|
||||
* [Runge Kutta](maths/numerical_analysis/runge_kutta.py)
|
||||
* [Runge Kutta Fehlberg 45](maths/numerical_analysis/runge_kutta_fehlberg_45.py)
|
||||
* [Runge Kutta Gills](maths/numerical_analysis/runge_kutta_gills.py)
|
||||
* [Secant Method](maths/numerical_analysis/secant_method.py)
|
||||
* [Simpson Rule](maths/numerical_analysis/simpson_rule.py)
|
||||
* [Square Root](maths/numerical_analysis/square_root.py)
|
||||
* [Odd Sieve](maths/odd_sieve.py)
|
||||
* [Perfect Cube](maths/perfect_cube.py)
|
||||
* [Perfect Number](maths/perfect_number.py)
|
||||
|
@ -622,7 +689,6 @@
|
|||
* [Pi Monte Carlo Estimation](maths/pi_monte_carlo_estimation.py)
|
||||
* [Points Are Collinear 3D](maths/points_are_collinear_3d.py)
|
||||
* [Pollard Rho](maths/pollard_rho.py)
|
||||
* [Polygonal Numbers](maths/polygonal_numbers.py)
|
||||
* [Polynomial Evaluation](maths/polynomial_evaluation.py)
|
||||
* Polynomials
|
||||
* [Single Indeterminate Operations](maths/polynomials/single_indeterminate_operations.py)
|
||||
|
@ -633,16 +699,12 @@
|
|||
* [Prime Sieve Eratosthenes](maths/prime_sieve_eratosthenes.py)
|
||||
* [Primelib](maths/primelib.py)
|
||||
* [Print Multiplication Table](maths/print_multiplication_table.py)
|
||||
* [Pronic Number](maths/pronic_number.py)
|
||||
* [Proth Number](maths/proth_number.py)
|
||||
* [Pythagoras](maths/pythagoras.py)
|
||||
* [Qr Decomposition](maths/qr_decomposition.py)
|
||||
* [Quadratic Equations Complex Numbers](maths/quadratic_equations_complex_numbers.py)
|
||||
* [Radians](maths/radians.py)
|
||||
* [Radix2 Fft](maths/radix2_fft.py)
|
||||
* [Relu](maths/relu.py)
|
||||
* [Remove Digit](maths/remove_digit.py)
|
||||
* [Runge Kutta](maths/runge_kutta.py)
|
||||
* [Segmented Sieve](maths/segmented_sieve.py)
|
||||
* Series
|
||||
* [Arithmetic](maths/series/arithmetic.py)
|
||||
|
@ -654,14 +716,31 @@
|
|||
* [P Series](maths/series/p_series.py)
|
||||
* [Sieve Of Eratosthenes](maths/sieve_of_eratosthenes.py)
|
||||
* [Sigmoid](maths/sigmoid.py)
|
||||
* [Sigmoid Linear Unit](maths/sigmoid_linear_unit.py)
|
||||
* [Signum](maths/signum.py)
|
||||
* [Simpson Rule](maths/simpson_rule.py)
|
||||
* [Simultaneous Linear Equation Solver](maths/simultaneous_linear_equation_solver.py)
|
||||
* [Sin](maths/sin.py)
|
||||
* [Sock Merchant](maths/sock_merchant.py)
|
||||
* [Softmax](maths/softmax.py)
|
||||
* [Square Root](maths/square_root.py)
|
||||
* [Solovay Strassen Primality Test](maths/solovay_strassen_primality_test.py)
|
||||
* [Spearman Rank Correlation Coefficient](maths/spearman_rank_correlation_coefficient.py)
|
||||
* Special Numbers
|
||||
* [Armstrong Numbers](maths/special_numbers/armstrong_numbers.py)
|
||||
* [Automorphic Number](maths/special_numbers/automorphic_number.py)
|
||||
* [Bell Numbers](maths/special_numbers/bell_numbers.py)
|
||||
* [Carmichael Number](maths/special_numbers/carmichael_number.py)
|
||||
* [Catalan Number](maths/special_numbers/catalan_number.py)
|
||||
* [Hamming Numbers](maths/special_numbers/hamming_numbers.py)
|
||||
* [Happy Number](maths/special_numbers/happy_number.py)
|
||||
* [Harshad Numbers](maths/special_numbers/harshad_numbers.py)
|
||||
* [Hexagonal Number](maths/special_numbers/hexagonal_number.py)
|
||||
* [Krishnamurthy Number](maths/special_numbers/krishnamurthy_number.py)
|
||||
* [Perfect Number](maths/special_numbers/perfect_number.py)
|
||||
* [Polygonal Numbers](maths/special_numbers/polygonal_numbers.py)
|
||||
* [Pronic Number](maths/special_numbers/pronic_number.py)
|
||||
* [Proth Number](maths/special_numbers/proth_number.py)
|
||||
* [Triangular Numbers](maths/special_numbers/triangular_numbers.py)
|
||||
* [Ugly Numbers](maths/special_numbers/ugly_numbers.py)
|
||||
* [Weird Number](maths/special_numbers/weird_number.py)
|
||||
* [Sum Of Arithmetic Series](maths/sum_of_arithmetic_series.py)
|
||||
* [Sum Of Digits](maths/sum_of_digits.py)
|
||||
* [Sum Of Geometric Progression](maths/sum_of_geometric_progression.py)
|
||||
|
@ -676,9 +755,7 @@
|
|||
* [Twin Prime](maths/twin_prime.py)
|
||||
* [Two Pointer](maths/two_pointer.py)
|
||||
* [Two Sum](maths/two_sum.py)
|
||||
* [Ugly Numbers](maths/ugly_numbers.py)
|
||||
* [Volume](maths/volume.py)
|
||||
* [Weird Number](maths/weird_number.py)
|
||||
* [Zellers Congruence](maths/zellers_congruence.py)
|
||||
|
||||
## Matrix
|
||||
|
@ -690,8 +767,10 @@
|
|||
* [Inverse Of Matrix](matrix/inverse_of_matrix.py)
|
||||
* [Largest Square Area In Matrix](matrix/largest_square_area_in_matrix.py)
|
||||
* [Matrix Class](matrix/matrix_class.py)
|
||||
* [Matrix Multiplication Recursion](matrix/matrix_multiplication_recursion.py)
|
||||
* [Matrix Operation](matrix/matrix_operation.py)
|
||||
* [Max Area Of Island](matrix/max_area_of_island.py)
|
||||
* [Median Matrix](matrix/median_matrix.py)
|
||||
* [Nth Fibonacci Using Matrix Exponentiation](matrix/nth_fibonacci_using_matrix_exponentiation.py)
|
||||
* [Pascal Triangle](matrix/pascal_triangle.py)
|
||||
* [Rotate Matrix](matrix/rotate_matrix.py)
|
||||
|
@ -700,6 +779,7 @@
|
|||
* [Spiral Print](matrix/spiral_print.py)
|
||||
* Tests
|
||||
* [Test Matrix Operation](matrix/tests/test_matrix_operation.py)
|
||||
* [Validate Sudoku Board](matrix/validate_sudoku_board.py)
|
||||
|
||||
## Networking Flow
|
||||
* [Ford Fulkerson](networking_flow/ford_fulkerson.py)
|
||||
|
@ -708,19 +788,25 @@
|
|||
## Neural Network
|
||||
* [2 Hidden Layers Neural Network](neural_network/2_hidden_layers_neural_network.py)
|
||||
* Activation Functions
|
||||
* [Binary Step](neural_network/activation_functions/binary_step.py)
|
||||
* [Exponential Linear Unit](neural_network/activation_functions/exponential_linear_unit.py)
|
||||
* [Leaky Rectified Linear Unit](neural_network/activation_functions/leaky_rectified_linear_unit.py)
|
||||
* [Mish](neural_network/activation_functions/mish.py)
|
||||
* [Rectified Linear Unit](neural_network/activation_functions/rectified_linear_unit.py)
|
||||
* [Scaled Exponential Linear Unit](neural_network/activation_functions/scaled_exponential_linear_unit.py)
|
||||
* [Soboleva Modified Hyperbolic Tangent](neural_network/activation_functions/soboleva_modified_hyperbolic_tangent.py)
|
||||
* [Softplus](neural_network/activation_functions/softplus.py)
|
||||
* [Squareplus](neural_network/activation_functions/squareplus.py)
|
||||
* [Swish](neural_network/activation_functions/swish.py)
|
||||
* [Back Propagation Neural Network](neural_network/back_propagation_neural_network.py)
|
||||
* [Convolution Neural Network](neural_network/convolution_neural_network.py)
|
||||
* [Perceptron](neural_network/perceptron.py)
|
||||
* [Simple Neural Network](neural_network/simple_neural_network.py)
|
||||
|
||||
## Other
|
||||
* [Activity Selection](other/activity_selection.py)
|
||||
* [Alternative List Arrange](other/alternative_list_arrange.py)
|
||||
* [Davisb Putnamb Logemannb Loveland](other/davisb_putnamb_logemannb_loveland.py)
|
||||
* [Dijkstra Bankers Algorithm](other/dijkstra_bankers_algorithm.py)
|
||||
* [Bankers Algorithm](other/bankers_algorithm.py)
|
||||
* [Davis Putnam Logemann Loveland](other/davis_putnam_logemann_loveland.py)
|
||||
* [Doomsday](other/doomsday.py)
|
||||
* [Fischer Yates Shuffle](other/fischer_yates_shuffle.py)
|
||||
* [Gauss Easter](other/gauss_easter.py)
|
||||
|
@ -733,6 +819,7 @@
|
|||
* [Linear Congruential Generator](other/linear_congruential_generator.py)
|
||||
* [Lru Cache](other/lru_cache.py)
|
||||
* [Magicdiamondpattern](other/magicdiamondpattern.py)
|
||||
* [Majority Vote Algorithm](other/majority_vote_algorithm.py)
|
||||
* [Maximum Subsequence](other/maximum_subsequence.py)
|
||||
* [Nested Brackets](other/nested_brackets.py)
|
||||
* [Number Container System](other/number_container_system.py)
|
||||
|
@ -745,25 +832,35 @@
|
|||
|
||||
## Physics
|
||||
* [Altitude Pressure](physics/altitude_pressure.py)
|
||||
* [Archimedes Principle](physics/archimedes_principle.py)
|
||||
* [Archimedes Principle Of Buoyant Force](physics/archimedes_principle_of_buoyant_force.py)
|
||||
* [Basic Orbital Capture](physics/basic_orbital_capture.py)
|
||||
* [Casimir Effect](physics/casimir_effect.py)
|
||||
* [Center Of Mass](physics/center_of_mass.py)
|
||||
* [Centripetal Force](physics/centripetal_force.py)
|
||||
* [Coulombs Law](physics/coulombs_law.py)
|
||||
* [Doppler Frequency](physics/doppler_frequency.py)
|
||||
* [Grahams Law](physics/grahams_law.py)
|
||||
* [Horizontal Projectile Motion](physics/horizontal_projectile_motion.py)
|
||||
* [Hubble Parameter](physics/hubble_parameter.py)
|
||||
* [Ideal Gas Law](physics/ideal_gas_law.py)
|
||||
* [In Static Equilibrium](physics/in_static_equilibrium.py)
|
||||
* [Kinetic Energy](physics/kinetic_energy.py)
|
||||
* [Lens Formulae](physics/lens_formulae.py)
|
||||
* [Lorentz Transformation Four Vector](physics/lorentz_transformation_four_vector.py)
|
||||
* [Malus Law](physics/malus_law.py)
|
||||
* [Mass Energy Equivalence](physics/mass_energy_equivalence.py)
|
||||
* [Mirror Formulae](physics/mirror_formulae.py)
|
||||
* [N Body Simulation](physics/n_body_simulation.py)
|
||||
* [Newtons Law Of Gravitation](physics/newtons_law_of_gravitation.py)
|
||||
* [Newtons Second Law Of Motion](physics/newtons_second_law_of_motion.py)
|
||||
* [Photoelectric Effect](physics/photoelectric_effect.py)
|
||||
* [Potential Energy](physics/potential_energy.py)
|
||||
* [Reynolds Number](physics/reynolds_number.py)
|
||||
* [Rms Speed Of Molecule](physics/rms_speed_of_molecule.py)
|
||||
* [Shear Stress](physics/shear_stress.py)
|
||||
* [Speed Of Sound](physics/speed_of_sound.py)
|
||||
* [Speeds Of Gas Molecules](physics/speeds_of_gas_molecules.py)
|
||||
* [Terminal Velocity](physics/terminal_velocity.py)
|
||||
|
||||
## Project Euler
|
||||
* Problem 001
|
||||
|
@ -1067,6 +1164,7 @@
|
|||
## Scheduling
|
||||
* [First Come First Served](scheduling/first_come_first_served.py)
|
||||
* [Highest Response Ratio Next](scheduling/highest_response_ratio_next.py)
|
||||
* [Job Sequence With Deadline](scheduling/job_sequence_with_deadline.py)
|
||||
* [Job Sequencing With Deadline](scheduling/job_sequencing_with_deadline.py)
|
||||
* [Multi Level Feedback Queue](scheduling/multi_level_feedback_queue.py)
|
||||
* [Non Preemptive Shortest Job First](scheduling/non_preemptive_shortest_job_first.py)
|
||||
|
@ -1083,6 +1181,7 @@
|
|||
* [Interpolation Search](searches/interpolation_search.py)
|
||||
* [Jump Search](searches/jump_search.py)
|
||||
* [Linear Search](searches/linear_search.py)
|
||||
* [Median Of Medians](searches/median_of_medians.py)
|
||||
* [Quick Select](searches/quick_select.py)
|
||||
* [Sentinel Linear Search](searches/sentinel_linear_search.py)
|
||||
* [Simple Binary Search](searches/simple_binary_search.py)
|
||||
|
@ -1125,7 +1224,6 @@
|
|||
* [Quick Sort](sorts/quick_sort.py)
|
||||
* [Quick Sort 3 Partition](sorts/quick_sort_3_partition.py)
|
||||
* [Radix Sort](sorts/radix_sort.py)
|
||||
* [Recursive Bubble Sort](sorts/recursive_bubble_sort.py)
|
||||
* [Recursive Insertion Sort](sorts/recursive_insertion_sort.py)
|
||||
* [Recursive Mergesort Array](sorts/recursive_mergesort_array.py)
|
||||
* [Recursive Quick Sort](sorts/recursive_quick_sort.py)
|
||||
|
@ -1147,19 +1245,24 @@
|
|||
* [Anagrams](strings/anagrams.py)
|
||||
* [Autocomplete Using Trie](strings/autocomplete_using_trie.py)
|
||||
* [Barcode Validator](strings/barcode_validator.py)
|
||||
* [Bitap String Match](strings/bitap_string_match.py)
|
||||
* [Boyer Moore Search](strings/boyer_moore_search.py)
|
||||
* [Camel Case To Snake Case](strings/camel_case_to_snake_case.py)
|
||||
* [Can String Be Rearranged As Palindrome](strings/can_string_be_rearranged_as_palindrome.py)
|
||||
* [Capitalize](strings/capitalize.py)
|
||||
* [Check Anagrams](strings/check_anagrams.py)
|
||||
* [Credit Card Validator](strings/credit_card_validator.py)
|
||||
* [Damerau Levenshtein Distance](strings/damerau_levenshtein_distance.py)
|
||||
* [Detecting English Programmatically](strings/detecting_english_programmatically.py)
|
||||
* [Dna](strings/dna.py)
|
||||
* [Edit Distance](strings/edit_distance.py)
|
||||
* [Frequency Finder](strings/frequency_finder.py)
|
||||
* [Hamming Distance](strings/hamming_distance.py)
|
||||
* [Indian Phone Validator](strings/indian_phone_validator.py)
|
||||
* [Is Contains Unique Chars](strings/is_contains_unique_chars.py)
|
||||
* [Is Isogram](strings/is_isogram.py)
|
||||
* [Is Pangram](strings/is_pangram.py)
|
||||
* [Is Polish National Id](strings/is_polish_national_id.py)
|
||||
* [Is Spain National Id](strings/is_spain_national_id.py)
|
||||
* [Is Srilankan Phone Number](strings/is_srilankan_phone_number.py)
|
||||
* [Is Valid Email Address](strings/is_valid_email_address.py)
|
||||
|
@ -1173,16 +1276,18 @@
|
|||
* [Naive String Search](strings/naive_string_search.py)
|
||||
* [Ngram](strings/ngram.py)
|
||||
* [Palindrome](strings/palindrome.py)
|
||||
* [Pig Latin](strings/pig_latin.py)
|
||||
* [Prefix Function](strings/prefix_function.py)
|
||||
* [Rabin Karp](strings/rabin_karp.py)
|
||||
* [Remove Duplicate](strings/remove_duplicate.py)
|
||||
* [Reverse Letters](strings/reverse_letters.py)
|
||||
* [Reverse Long Words](strings/reverse_long_words.py)
|
||||
* [Reverse Words](strings/reverse_words.py)
|
||||
* [Snake Case To Camel Pascal Case](strings/snake_case_to_camel_pascal_case.py)
|
||||
* [Split](strings/split.py)
|
||||
* [String Switch Case](strings/string_switch_case.py)
|
||||
* [Strip](strings/strip.py)
|
||||
* [Text Justification](strings/text_justification.py)
|
||||
* [Title](strings/title.py)
|
||||
* [Top K Frequent Words](strings/top_k_frequent_words.py)
|
||||
* [Upper](strings/upper.py)
|
||||
* [Wave](strings/wave.py)
|
||||
|
@ -1210,7 +1315,7 @@
|
|||
* [Fetch Well Rx Price](web_programming/fetch_well_rx_price.py)
|
||||
* [Get Amazon Product Data](web_programming/get_amazon_product_data.py)
|
||||
* [Get Imdb Top 250 Movies Csv](web_programming/get_imdb_top_250_movies_csv.py)
|
||||
* [Get Imdbtop](web_programming/get_imdbtop.py)
|
||||
* [Get Ip Geolocation](web_programming/get_ip_geolocation.py)
|
||||
* [Get Top Billionaires](web_programming/get_top_billionaires.py)
|
||||
* [Get Top Hn Posts](web_programming/get_top_hn_posts.py)
|
||||
* [Get User Tweets](web_programming/get_user_tweets.py)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
# Arithmetic analysis
|
||||
|
||||
Arithmetic analysis is a branch of mathematics that deals with solving linear equations.
|
||||
|
||||
* <https://en.wikipedia.org/wiki/System_of_linear_equations>
|
||||
* <https://en.wikipedia.org/wiki/Gaussian_elimination>
|
||||
* <https://en.wikipedia.org/wiki/Root-finding_algorithms>
|
|
@ -1,54 +0,0 @@
|
|||
"""Newton's Method."""
|
||||
|
||||
# Newton's Method - https://en.wikipedia.org/wiki/Newton%27s_method
|
||||
from collections.abc import Callable
|
||||
|
||||
RealFunc = Callable[[float], float] # type alias for a real -> real function
|
||||
|
||||
|
||||
# function is the f(x) and derivative is the f'(x)
|
||||
def newton(
|
||||
function: RealFunc,
|
||||
derivative: RealFunc,
|
||||
starting_int: int,
|
||||
) -> float:
|
||||
"""
|
||||
>>> newton(lambda x: x ** 3 - 2 * x - 5, lambda x: 3 * x ** 2 - 2, 3)
|
||||
2.0945514815423474
|
||||
>>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -2)
|
||||
1.0
|
||||
>>> newton(lambda x: x ** 3 - 1, lambda x: 3 * x ** 2, -4)
|
||||
1.0000000000000102
|
||||
>>> import math
|
||||
>>> newton(math.sin, math.cos, 1)
|
||||
0.0
|
||||
>>> newton(math.sin, math.cos, 2)
|
||||
3.141592653589793
|
||||
>>> newton(math.cos, lambda x: -math.sin(x), 2)
|
||||
1.5707963267948966
|
||||
>>> newton(math.cos, lambda x: -math.sin(x), 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: Could not find root
|
||||
"""
|
||||
prev_guess = float(starting_int)
|
||||
while True:
|
||||
try:
|
||||
next_guess = prev_guess - function(prev_guess) / derivative(prev_guess)
|
||||
except ZeroDivisionError:
|
||||
raise ZeroDivisionError("Could not find root") from None
|
||||
if abs(prev_guess - next_guess) < 10**-5:
|
||||
return next_guess
|
||||
prev_guess = next_guess
|
||||
|
||||
|
||||
def f(x: float) -> float:
|
||||
return (x**3) - (2 * x) - 5
|
||||
|
||||
|
||||
def f1(x: float) -> float:
|
||||
return 3 * (x**2) - 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(newton(f, f1, 3))
|
|
@ -1,46 +0,0 @@
|
|||
# Implementing Newton Raphson method in Python
|
||||
# Author: Syed Haseeb Shah (github.com/QuantumNovice)
|
||||
# The Newton-Raphson method (also known as Newton's method) is a way to
|
||||
# quickly find a good approximation for the root of a real-valued function
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
from math import * # noqa: F403
|
||||
|
||||
from sympy import diff
|
||||
|
||||
|
||||
def newton_raphson(
|
||||
func: str, a: float | Decimal, precision: float = 10**-10
|
||||
) -> float:
|
||||
"""Finds root from the point 'a' onwards by Newton-Raphson method
|
||||
>>> newton_raphson("sin(x)", 2)
|
||||
3.1415926536808043
|
||||
>>> newton_raphson("x**2 - 5*x +2", 0.4)
|
||||
0.4384471871911695
|
||||
>>> newton_raphson("x**2 - 5", 0.1)
|
||||
2.23606797749979
|
||||
>>> newton_raphson("log(x)- 1", 2)
|
||||
2.718281828458938
|
||||
"""
|
||||
x = a
|
||||
while True:
|
||||
x = Decimal(x) - (
|
||||
Decimal(eval(func)) / Decimal(eval(str(diff(func)))) # noqa: S307
|
||||
)
|
||||
# This number dictates the accuracy of the answer
|
||||
if abs(eval(func)) < precision: # noqa: S307
|
||||
return float(x)
|
||||
|
||||
|
||||
# Let's Execute
|
||||
if __name__ == "__main__":
|
||||
# Find root of trigonometric function
|
||||
# Find value of pi
|
||||
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
|
||||
# Find root of polynomial
|
||||
print(f"The root of x**2 - 5*x + 2 = 0 is {newton_raphson('x**2 - 5*x + 2', 0.4)}")
|
||||
# Find Square Root of 5
|
||||
print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}")
|
||||
# Exponential Roots
|
||||
print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}")
|
|
@ -1,83 +0,0 @@
|
|||
# Implementing Newton Raphson method in Python
|
||||
# Author: Saksham Gupta
|
||||
#
|
||||
# The Newton-Raphson method (also known as Newton's method) is a way to
|
||||
# quickly find a good approximation for the root of a functreal-valued ion
|
||||
# The method can also be extended to complex functions
|
||||
#
|
||||
# Newton's Method - https://en.wikipedia.org/wiki/Newton's_method
|
||||
|
||||
from sympy import diff, lambdify, symbols
|
||||
from sympy.functions import * # noqa: F403
|
||||
|
||||
|
||||
def newton_raphson(
|
||||
function: str,
|
||||
starting_point: complex,
|
||||
variable: str = "x",
|
||||
precision: float = 10**-10,
|
||||
multiplicity: int = 1,
|
||||
) -> complex:
|
||||
"""Finds root from the 'starting_point' onwards by Newton-Raphson method
|
||||
Refer to https://docs.sympy.org/latest/modules/functions/index.html
|
||||
for usable mathematical functions
|
||||
|
||||
>>> newton_raphson("sin(x)", 2)
|
||||
3.141592653589793
|
||||
>>> newton_raphson("x**4 -5", 0.4 + 5j)
|
||||
(-7.52316384526264e-37+1.4953487812212207j)
|
||||
>>> newton_raphson('log(y) - 1', 2, variable='y')
|
||||
2.7182818284590455
|
||||
>>> newton_raphson('exp(x) - 1', 10, precision=0.005)
|
||||
1.2186556186174883e-10
|
||||
>>> newton_raphson('cos(x)', 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: Could not find root
|
||||
"""
|
||||
|
||||
x = symbols(variable)
|
||||
func = lambdify(x, function)
|
||||
diff_function = lambdify(x, diff(function, x))
|
||||
|
||||
prev_guess = starting_point
|
||||
|
||||
while True:
|
||||
if diff_function(prev_guess) != 0:
|
||||
next_guess = prev_guess - multiplicity * func(prev_guess) / diff_function(
|
||||
prev_guess
|
||||
)
|
||||
else:
|
||||
raise ZeroDivisionError("Could not find root") from None
|
||||
|
||||
# Precision is checked by comparing the difference of consecutive guesses
|
||||
if abs(next_guess - prev_guess) < precision:
|
||||
return next_guess
|
||||
|
||||
prev_guess = next_guess
|
||||
|
||||
|
||||
# Let's Execute
|
||||
if __name__ == "__main__":
|
||||
# Find root of trigonometric function
|
||||
# Find value of pi
|
||||
print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
|
||||
|
||||
# Find root of polynomial
|
||||
# Find fourth Root of 5
|
||||
print(f"The root of x**4 - 5 = 0 is {newton_raphson('x**4 -5', 0.4 +5j)}")
|
||||
|
||||
# Find value of e
|
||||
print(
|
||||
"The root of log(y) - 1 = 0 is ",
|
||||
f"{newton_raphson('log(y) - 1', 2, variable='y')}",
|
||||
)
|
||||
|
||||
# Exponential Roots
|
||||
print(
|
||||
"The root of exp(x) - 1 = 0 is",
|
||||
f"{newton_raphson('exp(x) - 1', 10, precision=0.005)}",
|
||||
)
|
||||
|
||||
# Find root of cos(x)
|
||||
print(f"The root of cos(x) = 0 is {newton_raphson('cos(x)', 0)}")
|
|
@ -1,16 +1,47 @@
|
|||
"""
|
||||
In this problem, we want to determine all possible combinations of k
|
||||
numbers out of 1 ... n. We use backtracking to solve this problem.
|
||||
Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!)))
|
||||
|
||||
Time complexity: O(C(n,k)) which is O(n choose k) = O((n!/(k! * (n - k)!))),
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from itertools import combinations
|
||||
|
||||
|
||||
def combination_lists(n: int, k: int) -> list[list[int]]:
|
||||
"""
|
||||
>>> combination_lists(n=4, k=2)
|
||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
||||
"""
|
||||
return [list(x) for x in combinations(range(1, n + 1), k)]
|
||||
|
||||
|
||||
def generate_all_combinations(n: int, k: int) -> list[list[int]]:
|
||||
"""
|
||||
>>> generate_all_combinations(n=4, k=2)
|
||||
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
|
||||
>>> generate_all_combinations(n=0, k=0)
|
||||
[[]]
|
||||
>>> generate_all_combinations(n=10, k=-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: k must not be negative
|
||||
>>> generate_all_combinations(n=-1, k=10)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: n must not be negative
|
||||
>>> generate_all_combinations(n=5, k=4)
|
||||
[[1, 2, 3, 4], [1, 2, 3, 5], [1, 2, 4, 5], [1, 3, 4, 5], [2, 3, 4, 5]]
|
||||
>>> from itertools import combinations
|
||||
>>> all(generate_all_combinations(n, k) == combination_lists(n, k)
|
||||
... for n in range(1, 6) for k in range(1, 6))
|
||||
True
|
||||
"""
|
||||
if k < 0:
|
||||
raise ValueError("k must not be negative")
|
||||
if n < 0:
|
||||
raise ValueError("n must not be negative")
|
||||
|
||||
result: list[list[int]] = []
|
||||
create_all_state(1, n, k, [], result)
|
||||
|
@ -34,13 +65,17 @@ def create_all_state(
|
|||
current_list.pop()
|
||||
|
||||
|
||||
def print_all_state(total_list: list[list[int]]) -> None:
|
||||
for i in total_list:
|
||||
print(*i)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
n = 4
|
||||
k = 2
|
||||
total_list = generate_all_combinations(n, k)
|
||||
print_all_state(total_list)
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
print(generate_all_combinations(n=4, k=2))
|
||||
tests = ((n, k) for n in range(1, 5) for k in range(1, 5))
|
||||
for n, k in tests:
|
||||
print(n, k, generate_all_combinations(n, k) == combination_lists(n, k))
|
||||
|
||||
print("Benchmark:")
|
||||
from timeit import timeit
|
||||
|
||||
for func in ("combination_lists", "generate_all_combinations"):
|
||||
print(f"{func:>25}(): {timeit(f'{func}(n=4, k = 2)', globals=globals())}")
|
||||
|
|
132
backtracking/crossword_puzzle_solver.py
Normal file
132
backtracking/crossword_puzzle_solver.py
Normal file
|
@ -0,0 +1,132 @@
|
|||
# https://www.geeksforgeeks.org/solve-crossword-puzzle/
|
||||
|
||||
|
||||
def is_valid(
|
||||
puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a word can be placed at the given position.
|
||||
|
||||
>>> puzzle = [
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', '']
|
||||
... ]
|
||||
>>> is_valid(puzzle, 'word', 0, 0, True)
|
||||
True
|
||||
>>> puzzle = [
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', '']
|
||||
... ]
|
||||
>>> is_valid(puzzle, 'word', 0, 0, False)
|
||||
True
|
||||
"""
|
||||
for i in range(len(word)):
|
||||
if vertical:
|
||||
if row + i >= len(puzzle) or puzzle[row + i][col] != "":
|
||||
return False
|
||||
else:
|
||||
if col + i >= len(puzzle[0]) or puzzle[row][col + i] != "":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def place_word(
|
||||
puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool
|
||||
) -> None:
|
||||
"""
|
||||
Place a word at the given position.
|
||||
|
||||
>>> puzzle = [
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', '']
|
||||
... ]
|
||||
>>> place_word(puzzle, 'word', 0, 0, True)
|
||||
>>> puzzle
|
||||
[['w', '', '', ''], ['o', '', '', ''], ['r', '', '', ''], ['d', '', '', '']]
|
||||
"""
|
||||
for i, char in enumerate(word):
|
||||
if vertical:
|
||||
puzzle[row + i][col] = char
|
||||
else:
|
||||
puzzle[row][col + i] = char
|
||||
|
||||
|
||||
def remove_word(
|
||||
puzzle: list[list[str]], word: str, row: int, col: int, vertical: bool
|
||||
) -> None:
|
||||
"""
|
||||
Remove a word from the given position.
|
||||
|
||||
>>> puzzle = [
|
||||
... ['w', '', '', ''],
|
||||
... ['o', '', '', ''],
|
||||
... ['r', '', '', ''],
|
||||
... ['d', '', '', '']
|
||||
... ]
|
||||
>>> remove_word(puzzle, 'word', 0, 0, True)
|
||||
>>> puzzle
|
||||
[['', '', '', ''], ['', '', '', ''], ['', '', '', ''], ['', '', '', '']]
|
||||
"""
|
||||
for i in range(len(word)):
|
||||
if vertical:
|
||||
puzzle[row + i][col] = ""
|
||||
else:
|
||||
puzzle[row][col + i] = ""
|
||||
|
||||
|
||||
def solve_crossword(puzzle: list[list[str]], words: list[str]) -> bool:
|
||||
"""
|
||||
Solve the crossword puzzle using backtracking.
|
||||
|
||||
>>> puzzle = [
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', '']
|
||||
... ]
|
||||
|
||||
>>> words = ['word', 'four', 'more', 'last']
|
||||
>>> solve_crossword(puzzle, words)
|
||||
True
|
||||
>>> puzzle = [
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', ''],
|
||||
... ['', '', '', '']
|
||||
... ]
|
||||
>>> words = ['word', 'four', 'more', 'paragraphs']
|
||||
>>> solve_crossword(puzzle, words)
|
||||
False
|
||||
"""
|
||||
for row in range(len(puzzle)):
|
||||
for col in range(len(puzzle[0])):
|
||||
if puzzle[row][col] == "":
|
||||
for word in words:
|
||||
for vertical in [True, False]:
|
||||
if is_valid(puzzle, word, row, col, vertical):
|
||||
place_word(puzzle, word, row, col, vertical)
|
||||
words.remove(word)
|
||||
if solve_crossword(puzzle, words):
|
||||
return True
|
||||
words.append(word)
|
||||
remove_word(puzzle, word, row, col, vertical)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
PUZZLE = [[""] * 3 for _ in range(3)]
|
||||
WORDS = ["cat", "dog", "car"]
|
||||
|
||||
if solve_crossword(PUZZLE, WORDS):
|
||||
print("Solution found:")
|
||||
for row in PUZZLE:
|
||||
print(" ".join(row))
|
||||
else:
|
||||
print("No solution found:")
|
77
backtracking/generate_parentheses.py
Normal file
77
backtracking/generate_parentheses.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""
|
||||
author: Aayush Soni
|
||||
Given n pairs of parentheses, write a function to generate all
|
||||
combinations of well-formed parentheses.
|
||||
Input: n = 2
|
||||
Output: ["(())","()()"]
|
||||
Leetcode link: https://leetcode.com/problems/generate-parentheses/description/
|
||||
"""
|
||||
|
||||
|
||||
def backtrack(
|
||||
partial: str, open_count: int, close_count: int, n: int, result: list[str]
|
||||
) -> None:
|
||||
"""
|
||||
Generate valid combinations of balanced parentheses using recursion.
|
||||
|
||||
:param partial: A string representing the current combination.
|
||||
:param open_count: An integer representing the count of open parentheses.
|
||||
:param close_count: An integer representing the count of close parentheses.
|
||||
:param n: An integer representing the total number of pairs.
|
||||
:param result: A list to store valid combinations.
|
||||
:return: None
|
||||
|
||||
This function uses recursion to explore all possible combinations,
|
||||
ensuring that at each step, the parentheses remain balanced.
|
||||
|
||||
Example:
|
||||
>>> result = []
|
||||
>>> backtrack("", 0, 0, 2, result)
|
||||
>>> result
|
||||
['(())', '()()']
|
||||
"""
|
||||
if len(partial) == 2 * n:
|
||||
# When the combination is complete, add it to the result.
|
||||
result.append(partial)
|
||||
return
|
||||
|
||||
if open_count < n:
|
||||
# If we can add an open parenthesis, do so, and recurse.
|
||||
backtrack(partial + "(", open_count + 1, close_count, n, result)
|
||||
|
||||
if close_count < open_count:
|
||||
# If we can add a close parenthesis (it won't make the combination invalid),
|
||||
# do so, and recurse.
|
||||
backtrack(partial + ")", open_count, close_count + 1, n, result)
|
||||
|
||||
|
||||
def generate_parenthesis(n: int) -> list[str]:
|
||||
"""
|
||||
Generate valid combinations of balanced parentheses for a given n.
|
||||
|
||||
:param n: An integer representing the number of pairs of parentheses.
|
||||
:return: A list of strings with valid combinations.
|
||||
|
||||
This function uses a recursive approach to generate the combinations.
|
||||
|
||||
Time Complexity: O(2^(2n)) - In the worst case, we have 2^(2n) combinations.
|
||||
Space Complexity: O(n) - where 'n' is the number of pairs.
|
||||
|
||||
Example 1:
|
||||
>>> generate_parenthesis(3)
|
||||
['((()))', '(()())', '(())()', '()(())', '()()()']
|
||||
|
||||
Example 2:
|
||||
>>> generate_parenthesis(1)
|
||||
['()']
|
||||
"""
|
||||
|
||||
result: list[str] = []
|
||||
backtrack("", 0, 0, n, result)
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -79,7 +79,7 @@ def open_knight_tour(n: int) -> list[list[int]]:
|
|||
>>> open_knight_tour(2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Open Kight Tour cannot be performed on a board of size 2
|
||||
ValueError: Open Knight Tour cannot be performed on a board of size 2
|
||||
"""
|
||||
|
||||
board = [[0 for i in range(n)] for j in range(n)]
|
||||
|
@ -91,7 +91,7 @@ def open_knight_tour(n: int) -> list[list[int]]:
|
|||
return board
|
||||
board[i][j] = 0
|
||||
|
||||
msg = f"Open Kight Tour cannot be performed on a board of size {n}"
|
||||
msg = f"Open Knight Tour cannot be performed on a board of size {n}"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
|
|
61
backtracking/match_word_pattern.py
Normal file
61
backtracking/match_word_pattern.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
def match_word_pattern(pattern: str, input_string: str) -> bool:
|
||||
"""
|
||||
Determine if a given pattern matches a string using backtracking.
|
||||
|
||||
pattern: The pattern to match.
|
||||
input_string: The string to match against the pattern.
|
||||
return: True if the pattern matches the string, False otherwise.
|
||||
|
||||
>>> match_word_pattern("aba", "GraphTreesGraph")
|
||||
True
|
||||
|
||||
>>> match_word_pattern("xyx", "PythonRubyPython")
|
||||
True
|
||||
|
||||
>>> match_word_pattern("GG", "PythonJavaPython")
|
||||
False
|
||||
"""
|
||||
|
||||
def backtrack(pattern_index: int, str_index: int) -> bool:
|
||||
"""
|
||||
>>> backtrack(0, 0)
|
||||
True
|
||||
|
||||
>>> backtrack(0, 1)
|
||||
True
|
||||
|
||||
>>> backtrack(0, 4)
|
||||
False
|
||||
"""
|
||||
if pattern_index == len(pattern) and str_index == len(input_string):
|
||||
return True
|
||||
if pattern_index == len(pattern) or str_index == len(input_string):
|
||||
return False
|
||||
char = pattern[pattern_index]
|
||||
if char in pattern_map:
|
||||
mapped_str = pattern_map[char]
|
||||
if input_string.startswith(mapped_str, str_index):
|
||||
return backtrack(pattern_index + 1, str_index + len(mapped_str))
|
||||
else:
|
||||
return False
|
||||
for end in range(str_index + 1, len(input_string) + 1):
|
||||
substr = input_string[str_index:end]
|
||||
if substr in str_map:
|
||||
continue
|
||||
pattern_map[char] = substr
|
||||
str_map[substr] = char
|
||||
if backtrack(pattern_index + 1, end):
|
||||
return True
|
||||
del pattern_map[char]
|
||||
del str_map[substr]
|
||||
return False
|
||||
|
||||
pattern_map: dict[str, str] = {}
|
||||
str_map: dict[str, str] = {}
|
||||
return backtrack(0, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -16,6 +16,22 @@ def minimax(
|
|||
depth: int, node_index: int, is_max: bool, scores: list[int], height: float
|
||||
) -> int:
|
||||
"""
|
||||
This function implements the minimax algorithm, which helps achieve the optimal
|
||||
score for a player in a two-player game by checking all possible moves.
|
||||
If the player is the maximizer, then the score is maximized.
|
||||
If the player is the minimizer, then the score is minimized.
|
||||
|
||||
Parameters:
|
||||
- depth: Current depth in the game tree.
|
||||
- node_index: Index of the current node in the scores list.
|
||||
- is_max: A boolean indicating whether the current move
|
||||
is for the maximizer (True) or minimizer (False).
|
||||
- scores: A list containing the scores of the leaves of the game tree.
|
||||
- height: The maximum height of the game tree.
|
||||
|
||||
Returns:
|
||||
- An integer representing the optimal score for the current player.
|
||||
|
||||
>>> import math
|
||||
>>> scores = [90, 23, 6, 33, 21, 65, 123, 34423]
|
||||
>>> height = math.log(len(scores), 2)
|
||||
|
@ -37,19 +53,24 @@ def minimax(
|
|||
|
||||
if depth < 0:
|
||||
raise ValueError("Depth cannot be less than 0")
|
||||
|
||||
if len(scores) == 0:
|
||||
raise ValueError("Scores cannot be empty")
|
||||
|
||||
# Base case: If the current depth equals the height of the tree,
|
||||
# return the score of the current node.
|
||||
if depth == height:
|
||||
return scores[node_index]
|
||||
|
||||
# If it's the maximizer's turn, choose the maximum score
|
||||
# between the two possible moves.
|
||||
if is_max:
|
||||
return max(
|
||||
minimax(depth + 1, node_index * 2, False, scores, height),
|
||||
minimax(depth + 1, node_index * 2 + 1, False, scores, height),
|
||||
)
|
||||
|
||||
# If it's the minimizer's turn, choose the minimum score
|
||||
# between the two possible moves.
|
||||
return min(
|
||||
minimax(depth + 1, node_index * 2, True, scores, height),
|
||||
minimax(depth + 1, node_index * 2 + 1, True, scores, height),
|
||||
|
@ -57,8 +78,11 @@ def minimax(
|
|||
|
||||
|
||||
def main() -> None:
|
||||
# Sample scores and height calculation
|
||||
scores = [90, 23, 6, 33, 21, 65, 123, 34423]
|
||||
height = math.log(len(scores), 2)
|
||||
|
||||
# Calculate and print the optimal value using the minimax algorithm
|
||||
print("Optimal value : ", end="")
|
||||
print(minimax(0, 0, True, scores, height))
|
||||
|
||||
|
|
|
@ -17,40 +17,43 @@ def is_safe(board: list[list[int]], row: int, column: int) -> bool:
|
|||
This function returns a boolean value True if it is safe to place a queen there
|
||||
considering the current state of the board.
|
||||
|
||||
Parameters :
|
||||
board(2D matrix) : board
|
||||
row ,column : coordinates of the cell on a board
|
||||
Parameters:
|
||||
board (2D matrix): The chessboard
|
||||
row, column: Coordinates of the cell on the board
|
||||
|
||||
Returns :
|
||||
Returns:
|
||||
Boolean Value
|
||||
|
||||
>>> is_safe([[0, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
|
||||
True
|
||||
>>> is_safe([[1, 0, 0], [0, 0, 0], [0, 0, 0]], 1, 1)
|
||||
False
|
||||
"""
|
||||
for i in range(len(board)):
|
||||
if board[row][i] == 1:
|
||||
return False
|
||||
for i in range(len(board)):
|
||||
if board[i][column] == 1:
|
||||
return False
|
||||
for i, j in zip(range(row, -1, -1), range(column, -1, -1)):
|
||||
if board[i][j] == 1:
|
||||
return False
|
||||
for i, j in zip(range(row, -1, -1), range(column, len(board))):
|
||||
if board[i][j] == 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
n = len(board) # Size of the board
|
||||
|
||||
# Check if there is any queen in the same row, column,
|
||||
# left upper diagonal, and right upper diagonal
|
||||
return (
|
||||
all(board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, n)))
|
||||
and all(
|
||||
board[i][j] != 1 for i, j in zip(range(row, -1, -1), range(column, -1, -1))
|
||||
)
|
||||
and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, n)))
|
||||
and all(board[i][j] != 1 for i, j in zip(range(row, n), range(column, -1, -1)))
|
||||
)
|
||||
|
||||
|
||||
def solve(board: list[list[int]], row: int) -> bool:
|
||||
"""
|
||||
It creates a state space tree and calls the safe function until it receives a
|
||||
False Boolean and terminates that branch and backtracks to the next
|
||||
This function creates a state space tree and calls the safe function until it
|
||||
receives a False Boolean and terminates that branch and backtracks to the next
|
||||
possible solution branch.
|
||||
"""
|
||||
if row >= len(board):
|
||||
"""
|
||||
If the row number exceeds N we have board with a successful combination
|
||||
If the row number exceeds N, we have a board with a successful combination
|
||||
and that combination is appended to the solution list and the board is printed.
|
||||
|
||||
"""
|
||||
solution.append(board)
|
||||
printboard(board)
|
||||
|
@ -58,9 +61,9 @@ def solve(board: list[list[int]], row: int) -> bool:
|
|||
return True
|
||||
for i in range(len(board)):
|
||||
"""
|
||||
For every row it iterates through each column to check if it is feasible to
|
||||
For every row, it iterates through each column to check if it is feasible to
|
||||
place a queen there.
|
||||
If all the combinations for that particular branch are successful the board is
|
||||
If all the combinations for that particular branch are successful, the board is
|
||||
reinitialized for the next possible combination.
|
||||
"""
|
||||
if is_safe(board, row, i):
|
||||
|
@ -77,14 +80,14 @@ def printboard(board: list[list[int]]) -> None:
|
|||
for i in range(len(board)):
|
||||
for j in range(len(board)):
|
||||
if board[i][j] == 1:
|
||||
print("Q", end=" ")
|
||||
print("Q", end=" ") # Queen is present
|
||||
else:
|
||||
print(".", end=" ")
|
||||
print(".", end=" ") # Empty cell
|
||||
print()
|
||||
|
||||
|
||||
# n=int(input("The no. of queens"))
|
||||
# Number of queens (e.g., n=8 for an 8x8 board)
|
||||
n = 8
|
||||
board = [[0 for i in range(n)] for j in range(n)]
|
||||
solve(board, 0)
|
||||
print("The total no. of solutions are :", len(solution))
|
||||
print("The total number of solutions are:", len(solution))
|
||||
|
|
|
@ -6,8 +6,6 @@ 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,
|
||||
|
@ -19,25 +17,25 @@ def backtrack(
|
|||
"""
|
||||
>>> backtrack(13, 2, 1, 0, 0)
|
||||
(0, 1)
|
||||
>>> backtrack(100, 2, 1, 0, 0)
|
||||
(0, 3)
|
||||
>>> backtrack(100, 3, 1, 0, 0)
|
||||
>>> backtrack(10, 2, 1, 0, 0)
|
||||
(0, 1)
|
||||
>>> backtrack(800, 2, 1, 0, 0)
|
||||
(0, 561)
|
||||
>>> backtrack(1000, 10, 1, 0, 0)
|
||||
>>> backtrack(10, 3, 1, 0, 0)
|
||||
(0, 0)
|
||||
>>> backtrack(400, 2, 1, 0, 0)
|
||||
(0, 55)
|
||||
>>> backtrack(50, 1, 1, 0, 0)
|
||||
(0, 3658)
|
||||
>>> backtrack(20, 2, 1, 0, 0)
|
||||
(0, 1)
|
||||
>>> backtrack(15, 10, 1, 0, 0)
|
||||
(0, 0)
|
||||
>>> backtrack(16, 2, 1, 0, 0)
|
||||
(0, 1)
|
||||
>>> backtrack(20, 1, 1, 0, 0)
|
||||
(0, 64)
|
||||
"""
|
||||
if current_sum == needed_sum:
|
||||
# If the sum of the powers is equal to needed_sum, then we have a solution.
|
||||
solutions_count += 1
|
||||
return current_sum, solutions_count
|
||||
|
||||
i_to_n = int(pow(current_number, power))
|
||||
i_to_n = current_number**power
|
||||
if current_sum + i_to_n <= needed_sum:
|
||||
# If the sum of the powers is less than needed_sum, then continue adding powers.
|
||||
current_sum += i_to_n
|
||||
|
@ -57,17 +55,17 @@ def solve(needed_sum: int, power: int) -> int:
|
|||
"""
|
||||
>>> solve(13, 2)
|
||||
1
|
||||
>>> solve(100, 2)
|
||||
3
|
||||
>>> solve(100, 3)
|
||||
>>> solve(10, 2)
|
||||
1
|
||||
>>> solve(800, 2)
|
||||
561
|
||||
>>> solve(1000, 10)
|
||||
>>> solve(10, 3)
|
||||
0
|
||||
>>> solve(400, 2)
|
||||
55
|
||||
>>> solve(50, 1)
|
||||
>>> solve(20, 2)
|
||||
1
|
||||
>>> solve(15, 10)
|
||||
0
|
||||
>>> solve(16, 2)
|
||||
1
|
||||
>>> solve(20, 1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid input
|
||||
|
|
|
@ -1,91 +1,164 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
def solve_maze(maze: list[list[int]]) -> bool:
|
||||
def solve_maze(
|
||||
maze: list[list[int]],
|
||||
source_row: int,
|
||||
source_column: int,
|
||||
destination_row: int,
|
||||
destination_column: int,
|
||||
) -> list[list[int]]:
|
||||
"""
|
||||
This method solves the "rat in maze" problem.
|
||||
In this problem we have some n by n matrix, a start point and an end point.
|
||||
We want to go from the start to the end. In this matrix zeroes represent walls
|
||||
and ones paths we can use.
|
||||
Parameters :
|
||||
maze(2D matrix) : maze
|
||||
- maze: A two dimensional matrix of zeros and ones.
|
||||
- source_row: The row index of the starting point.
|
||||
- source_column: The column index of the starting point.
|
||||
- destination_row: The row index of the destination point.
|
||||
- destination_column: The column index of the destination point.
|
||||
Returns:
|
||||
Return: True if the maze has a solution or False if it does not.
|
||||
- solution: A 2D matrix representing the solution path if it exists.
|
||||
Raises:
|
||||
- ValueError: If no solution exists or if the source or
|
||||
destination coordinates are invalid.
|
||||
Description:
|
||||
This method navigates through a maze represented as an n by n matrix,
|
||||
starting from a specified source cell and
|
||||
aiming to reach a destination cell.
|
||||
The maze consists of walls (1s) and open paths (0s).
|
||||
By providing custom row and column values, the source and destination
|
||||
cells can be adjusted.
|
||||
>>> maze = [[0, 1, 0, 1, 1],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [1, 0, 1, 0, 1],
|
||||
... [0, 0, 1, 0, 0],
|
||||
... [1, 0, 0, 1, 0]]
|
||||
>>> solve_maze(maze)
|
||||
[1, 0, 0, 0, 0]
|
||||
[1, 1, 1, 1, 0]
|
||||
[0, 0, 0, 1, 0]
|
||||
[0, 0, 0, 1, 1]
|
||||
[0, 0, 0, 0, 1]
|
||||
True
|
||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
||||
[[0, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 0, 1],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 0]]
|
||||
|
||||
Note:
|
||||
In the output maze, the zeros (0s) represent one of the possible
|
||||
paths from the source to the destination.
|
||||
|
||||
>>> maze = [[0, 1, 0, 1, 1],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 1],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0]]
|
||||
>>> solve_maze(maze)
|
||||
[1, 0, 0, 0, 0]
|
||||
[1, 0, 0, 0, 0]
|
||||
[1, 0, 0, 0, 0]
|
||||
[1, 0, 0, 0, 0]
|
||||
[1, 1, 1, 1, 1]
|
||||
True
|
||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
||||
[[0, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0]]
|
||||
|
||||
>>> maze = [[0, 0, 0],
|
||||
... [0, 1, 0],
|
||||
... [1, 0, 0]]
|
||||
>>> solve_maze(maze)
|
||||
[1, 1, 1]
|
||||
[0, 0, 1]
|
||||
[0, 0, 1]
|
||||
True
|
||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
||||
[[0, 0, 0],
|
||||
[1, 1, 0],
|
||||
[1, 1, 0]]
|
||||
|
||||
>>> maze = [[0, 1, 0],
|
||||
>>> maze = [[1, 0, 0],
|
||||
... [0, 1, 0],
|
||||
... [1, 0, 0]]
|
||||
>>> solve_maze(maze)
|
||||
No solution exists!
|
||||
False
|
||||
>>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1) # doctest: +NORMALIZE_WHITESPACE
|
||||
[[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[1, 1, 0]]
|
||||
|
||||
>>> maze = [[1, 1, 0, 0, 1, 0, 0, 1],
|
||||
... [1, 0, 1, 0, 0, 1, 1, 1],
|
||||
... [0, 1, 0, 1, 0, 0, 1, 0],
|
||||
... [1, 1, 1, 0, 0, 1, 0, 1],
|
||||
... [0, 1, 0, 0, 1, 0, 1, 1],
|
||||
... [0, 0, 0, 1, 1, 1, 0, 1],
|
||||
... [0, 1, 0, 1, 0, 1, 1, 1],
|
||||
... [1, 1, 0, 0, 0, 0, 0, 1]]
|
||||
>>> solve_maze(maze,0,2,len(maze)-1,2) # doctest: +NORMALIZE_WHITESPACE
|
||||
[[1, 1, 0, 0, 1, 1, 1, 1],
|
||||
[1, 1, 1, 0, 0, 1, 1, 1],
|
||||
[1, 1, 1, 1, 0, 1, 1, 1],
|
||||
[1, 1, 1, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 1, 1, 1, 1],
|
||||
[1, 1, 0, 1, 1, 1, 1, 1],
|
||||
[1, 1, 0, 1, 1, 1, 1, 1],
|
||||
[1, 1, 0, 1, 1, 1, 1, 1]]
|
||||
>>> maze = [[1, 0, 0],
|
||||
... [0, 1, 1],
|
||||
... [1, 0, 1]]
|
||||
>>> solve_maze(maze,0,1,len(maze)-1,len(maze)-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No solution exists!
|
||||
|
||||
>>> maze = [[0, 0],
|
||||
... [1, 1]]
|
||||
>>> solve_maze(maze,0,0,len(maze)-1,len(maze)-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: No solution exists!
|
||||
|
||||
>>> maze = [[0, 1],
|
||||
... [1, 0]]
|
||||
>>> solve_maze(maze)
|
||||
No solution exists!
|
||||
False
|
||||
>>> solve_maze(maze,2,0,len(maze)-1,len(maze)-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid source or destination coordinates
|
||||
|
||||
>>> maze = [[1, 0, 0],
|
||||
... [0, 1, 0],
|
||||
... [1, 0, 0]]
|
||||
>>> solve_maze(maze,0,1,len(maze),len(maze)-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid source or destination coordinates
|
||||
"""
|
||||
size = len(maze)
|
||||
# Check if source and destination coordinates are Invalid.
|
||||
if not (0 <= source_row <= size - 1 and 0 <= source_column <= size - 1) or (
|
||||
not (0 <= destination_row <= size - 1 and 0 <= destination_column <= size - 1)
|
||||
):
|
||||
raise ValueError("Invalid source or destination coordinates")
|
||||
# We need to create solution object to save path.
|
||||
solutions = [[0 for _ in range(size)] for _ in range(size)]
|
||||
solved = run_maze(maze, 0, 0, solutions)
|
||||
solutions = [[1 for _ in range(size)] for _ in range(size)]
|
||||
solved = run_maze(
|
||||
maze, source_row, source_column, destination_row, destination_column, solutions
|
||||
)
|
||||
if solved:
|
||||
print("\n".join(str(row) for row in solutions))
|
||||
return solutions
|
||||
else:
|
||||
print("No solution exists!")
|
||||
return solved
|
||||
raise ValueError("No solution exists!")
|
||||
|
||||
|
||||
def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]]) -> bool:
|
||||
def run_maze(
|
||||
maze: list[list[int]],
|
||||
i: int,
|
||||
j: int,
|
||||
destination_row: int,
|
||||
destination_column: int,
|
||||
solutions: list[list[int]],
|
||||
) -> bool:
|
||||
"""
|
||||
This method is recursive starting from (i, j) and going in one of four directions:
|
||||
up, down, left, right.
|
||||
If a path is found to destination it returns True otherwise it returns False.
|
||||
Parameters:
|
||||
maze(2D matrix) : maze
|
||||
Parameters
|
||||
maze: A two dimensional matrix of zeros and ones.
|
||||
i, j : coordinates of matrix
|
||||
solutions(2D matrix) : solutions
|
||||
solutions: A two dimensional matrix of solutions.
|
||||
Returns:
|
||||
Boolean if path is found True, Otherwise False.
|
||||
"""
|
||||
size = len(maze)
|
||||
# Final check point.
|
||||
if i == j == (size - 1):
|
||||
solutions[i][j] = 1
|
||||
if i == destination_row and j == destination_column and maze[i][j] == 0:
|
||||
solutions[i][j] = 0
|
||||
return True
|
||||
|
||||
lower_flag = (not i < 0) and (not j < 0) # Check lower bounds
|
||||
|
@ -93,21 +166,27 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]])
|
|||
|
||||
if lower_flag and upper_flag:
|
||||
# check for already visited and block points.
|
||||
block_flag = (not solutions[i][j]) and (not maze[i][j])
|
||||
block_flag = (solutions[i][j]) and (not maze[i][j])
|
||||
if block_flag:
|
||||
# check visited
|
||||
solutions[i][j] = 1
|
||||
solutions[i][j] = 0
|
||||
|
||||
# check for directions
|
||||
if (
|
||||
run_maze(maze, i + 1, j, solutions)
|
||||
or run_maze(maze, i, j + 1, solutions)
|
||||
or run_maze(maze, i - 1, j, solutions)
|
||||
or run_maze(maze, i, j - 1, solutions)
|
||||
run_maze(maze, i + 1, j, destination_row, destination_column, solutions)
|
||||
or run_maze(
|
||||
maze, i, j + 1, destination_row, destination_column, solutions
|
||||
)
|
||||
or run_maze(
|
||||
maze, i - 1, j, destination_row, destination_column, solutions
|
||||
)
|
||||
or run_maze(
|
||||
maze, i, j - 1, destination_row, destination_column, solutions
|
||||
)
|
||||
):
|
||||
return True
|
||||
|
||||
solutions[i][j] = 0
|
||||
solutions[i][j] = 1
|
||||
return False
|
||||
return False
|
||||
|
||||
|
@ -115,4 +194,4 @@ def run_maze(maze: list[list[int]], i: int, j: int, solutions: list[list[int]])
|
|||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
|
||||
|
|
|
@ -98,13 +98,7 @@ def word_exists(board: list[list[str]], word: str) -> bool:
|
|||
False
|
||||
>>> word_exists([["A"]], "A")
|
||||
True
|
||||
>>> word_exists([["A","A","A","A","A","A"],
|
||||
... ["A","A","A","A","A","A"],
|
||||
... ["A","A","A","A","A","A"],
|
||||
... ["A","A","A","A","A","A"],
|
||||
... ["A","A","A","A","A","B"],
|
||||
... ["A","A","A","A","B","A"]],
|
||||
... "AAAAAAAAAAAAABB")
|
||||
>>> word_exists([["B", "A", "A"], ["A", "A", "A"], ["A", "B", "A"]], "ABB")
|
||||
False
|
||||
>>> word_exists([["A"]], 123)
|
||||
Traceback (most recent call last):
|
||||
|
|
29
bit_manipulation/binary_coded_decimal.py
Normal file
29
bit_manipulation/binary_coded_decimal.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
def binary_coded_decimal(number: int) -> str:
|
||||
"""
|
||||
Find binary coded decimal (bcd) of integer base 10.
|
||||
Each digit of the number is represented by a 4-bit binary.
|
||||
Example:
|
||||
>>> binary_coded_decimal(-2)
|
||||
'0b0000'
|
||||
>>> binary_coded_decimal(-1)
|
||||
'0b0000'
|
||||
>>> binary_coded_decimal(0)
|
||||
'0b0000'
|
||||
>>> binary_coded_decimal(3)
|
||||
'0b0011'
|
||||
>>> binary_coded_decimal(2)
|
||||
'0b0010'
|
||||
>>> binary_coded_decimal(12)
|
||||
'0b00010010'
|
||||
>>> binary_coded_decimal(987)
|
||||
'0b100110000111'
|
||||
"""
|
||||
return "0b" + "".join(
|
||||
str(bin(int(digit)))[2:].zfill(4) for digit in str(max(0, number))
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -70,11 +70,13 @@ def benchmark() -> None:
|
|||
setup = "import __main__ as z"
|
||||
print(f"Benchmark when {number = }:")
|
||||
print(f"{get_set_bits_count_using_modulo_operator(number) = }")
|
||||
timing = timeit("z.get_set_bits_count_using_modulo_operator(25)", setup=setup)
|
||||
timing = timeit(
|
||||
f"z.get_set_bits_count_using_modulo_operator({number})", setup=setup
|
||||
)
|
||||
print(f"timeit() runs in {timing} seconds")
|
||||
print(f"{get_set_bits_count_using_brian_kernighans_algorithm(number) = }")
|
||||
timing = timeit(
|
||||
"z.get_set_bits_count_using_brian_kernighans_algorithm(25)",
|
||||
f"z.get_set_bits_count_using_brian_kernighans_algorithm({number})",
|
||||
setup=setup,
|
||||
)
|
||||
print(f"timeit() runs in {timing} seconds")
|
||||
|
|
27
bit_manipulation/excess_3_code.py
Normal file
27
bit_manipulation/excess_3_code.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
def excess_3_code(number: int) -> str:
|
||||
"""
|
||||
Find excess-3 code of integer base 10.
|
||||
Add 3 to all digits in a decimal number then convert to a binary-coded decimal.
|
||||
https://en.wikipedia.org/wiki/Excess-3
|
||||
|
||||
>>> excess_3_code(0)
|
||||
'0b0011'
|
||||
>>> excess_3_code(3)
|
||||
'0b0110'
|
||||
>>> excess_3_code(2)
|
||||
'0b0101'
|
||||
>>> excess_3_code(20)
|
||||
'0b01010011'
|
||||
>>> excess_3_code(120)
|
||||
'0b010001010011'
|
||||
"""
|
||||
num = ""
|
||||
for digit in str(max(0, number)):
|
||||
num += str(bin(int(digit) + 3))[2:].zfill(4)
|
||||
return "0b" + num
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
30
bit_manipulation/find_previous_power_of_two.py
Normal file
30
bit_manipulation/find_previous_power_of_two.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
def find_previous_power_of_two(number: int) -> int:
|
||||
"""
|
||||
Find the largest power of two that is less than or equal to a given integer.
|
||||
https://stackoverflow.com/questions/1322510
|
||||
|
||||
>>> [find_previous_power_of_two(i) for i in range(18)]
|
||||
[0, 1, 2, 2, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16]
|
||||
>>> find_previous_power_of_two(-5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Input must be a non-negative integer
|
||||
>>> find_previous_power_of_two(10.5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Input must be a non-negative integer
|
||||
"""
|
||||
if not isinstance(number, int) or number < 0:
|
||||
raise ValueError("Input must be a non-negative integer")
|
||||
if number == 0:
|
||||
return 0
|
||||
power = 1
|
||||
while power <= number:
|
||||
power <<= 1 # Equivalent to multiplying by 2
|
||||
return power >> 1 if number > 1 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -1,7 +1,7 @@
|
|||
def is_even(number: int) -> bool:
|
||||
"""
|
||||
return true if the input integer is even
|
||||
Explanation: Lets take a look at the following deicmal to binary conversions
|
||||
Explanation: Lets take a look at the following decimal to binary conversions
|
||||
2 => 10
|
||||
14 => 1110
|
||||
100 => 1100100
|
||||
|
|
60
bit_manipulation/largest_pow_of_two_le_num.py
Normal file
60
bit_manipulation/largest_pow_of_two_le_num.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
"""
|
||||
Author : Naman Sharma
|
||||
Date : October 2, 2023
|
||||
|
||||
Task:
|
||||
To Find the largest power of 2 less than or equal to a given number.
|
||||
|
||||
Implementation notes: Use bit manipulation.
|
||||
We start from 1 & left shift the set bit to check if (res<<1)<=number.
|
||||
Each left bit shift represents a pow of 2.
|
||||
|
||||
For example:
|
||||
number: 15
|
||||
res: 1 0b1
|
||||
2 0b10
|
||||
4 0b100
|
||||
8 0b1000
|
||||
16 0b10000 (Exit)
|
||||
"""
|
||||
|
||||
|
||||
def largest_pow_of_two_le_num(number: int) -> int:
|
||||
"""
|
||||
Return the largest power of two less than or equal to a number.
|
||||
|
||||
>>> largest_pow_of_two_le_num(0)
|
||||
0
|
||||
>>> largest_pow_of_two_le_num(1)
|
||||
1
|
||||
>>> largest_pow_of_two_le_num(-1)
|
||||
0
|
||||
>>> largest_pow_of_two_le_num(3)
|
||||
2
|
||||
>>> largest_pow_of_two_le_num(15)
|
||||
8
|
||||
>>> largest_pow_of_two_le_num(99)
|
||||
64
|
||||
>>> largest_pow_of_two_le_num(178)
|
||||
128
|
||||
>>> largest_pow_of_two_le_num(999999)
|
||||
524288
|
||||
>>> largest_pow_of_two_le_num(99.9)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Input value must be a 'int' type
|
||||
"""
|
||||
if isinstance(number, float):
|
||||
raise TypeError("Input value must be a 'int' type")
|
||||
if number <= 0:
|
||||
return 0
|
||||
res = 1
|
||||
while (res << 1) <= number:
|
||||
res <<= 1
|
||||
return res
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -11,11 +11,30 @@ def find_missing_number(nums: list[int]) -> int:
|
|||
Example:
|
||||
>>> find_missing_number([0, 1, 3, 4])
|
||||
2
|
||||
>>> find_missing_number([4, 3, 1, 0])
|
||||
2
|
||||
>>> find_missing_number([-4, -3, -1, 0])
|
||||
-2
|
||||
>>> find_missing_number([-2, 2, 1, 3, 0])
|
||||
-1
|
||||
>>> find_missing_number([1, 3, 4, 5, 6])
|
||||
2
|
||||
>>> find_missing_number([6, 5, 4, 2, 1])
|
||||
3
|
||||
>>> find_missing_number([6, 1, 5, 3, 4])
|
||||
2
|
||||
"""
|
||||
n = len(nums)
|
||||
missing_number = n
|
||||
low = min(nums)
|
||||
high = max(nums)
|
||||
missing_number = high
|
||||
|
||||
for i in range(n):
|
||||
missing_number ^= i ^ nums[i]
|
||||
for i in range(low, high):
|
||||
missing_number ^= i ^ nums[i - low]
|
||||
|
||||
return missing_number
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
67
bit_manipulation/power_of_4.py
Normal file
67
bit_manipulation/power_of_4.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
"""
|
||||
|
||||
Task:
|
||||
Given a positive int number. Return True if this number is power of 4
|
||||
or False otherwise.
|
||||
|
||||
Implementation notes: Use bit manipulation.
|
||||
For example if the number is the power of 2 it's bits representation:
|
||||
n = 0..100..00
|
||||
n - 1 = 0..011..11
|
||||
|
||||
n & (n - 1) - no intersections = 0
|
||||
If the number is a power of 4 then it should be a power of 2
|
||||
and the set bit should be at an odd position.
|
||||
"""
|
||||
|
||||
|
||||
def power_of_4(number: int) -> bool:
|
||||
"""
|
||||
Return True if this number is power of 4 or False otherwise.
|
||||
|
||||
>>> power_of_4(0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: number must be positive
|
||||
>>> power_of_4(1)
|
||||
True
|
||||
>>> power_of_4(2)
|
||||
False
|
||||
>>> power_of_4(4)
|
||||
True
|
||||
>>> power_of_4(6)
|
||||
False
|
||||
>>> power_of_4(8)
|
||||
False
|
||||
>>> power_of_4(17)
|
||||
False
|
||||
>>> power_of_4(64)
|
||||
True
|
||||
>>> power_of_4(-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: number must be positive
|
||||
>>> power_of_4(1.2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: number must be an integer
|
||||
|
||||
"""
|
||||
if not isinstance(number, int):
|
||||
raise TypeError("number must be an integer")
|
||||
if number <= 0:
|
||||
raise ValueError("number must be positive")
|
||||
if number & (number - 1) == 0:
|
||||
c = 0
|
||||
while number:
|
||||
c += 1
|
||||
number >>= 1
|
||||
return c % 2 == 1
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
58
bit_manipulation/swap_all_odd_and_even_bits.py
Normal file
58
bit_manipulation/swap_all_odd_and_even_bits.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
def show_bits(before: int, after: int) -> str:
|
||||
"""
|
||||
>>> print(show_bits(0, 0xFFFF))
|
||||
0: 00000000
|
||||
65535: 1111111111111111
|
||||
"""
|
||||
return f"{before:>5}: {before:08b}\n{after:>5}: {after:08b}"
|
||||
|
||||
|
||||
def swap_odd_even_bits(num: int) -> int:
|
||||
"""
|
||||
1. We use bitwise AND operations to separate the even bits (0, 2, 4, 6, etc.) and
|
||||
odd bits (1, 3, 5, 7, etc.) in the input number.
|
||||
2. We then right-shift the even bits by 1 position and left-shift the odd bits by
|
||||
1 position to swap them.
|
||||
3. Finally, we combine the swapped even and odd bits using a bitwise OR operation
|
||||
to obtain the final result.
|
||||
>>> print(show_bits(0, swap_odd_even_bits(0)))
|
||||
0: 00000000
|
||||
0: 00000000
|
||||
>>> print(show_bits(1, swap_odd_even_bits(1)))
|
||||
1: 00000001
|
||||
2: 00000010
|
||||
>>> print(show_bits(2, swap_odd_even_bits(2)))
|
||||
2: 00000010
|
||||
1: 00000001
|
||||
>>> print(show_bits(3, swap_odd_even_bits(3)))
|
||||
3: 00000011
|
||||
3: 00000011
|
||||
>>> print(show_bits(4, swap_odd_even_bits(4)))
|
||||
4: 00000100
|
||||
8: 00001000
|
||||
>>> print(show_bits(5, swap_odd_even_bits(5)))
|
||||
5: 00000101
|
||||
10: 00001010
|
||||
>>> print(show_bits(6, swap_odd_even_bits(6)))
|
||||
6: 00000110
|
||||
9: 00001001
|
||||
>>> print(show_bits(23, swap_odd_even_bits(23)))
|
||||
23: 00010111
|
||||
43: 00101011
|
||||
"""
|
||||
# Get all even bits - 0xAAAAAAAA is a 32-bit number with all even bits set to 1
|
||||
even_bits = num & 0xAAAAAAAA
|
||||
|
||||
# Get all odd bits - 0x55555555 is a 32-bit number with all odd bits set to 1
|
||||
odd_bits = num & 0x55555555
|
||||
|
||||
# Right shift even bits and left shift odd bits and swap them
|
||||
return even_bits >> 1 | odd_bits << 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
for i in (-1, 0, 1, 2, 3, 4, 23, 24):
|
||||
print(show_bits(i, swap_odd_even_bits(i)), "\n")
|
|
@ -1,11 +1,13 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from maths.greatest_common_divisor import greatest_common_divisor
|
||||
|
||||
|
||||
def diophantine(a: int, b: int, c: int) -> tuple[float, float]:
|
||||
"""
|
||||
Diophantine Equation : Given integers a,b,c ( at least one of a and b != 0), the
|
||||
diophantine equation a*x + b*y = c has a solution (where x and y are integers)
|
||||
iff gcd(a,b) divides c.
|
||||
iff greatest_common_divisor(a,b) divides c.
|
||||
|
||||
GCD ( Greatest Common Divisor ) or HCF ( Highest Common Factor )
|
||||
|
||||
|
@ -22,7 +24,7 @@ def diophantine(a: int, b: int, c: int) -> tuple[float, float]:
|
|||
|
||||
assert (
|
||||
c % greatest_common_divisor(a, b) == 0
|
||||
) # greatest_common_divisor(a,b) function implemented below
|
||||
) # greatest_common_divisor(a,b) is in maths directory
|
||||
(d, x, y) = extended_gcd(a, b) # extended_gcd(a,b) function implemented below
|
||||
r = c / d
|
||||
return (r * x, r * y)
|
||||
|
@ -69,32 +71,6 @@ def diophantine_all_soln(a: int, b: int, c: int, n: int = 2) -> None:
|
|||
print(x, y)
|
||||
|
||||
|
||||
def greatest_common_divisor(a: int, b: int) -> int:
|
||||
"""
|
||||
Euclid's Lemma : d divides a and b, if and only if d divides a-b and b
|
||||
|
||||
Euclid's Algorithm
|
||||
|
||||
>>> greatest_common_divisor(7,5)
|
||||
1
|
||||
|
||||
Note : In number theory, two integers a and b are said to be relatively prime,
|
||||
mutually prime, or co-prime if the only positive integer (factor) that
|
||||
divides both of them is 1 i.e., gcd(a,b) = 1.
|
||||
|
||||
>>> greatest_common_divisor(121, 11)
|
||||
11
|
||||
|
||||
"""
|
||||
if a < b:
|
||||
a, b = b, a
|
||||
|
||||
while a % b != 0:
|
||||
a, b = b, a % b
|
||||
|
||||
return b
|
||||
|
||||
|
||||
def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
|
||||
"""
|
||||
Extended Euclid's Algorithm : If d divides a and b and d = a*x + b*y for integers
|
||||
|
@ -107,7 +83,8 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
|
|||
(1, -2, 3)
|
||||
|
||||
"""
|
||||
assert a >= 0 and b >= 0
|
||||
assert a >= 0
|
||||
assert b >= 0
|
||||
|
||||
if b == 0:
|
||||
d, x, y = a, 1, 0
|
||||
|
@ -116,7 +93,8 @@ def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
|
|||
x = q
|
||||
y = p - q * (a // b)
|
||||
|
||||
assert a % d == 0 and b % d == 0
|
||||
assert a % d == 0
|
||||
assert b % d == 0
|
||||
assert d == a * x + b * y
|
||||
|
||||
return (d, x, y)
|
||||
|
|
|
@ -29,22 +29,10 @@ def and_gate(input_1: int, input_2: int) -> int:
|
|||
>>> and_gate(1, 1)
|
||||
1
|
||||
"""
|
||||
return int((input_1, input_2).count(0) == 0)
|
||||
|
||||
|
||||
def test_and_gate() -> None:
|
||||
"""
|
||||
Tests the and_gate function
|
||||
"""
|
||||
assert and_gate(0, 0) == 0
|
||||
assert and_gate(0, 1) == 0
|
||||
assert and_gate(1, 0) == 0
|
||||
assert and_gate(1, 1) == 1
|
||||
return int(input_1 and input_2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_and_gate()
|
||||
print(and_gate(1, 0))
|
||||
print(and_gate(0, 0))
|
||||
print(and_gate(0, 1))
|
||||
print(and_gate(1, 1))
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
39
boolean_algebra/imply_gate.py
Normal file
39
boolean_algebra/imply_gate.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
An IMPLY Gate is a logic gate in boolean algebra which results to 1 if
|
||||
either input 1 is 0, or if input 1 is 1, then the output is 1 only if input 2 is 1.
|
||||
It is true if input 1 implies input 2.
|
||||
|
||||
Following is the truth table of an IMPLY Gate:
|
||||
------------------------------
|
||||
| Input 1 | Input 2 | Output |
|
||||
------------------------------
|
||||
| 0 | 0 | 1 |
|
||||
| 0 | 1 | 1 |
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 1 |
|
||||
------------------------------
|
||||
|
||||
Refer - https://en.wikipedia.org/wiki/IMPLY_gate
|
||||
"""
|
||||
|
||||
|
||||
def imply_gate(input_1: int, input_2: int) -> int:
|
||||
"""
|
||||
Calculate IMPLY of the input values
|
||||
|
||||
>>> imply_gate(0, 0)
|
||||
1
|
||||
>>> imply_gate(0, 1)
|
||||
1
|
||||
>>> imply_gate(1, 0)
|
||||
0
|
||||
>>> imply_gate(1, 1)
|
||||
1
|
||||
"""
|
||||
return int(input_1 == 0 or input_2 == 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
55
boolean_algebra/karnaugh_map_simplification.py
Normal file
55
boolean_algebra/karnaugh_map_simplification.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
https://en.wikipedia.org/wiki/Karnaugh_map
|
||||
https://www.allaboutcircuits.com/technical-articles/karnaugh-map-boolean-algebraic-simplification-technique
|
||||
"""
|
||||
|
||||
|
||||
def simplify_kmap(kmap: list[list[int]]) -> str:
|
||||
"""
|
||||
Simplify the Karnaugh map.
|
||||
>>> simplify_kmap(kmap=[[0, 1], [1, 1]])
|
||||
"A'B + AB' + AB"
|
||||
>>> simplify_kmap(kmap=[[0, 0], [0, 0]])
|
||||
''
|
||||
>>> simplify_kmap(kmap=[[0, 1], [1, -1]])
|
||||
"A'B + AB' + AB"
|
||||
>>> simplify_kmap(kmap=[[0, 1], [1, 2]])
|
||||
"A'B + AB' + AB"
|
||||
>>> simplify_kmap(kmap=[[0, 1], [1, 1.1]])
|
||||
"A'B + AB' + AB"
|
||||
>>> simplify_kmap(kmap=[[0, 1], [1, 'a']])
|
||||
"A'B + AB' + AB"
|
||||
"""
|
||||
simplified_f = []
|
||||
for a, row in enumerate(kmap):
|
||||
for b, item in enumerate(row):
|
||||
if item:
|
||||
term = ("A" if a else "A'") + ("B" if b else "B'")
|
||||
simplified_f.append(term)
|
||||
return " + ".join(simplified_f)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Main function to create and simplify a K-Map.
|
||||
|
||||
>>> main()
|
||||
[0, 1]
|
||||
[1, 1]
|
||||
Simplified Expression:
|
||||
A'B + AB' + AB
|
||||
"""
|
||||
kmap = [[0, 1], [1, 1]]
|
||||
|
||||
# Manually generate the product of [0, 1] and [0, 1]
|
||||
|
||||
for row in kmap:
|
||||
print(row)
|
||||
|
||||
print("Simplified Expression:")
|
||||
print(simplify_kmap(kmap))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
print(f"{simplify_kmap(kmap=[[0, 1], [1, 1]]) = }")
|
42
boolean_algebra/multiplexer.py
Normal file
42
boolean_algebra/multiplexer.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
def mux(input0: int, input1: int, select: int) -> int:
|
||||
"""
|
||||
Implement a 2-to-1 Multiplexer.
|
||||
|
||||
:param input0: The first input value (0 or 1).
|
||||
:param input1: The second input value (0 or 1).
|
||||
:param select: The select signal (0 or 1) to choose between input0 and input1.
|
||||
:return: The output based on the select signal. input1 if select else input0.
|
||||
|
||||
https://www.electrically4u.com/solved-problems-on-multiplexer
|
||||
https://en.wikipedia.org/wiki/Multiplexer
|
||||
|
||||
>>> mux(0, 1, 0)
|
||||
0
|
||||
>>> mux(0, 1, 1)
|
||||
1
|
||||
>>> mux(1, 0, 0)
|
||||
1
|
||||
>>> mux(1, 0, 1)
|
||||
0
|
||||
>>> mux(2, 1, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Inputs and select signal must be 0 or 1
|
||||
>>> mux(0, -1, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Inputs and select signal must be 0 or 1
|
||||
>>> mux(0, 1, 1.1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Inputs and select signal must be 0 or 1
|
||||
"""
|
||||
if all(i in (0, 1) for i in (input0, input1, select)):
|
||||
return input1 if select else input0
|
||||
raise ValueError("Inputs and select signal must be 0 or 1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -27,21 +27,10 @@ def nand_gate(input_1: int, input_2: int) -> int:
|
|||
>>> nand_gate(1, 1)
|
||||
0
|
||||
"""
|
||||
return int((input_1, input_2).count(0) != 0)
|
||||
|
||||
|
||||
def test_nand_gate() -> None:
|
||||
"""
|
||||
Tests the nand_gate function
|
||||
"""
|
||||
assert nand_gate(0, 0) == 1
|
||||
assert nand_gate(0, 1) == 1
|
||||
assert nand_gate(1, 0) == 1
|
||||
assert nand_gate(1, 1) == 0
|
||||
return int(not (input_1 and input_2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(nand_gate(0, 0))
|
||||
print(nand_gate(0, 1))
|
||||
print(nand_gate(1, 0))
|
||||
print(nand_gate(1, 1))
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
39
boolean_algebra/nimply_gate.py
Normal file
39
boolean_algebra/nimply_gate.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
"""
|
||||
An NIMPLY Gate is a logic gate in boolean algebra which results to 0 if
|
||||
either input 1 is 0, or if input 1 is 1, then it is 0 only if input 2 is 1.
|
||||
It is false if input 1 implies input 2. It is the negated form of imply
|
||||
|
||||
Following is the truth table of an NIMPLY Gate:
|
||||
------------------------------
|
||||
| Input 1 | Input 2 | Output |
|
||||
------------------------------
|
||||
| 0 | 0 | 0 |
|
||||
| 0 | 1 | 0 |
|
||||
| 1 | 0 | 1 |
|
||||
| 1 | 1 | 0 |
|
||||
------------------------------
|
||||
|
||||
Refer - https://en.wikipedia.org/wiki/NIMPLY_gate
|
||||
"""
|
||||
|
||||
|
||||
def nimply_gate(input_1: int, input_2: int) -> int:
|
||||
"""
|
||||
Calculate NIMPLY of the input values
|
||||
|
||||
>>> nimply_gate(0, 0)
|
||||
0
|
||||
>>> nimply_gate(0, 1)
|
||||
0
|
||||
>>> nimply_gate(1, 0)
|
||||
1
|
||||
>>> nimply_gate(1, 1)
|
||||
0
|
||||
"""
|
||||
return int(input_1 == 1 and input_2 == 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -1,15 +1,18 @@
|
|||
"""
|
||||
A NOR Gate is a logic gate in boolean algebra which results to false(0)
|
||||
if any of the input is 1, and True(1) if both the inputs are 0.
|
||||
A NOR Gate is a logic gate in boolean algebra which results in false(0) if any of the
|
||||
inputs is 1, and True(1) if all inputs are 0.
|
||||
Following is the truth table of a NOR Gate:
|
||||
| Input 1 | Input 2 | Output |
|
||||
| 0 | 0 | 1 |
|
||||
| 0 | 1 | 0 |
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 0 |
|
||||
Truth Table of NOR Gate:
|
||||
| Input 1 | Input 2 | Output |
|
||||
| 0 | 0 | 1 |
|
||||
| 0 | 1 | 0 |
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 0 |
|
||||
|
||||
Following is the code implementation of the NOR Gate
|
||||
Code provided by Akshaj Vishwanathan
|
||||
https://www.geeksforgeeks.org/logic-gates-in-python
|
||||
"""
|
||||
from collections.abc import Callable
|
||||
|
||||
|
||||
def nor_gate(input_1: int, input_2: int) -> int:
|
||||
|
@ -30,19 +33,35 @@ def nor_gate(input_1: int, input_2: int) -> int:
|
|||
return int(input_1 == input_2 == 0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("Truth Table of NOR Gate:")
|
||||
print("| Input 1 | Input 2 | Output |")
|
||||
print(f"| 0 | 0 | {nor_gate(0, 0)} |")
|
||||
print(f"| 0 | 1 | {nor_gate(0, 1)} |")
|
||||
print(f"| 1 | 0 | {nor_gate(1, 0)} |")
|
||||
print(f"| 1 | 1 | {nor_gate(1, 1)} |")
|
||||
def truth_table(func: Callable) -> str:
|
||||
"""
|
||||
>>> print(truth_table(nor_gate))
|
||||
Truth Table of NOR Gate:
|
||||
| Input 1 | Input 2 | Output |
|
||||
| 0 | 0 | 1 |
|
||||
| 0 | 1 | 0 |
|
||||
| 1 | 0 | 0 |
|
||||
| 1 | 1 | 0 |
|
||||
"""
|
||||
|
||||
def make_table_row(items: list | tuple) -> str:
|
||||
"""
|
||||
>>> make_table_row(("One", "Two", "Three"))
|
||||
'| One | Two | Three |'
|
||||
"""
|
||||
return f"| {' | '.join(f'{item:^8}' for item in items)} |"
|
||||
|
||||
return "\n".join(
|
||||
(
|
||||
"Truth Table of NOR Gate:",
|
||||
make_table_row(("Input 1", "Input 2", "Output")),
|
||||
*[make_table_row((i, j, func(i, j))) for i in (0, 1) for j in (0, 1)],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
main()
|
||||
"""Code provided by Akshaj Vishwanathan"""
|
||||
"""Reference: https://www.geeksforgeeks.org/logic-gates-in-python/"""
|
||||
print(truth_table(nor_gate))
|
||||
|
|
|
@ -24,14 +24,7 @@ def not_gate(input_1: int) -> int:
|
|||
return 1 if input_1 == 0 else 0
|
||||
|
||||
|
||||
def test_not_gate() -> None:
|
||||
"""
|
||||
Tests the not_gate function
|
||||
"""
|
||||
assert not_gate(0) == 1
|
||||
assert not_gate(1) == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(not_gate(0))
|
||||
print(not_gate(1))
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -29,18 +29,7 @@ def or_gate(input_1: int, input_2: int) -> int:
|
|||
return int((input_1, input_2).count(1) != 0)
|
||||
|
||||
|
||||
def test_or_gate() -> None:
|
||||
"""
|
||||
Tests the or_gate function
|
||||
"""
|
||||
assert or_gate(0, 0) == 0
|
||||
assert or_gate(0, 1) == 1
|
||||
assert or_gate(1, 0) == 1
|
||||
assert or_gate(1, 1) == 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(or_gate(0, 1))
|
||||
print(or_gate(1, 0))
|
||||
print(or_gate(0, 0))
|
||||
print(or_gate(1, 1))
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -31,18 +31,7 @@ def xnor_gate(input_1: int, input_2: int) -> int:
|
|||
return 1 if input_1 == input_2 else 0
|
||||
|
||||
|
||||
def test_xnor_gate() -> None:
|
||||
"""
|
||||
Tests the xnor_gate function
|
||||
"""
|
||||
assert xnor_gate(0, 0) == 1
|
||||
assert xnor_gate(0, 1) == 0
|
||||
assert xnor_gate(1, 0) == 0
|
||||
assert xnor_gate(1, 1) == 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(xnor_gate(0, 0))
|
||||
print(xnor_gate(0, 1))
|
||||
print(xnor_gate(1, 0))
|
||||
print(xnor_gate(1, 1))
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -31,16 +31,7 @@ def xor_gate(input_1: int, input_2: int) -> int:
|
|||
return (input_1, input_2).count(0) % 2
|
||||
|
||||
|
||||
def test_xor_gate() -> None:
|
||||
"""
|
||||
Tests the xor_gate function
|
||||
"""
|
||||
assert xor_gate(0, 0) == 0
|
||||
assert xor_gate(0, 1) == 1
|
||||
assert xor_gate(1, 0) == 1
|
||||
assert xor_gate(1, 1) == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(xor_gate(0, 0))
|
||||
print(xor_gate(0, 1))
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import random
|
||||
import sys
|
||||
|
||||
from maths.greatest_common_divisor import gcd_by_iterative
|
||||
|
||||
from . import cryptomath_module as cryptomath
|
||||
|
||||
SYMBOLS = (
|
||||
|
@ -26,7 +28,7 @@ def check_keys(key_a: int, key_b: int, mode: str) -> None:
|
|||
"Key A must be greater than 0 and key B must "
|
||||
f"be between 0 and {len(SYMBOLS) - 1}."
|
||||
)
|
||||
if cryptomath.gcd(key_a, len(SYMBOLS)) != 1:
|
||||
if gcd_by_iterative(key_a, len(SYMBOLS)) != 1:
|
||||
sys.exit(
|
||||
f"Key A {key_a} and the symbol set size {len(SYMBOLS)} "
|
||||
"are not relatively prime. Choose a different key."
|
||||
|
@ -76,7 +78,7 @@ def get_random_key() -> int:
|
|||
while True:
|
||||
key_b = random.randint(2, len(SYMBOLS))
|
||||
key_b = random.randint(2, len(SYMBOLS))
|
||||
if cryptomath.gcd(key_b, len(SYMBOLS)) == 1 and key_b % len(SYMBOLS) != 0:
|
||||
if gcd_by_iterative(key_b, len(SYMBOLS)) == 1 and key_b % len(SYMBOLS) != 0:
|
||||
return key_b * len(SYMBOLS) + key_b
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
def gcd(a: int, b: int) -> int:
|
||||
while a != 0:
|
||||
a, b = b % a, a
|
||||
return b
|
||||
from maths.greatest_common_divisor import gcd_by_iterative
|
||||
|
||||
|
||||
def find_mod_inverse(a: int, m: int) -> int:
|
||||
if gcd(a, m) != 1:
|
||||
if gcd_by_iterative(a, m) != 1:
|
||||
msg = f"mod inverse of {a!r} and {m!r} does not exist"
|
||||
raise ValueError(msg)
|
||||
u1, u2, u3 = 1, 0, a
|
||||
|
|
|
@ -1,11 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
|
||||
def find_primitive(n: int) -> int | None:
|
||||
for r in range(1, n):
|
||||
def find_primitive(modulus: int) -> int | None:
|
||||
"""
|
||||
Find a primitive root modulo modulus, if one exists.
|
||||
|
||||
Args:
|
||||
modulus : The modulus for which to find a primitive root.
|
||||
|
||||
Returns:
|
||||
The primitive root if one exists, or None if there is none.
|
||||
|
||||
Examples:
|
||||
>>> find_primitive(7) # Modulo 7 has primitive root 3
|
||||
3
|
||||
>>> find_primitive(11) # Modulo 11 has primitive root 2
|
||||
2
|
||||
>>> find_primitive(8) == None # Modulo 8 has no primitive root
|
||||
True
|
||||
"""
|
||||
for r in range(1, modulus):
|
||||
li = []
|
||||
for x in range(n - 1):
|
||||
val = pow(r, x, n)
|
||||
for x in range(modulus - 1):
|
||||
val = pow(r, x, modulus)
|
||||
if val in li:
|
||||
break
|
||||
li.append(val)
|
||||
|
@ -15,18 +32,22 @@ def find_primitive(n: int) -> int | None:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
q = int(input("Enter a prime number q: "))
|
||||
a = find_primitive(q)
|
||||
if a is None:
|
||||
print(f"Cannot find the primitive for the value: {a!r}")
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
prime = int(input("Enter a prime number q: "))
|
||||
primitive_root = find_primitive(prime)
|
||||
if primitive_root is None:
|
||||
print(f"Cannot find the primitive for the value: {primitive_root!r}")
|
||||
else:
|
||||
a_private = int(input("Enter private key of A: "))
|
||||
a_public = pow(a, a_private, q)
|
||||
a_public = pow(primitive_root, a_private, prime)
|
||||
b_private = int(input("Enter private key of B: "))
|
||||
b_public = pow(a, b_private, q)
|
||||
b_public = pow(primitive_root, b_private, prime)
|
||||
|
||||
a_secret = pow(b_public, a_private, q)
|
||||
b_secret = pow(a_public, b_private, q)
|
||||
a_secret = pow(b_public, a_private, prime)
|
||||
b_secret = pow(a_public, b_private, prime)
|
||||
|
||||
print("The key value generated by A is: ", a_secret)
|
||||
print("The key value generated by B is: ", b_secret)
|
||||
|
|
167
ciphers/fractionated_morse_cipher.py
Normal file
167
ciphers/fractionated_morse_cipher.py
Normal file
|
@ -0,0 +1,167 @@
|
|||
"""
|
||||
Python program for the Fractionated Morse Cipher.
|
||||
|
||||
The Fractionated Morse cipher first converts the plaintext to Morse code,
|
||||
then enciphers fixed-size blocks of Morse code back to letters.
|
||||
This procedure means plaintext letters are mixed into the ciphertext letters,
|
||||
making it more secure than substitution ciphers.
|
||||
|
||||
http://practicalcryptography.com/ciphers/fractionated-morse-cipher/
|
||||
"""
|
||||
import string
|
||||
|
||||
MORSE_CODE_DICT = {
|
||||
"A": ".-",
|
||||
"B": "-...",
|
||||
"C": "-.-.",
|
||||
"D": "-..",
|
||||
"E": ".",
|
||||
"F": "..-.",
|
||||
"G": "--.",
|
||||
"H": "....",
|
||||
"I": "..",
|
||||
"J": ".---",
|
||||
"K": "-.-",
|
||||
"L": ".-..",
|
||||
"M": "--",
|
||||
"N": "-.",
|
||||
"O": "---",
|
||||
"P": ".--.",
|
||||
"Q": "--.-",
|
||||
"R": ".-.",
|
||||
"S": "...",
|
||||
"T": "-",
|
||||
"U": "..-",
|
||||
"V": "...-",
|
||||
"W": ".--",
|
||||
"X": "-..-",
|
||||
"Y": "-.--",
|
||||
"Z": "--..",
|
||||
" ": "",
|
||||
}
|
||||
|
||||
# Define possible trigrams of Morse code
|
||||
MORSE_COMBINATIONS = [
|
||||
"...",
|
||||
"..-",
|
||||
"..x",
|
||||
".-.",
|
||||
".--",
|
||||
".-x",
|
||||
".x.",
|
||||
".x-",
|
||||
".xx",
|
||||
"-..",
|
||||
"-.-",
|
||||
"-.x",
|
||||
"--.",
|
||||
"---",
|
||||
"--x",
|
||||
"-x.",
|
||||
"-x-",
|
||||
"-xx",
|
||||
"x..",
|
||||
"x.-",
|
||||
"x.x",
|
||||
"x-.",
|
||||
"x--",
|
||||
"x-x",
|
||||
"xx.",
|
||||
"xx-",
|
||||
"xxx",
|
||||
]
|
||||
|
||||
# Create a reverse dictionary for Morse code
|
||||
REVERSE_DICT = {value: key for key, value in MORSE_CODE_DICT.items()}
|
||||
|
||||
|
||||
def encode_to_morse(plaintext: str) -> str:
|
||||
"""Encode a plaintext message into Morse code.
|
||||
|
||||
Args:
|
||||
plaintext: The plaintext message to encode.
|
||||
|
||||
Returns:
|
||||
The Morse code representation of the plaintext message.
|
||||
|
||||
Example:
|
||||
>>> encode_to_morse("defend the east")
|
||||
'-..x.x..-.x.x-.x-..xx-x....x.xx.x.-x...x-'
|
||||
"""
|
||||
return "x".join([MORSE_CODE_DICT.get(letter.upper(), "") for letter in plaintext])
|
||||
|
||||
|
||||
def encrypt_fractionated_morse(plaintext: str, key: str) -> str:
|
||||
"""Encrypt a plaintext message using Fractionated Morse Cipher.
|
||||
|
||||
Args:
|
||||
plaintext: The plaintext message to encrypt.
|
||||
key: The encryption key.
|
||||
|
||||
Returns:
|
||||
The encrypted ciphertext.
|
||||
|
||||
Example:
|
||||
>>> encrypt_fractionated_morse("defend the east","Roundtable")
|
||||
'ESOAVVLJRSSTRX'
|
||||
|
||||
"""
|
||||
morse_code = encode_to_morse(plaintext)
|
||||
key = key.upper() + string.ascii_uppercase
|
||||
key = "".join(sorted(set(key), key=key.find))
|
||||
|
||||
# Ensure morse_code length is a multiple of 3
|
||||
padding_length = 3 - (len(morse_code) % 3)
|
||||
morse_code += "x" * padding_length
|
||||
|
||||
fractionated_morse_dict = {v: k for k, v in zip(key, MORSE_COMBINATIONS)}
|
||||
fractionated_morse_dict["xxx"] = ""
|
||||
encrypted_text = "".join(
|
||||
[
|
||||
fractionated_morse_dict[morse_code[i : i + 3]]
|
||||
for i in range(0, len(morse_code), 3)
|
||||
]
|
||||
)
|
||||
return encrypted_text
|
||||
|
||||
|
||||
def decrypt_fractionated_morse(ciphertext: str, key: str) -> str:
|
||||
"""Decrypt a ciphertext message encrypted with Fractionated Morse Cipher.
|
||||
|
||||
Args:
|
||||
ciphertext: The ciphertext message to decrypt.
|
||||
key: The decryption key.
|
||||
|
||||
Returns:
|
||||
The decrypted plaintext message.
|
||||
|
||||
Example:
|
||||
>>> decrypt_fractionated_morse("ESOAVVLJRSSTRX","Roundtable")
|
||||
'DEFEND THE EAST'
|
||||
"""
|
||||
key = key.upper() + string.ascii_uppercase
|
||||
key = "".join(sorted(set(key), key=key.find))
|
||||
|
||||
inverse_fractionated_morse_dict = dict(zip(key, MORSE_COMBINATIONS))
|
||||
morse_code = "".join(
|
||||
[inverse_fractionated_morse_dict.get(letter, "") for letter in ciphertext]
|
||||
)
|
||||
decrypted_text = "".join(
|
||||
[REVERSE_DICT[code] for code in morse_code.split("x")]
|
||||
).strip()
|
||||
return decrypted_text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Example usage of Fractionated Morse Cipher.
|
||||
"""
|
||||
plaintext = "defend the east"
|
||||
print("Plain Text:", plaintext)
|
||||
key = "ROUNDTABLE"
|
||||
|
||||
ciphertext = encrypt_fractionated_morse(plaintext, key)
|
||||
print("Encrypted:", ciphertext)
|
||||
|
||||
decrypted_text = decrypt_fractionated_morse(ciphertext, key)
|
||||
print("Decrypted:", decrypted_text)
|
|
@ -39,19 +39,7 @@ import string
|
|||
|
||||
import numpy
|
||||
|
||||
|
||||
def greatest_common_divisor(a: int, b: int) -> int:
|
||||
"""
|
||||
>>> greatest_common_divisor(4, 8)
|
||||
4
|
||||
>>> greatest_common_divisor(8, 4)
|
||||
4
|
||||
>>> greatest_common_divisor(4, 7)
|
||||
1
|
||||
>>> greatest_common_divisor(0, 10)
|
||||
10
|
||||
"""
|
||||
return b if a == 0 else greatest_common_divisor(b % a, a)
|
||||
from maths.greatest_common_divisor import greatest_common_divisor
|
||||
|
||||
|
||||
class HillCipher:
|
||||
|
|
|
@ -4,7 +4,27 @@ import random
|
|||
class Onepad:
|
||||
@staticmethod
|
||||
def encrypt(text: str) -> tuple[list[int], list[int]]:
|
||||
"""Function to encrypt text using pseudo-random numbers"""
|
||||
"""
|
||||
Function to encrypt text using pseudo-random numbers
|
||||
>>> Onepad().encrypt("")
|
||||
([], [])
|
||||
>>> Onepad().encrypt([])
|
||||
([], [])
|
||||
>>> random.seed(1)
|
||||
>>> Onepad().encrypt(" ")
|
||||
([6969], [69])
|
||||
>>> random.seed(1)
|
||||
>>> Onepad().encrypt("Hello")
|
||||
([9729, 114756, 4653, 31309, 10492], [69, 292, 33, 131, 61])
|
||||
>>> Onepad().encrypt(1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'int' object is not iterable
|
||||
>>> Onepad().encrypt(1.1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'float' object is not iterable
|
||||
"""
|
||||
plain = [ord(i) for i in text]
|
||||
key = []
|
||||
cipher = []
|
||||
|
@ -17,7 +37,20 @@ class Onepad:
|
|||
|
||||
@staticmethod
|
||||
def decrypt(cipher: list[int], key: list[int]) -> str:
|
||||
"""Function to decrypt text using pseudo-random numbers."""
|
||||
"""
|
||||
Function to decrypt text using pseudo-random numbers.
|
||||
>>> Onepad().decrypt([], [])
|
||||
''
|
||||
>>> Onepad().decrypt([35], [])
|
||||
''
|
||||
>>> Onepad().decrypt([], [35])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> random.seed(1)
|
||||
>>> Onepad().decrypt([9729, 114756, 4653, 31309, 10492], [69, 292, 33, 131, 61])
|
||||
'Hello'
|
||||
"""
|
||||
plain = []
|
||||
for i in range(len(key)):
|
||||
p = int((cipher[i] - (key[i]) ** 2) / key[i])
|
||||
|
|
142
ciphers/permutation_cipher.py
Normal file
142
ciphers/permutation_cipher.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
"""
|
||||
The permutation cipher, also called the transposition cipher, is a simple encryption
|
||||
technique that rearranges the characters in a message based on a secret key. It
|
||||
divides the message into blocks and applies a permutation to the characters within
|
||||
each block according to the key. The key is a sequence of unique integers that
|
||||
determine the order of character rearrangement.
|
||||
|
||||
For more info: https://www.nku.edu/~christensen/1402%20permutation%20ciphers.pdf
|
||||
"""
|
||||
import random
|
||||
|
||||
|
||||
def generate_valid_block_size(message_length: int) -> int:
|
||||
"""
|
||||
Generate a valid block size that is a factor of the message length.
|
||||
|
||||
Args:
|
||||
message_length (int): The length of the message.
|
||||
|
||||
Returns:
|
||||
int: A valid block size.
|
||||
|
||||
Example:
|
||||
>>> random.seed(1)
|
||||
>>> generate_valid_block_size(12)
|
||||
3
|
||||
"""
|
||||
block_sizes = [
|
||||
block_size
|
||||
for block_size in range(2, message_length + 1)
|
||||
if message_length % block_size == 0
|
||||
]
|
||||
return random.choice(block_sizes)
|
||||
|
||||
|
||||
def generate_permutation_key(block_size: int) -> list[int]:
|
||||
"""
|
||||
Generate a random permutation key of a specified block size.
|
||||
|
||||
Args:
|
||||
block_size (int): The size of each permutation block.
|
||||
|
||||
Returns:
|
||||
list[int]: A list containing a random permutation of digits.
|
||||
|
||||
Example:
|
||||
>>> random.seed(0)
|
||||
>>> generate_permutation_key(4)
|
||||
[2, 0, 1, 3]
|
||||
"""
|
||||
digits = list(range(block_size))
|
||||
random.shuffle(digits)
|
||||
return digits
|
||||
|
||||
|
||||
def encrypt(
|
||||
message: str, key: list[int] | None = None, block_size: int | None = None
|
||||
) -> tuple[str, list[int]]:
|
||||
"""
|
||||
Encrypt a message using a permutation cipher with block rearrangement using a key.
|
||||
|
||||
Args:
|
||||
message (str): The plaintext message to be encrypted.
|
||||
key (list[int]): The permutation key for decryption.
|
||||
block_size (int): The size of each permutation block.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the encrypted message and the encryption key.
|
||||
|
||||
Example:
|
||||
>>> encrypted_message, key = encrypt("HELLO WORLD")
|
||||
>>> decrypted_message = decrypt(encrypted_message, key)
|
||||
>>> decrypted_message
|
||||
'HELLO WORLD'
|
||||
"""
|
||||
message = message.upper()
|
||||
message_length = len(message)
|
||||
|
||||
if key is None or block_size is None:
|
||||
block_size = generate_valid_block_size(message_length)
|
||||
key = generate_permutation_key(block_size)
|
||||
|
||||
encrypted_message = ""
|
||||
|
||||
for i in range(0, message_length, block_size):
|
||||
block = message[i : i + block_size]
|
||||
rearranged_block = [block[digit] for digit in key]
|
||||
encrypted_message += "".join(rearranged_block)
|
||||
|
||||
return encrypted_message, key
|
||||
|
||||
|
||||
def decrypt(encrypted_message: str, key: list[int]) -> str:
|
||||
"""
|
||||
Decrypt an encrypted message using a permutation cipher with block rearrangement.
|
||||
|
||||
Args:
|
||||
encrypted_message (str): The encrypted message.
|
||||
key (list[int]): The permutation key for decryption.
|
||||
|
||||
Returns:
|
||||
str: The decrypted plaintext message.
|
||||
|
||||
Example:
|
||||
>>> encrypted_message, key = encrypt("HELLO WORLD")
|
||||
>>> decrypted_message = decrypt(encrypted_message, key)
|
||||
>>> decrypted_message
|
||||
'HELLO WORLD'
|
||||
"""
|
||||
key_length = len(key)
|
||||
decrypted_message = ""
|
||||
|
||||
for i in range(0, len(encrypted_message), key_length):
|
||||
block = encrypted_message[i : i + key_length]
|
||||
original_block = [""] * key_length
|
||||
for j, digit in enumerate(key):
|
||||
original_block[digit] = block[j]
|
||||
decrypted_message += "".join(original_block)
|
||||
|
||||
return decrypted_message
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Driver function to pass message to get encrypted, then decrypted.
|
||||
|
||||
Example:
|
||||
>>> main()
|
||||
Decrypted message: HELLO WORLD
|
||||
"""
|
||||
message = "HELLO WORLD"
|
||||
encrypted_message, key = encrypt(message)
|
||||
|
||||
decrypted_message = decrypt(encrypted_message, key)
|
||||
print(f"Decrypted message: {decrypted_message}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
main()
|
|
@ -1,3 +1,24 @@
|
|||
"""
|
||||
https://en.wikipedia.org/wiki/Playfair_cipher#Description
|
||||
|
||||
The Playfair cipher was developed by Charles Wheatstone in 1854
|
||||
It's use was heavily promotedby Lord Playfair, hence its name
|
||||
|
||||
Some features of the Playfair cipher are:
|
||||
|
||||
1) It was the first literal diagram substitution cipher
|
||||
2) It is a manual symmetric encryption technique
|
||||
3) It is a multiple letter encryption cipher
|
||||
|
||||
The implementation in the code below encodes alphabets only.
|
||||
It removes spaces, special characters and numbers from the
|
||||
code.
|
||||
|
||||
Playfair is no longer used by military forces because of known
|
||||
insecurities and of the advent of automated encryption devices.
|
||||
This cipher is regarded as insecure since before World War I.
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import string
|
||||
from collections.abc import Generator, Iterable
|
||||
|
@ -60,11 +81,26 @@ def generate_table(key: str) -> list[str]:
|
|||
|
||||
|
||||
def encode(plaintext: str, key: str) -> str:
|
||||
"""
|
||||
Encode the given plaintext using the Playfair cipher.
|
||||
Takes the plaintext and the key as input and returns the encoded string.
|
||||
|
||||
>>> encode("Hello", "MONARCHY")
|
||||
'CFSUPM'
|
||||
>>> encode("attack on the left flank", "EMERGENCY")
|
||||
'DQZSBYFSDZFMFNLOHFDRSG'
|
||||
>>> encode("Sorry!", "SPECIAL")
|
||||
'AVXETX'
|
||||
>>> encode("Number 1", "NUMBER")
|
||||
'UMBENF'
|
||||
>>> encode("Photosynthesis!", "THE SUN")
|
||||
'OEMHQHVCHESUKE'
|
||||
"""
|
||||
|
||||
table = generate_table(key)
|
||||
plaintext = prepare_input(plaintext)
|
||||
ciphertext = ""
|
||||
|
||||
# https://en.wikipedia.org/wiki/Playfair_cipher#Description
|
||||
for char1, char2 in chunker(plaintext, 2):
|
||||
row1, col1 = divmod(table.index(char1), 5)
|
||||
row2, col2 = divmod(table.index(char2), 5)
|
||||
|
@ -83,10 +119,20 @@ def encode(plaintext: str, key: str) -> str:
|
|||
|
||||
|
||||
def decode(ciphertext: str, key: str) -> str:
|
||||
"""
|
||||
Decode the input string using the provided key.
|
||||
|
||||
>>> decode("BMZFAZRZDH", "HAZARD")
|
||||
'FIREHAZARD'
|
||||
>>> decode("HNBWBPQT", "AUTOMOBILE")
|
||||
'DRIVINGX'
|
||||
>>> decode("SLYSSAQS", "CASTLE")
|
||||
'ATXTACKX'
|
||||
"""
|
||||
|
||||
table = generate_table(key)
|
||||
plaintext = ""
|
||||
|
||||
# https://en.wikipedia.org/wiki/Playfair_cipher#Description
|
||||
for char1, char2 in chunker(ciphertext, 2):
|
||||
row1, col1 = divmod(table.index(char1), 5)
|
||||
row2, col2 = divmod(table.index(char2), 5)
|
||||
|
@ -102,3 +148,12 @@ def decode(ciphertext: str, key: str) -> str:
|
|||
plaintext += table[row2 * 5 + col1]
|
||||
|
||||
return plaintext
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
print("Encoded:", encode("BYE AND THANKS", "GREETING"))
|
||||
print("Decoded:", decode("CXRBANRLBALQ", "GREETING"))
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,8 @@ import os
|
|||
import random
|
||||
import sys
|
||||
|
||||
from maths.greatest_common_divisor import gcd_by_iterative
|
||||
|
||||
from . import cryptomath_module, rabin_miller
|
||||
|
||||
|
||||
|
@ -27,7 +29,7 @@ def generate_key(key_size: int) -> tuple[tuple[int, int], tuple[int, int]]:
|
|||
# Generate e that is relatively prime to (p - 1) * (q - 1)
|
||||
while True:
|
||||
e = random.randrange(2 ** (key_size - 1), 2 ** (key_size))
|
||||
if cryptomath_module.gcd(e, (p - 1) * (q - 1)) == 1:
|
||||
if gcd_by_iterative(e, (p - 1) * (q - 1)) == 1:
|
||||
break
|
||||
|
||||
# Calculate d that is mod inverse of e
|
||||
|
|
75
ciphers/running_key_cipher.py
Normal file
75
ciphers/running_key_cipher.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
"""
|
||||
https://en.wikipedia.org/wiki/Running_key_cipher
|
||||
"""
|
||||
|
||||
|
||||
def running_key_encrypt(key: str, plaintext: str) -> str:
|
||||
"""
|
||||
Encrypts the plaintext using the Running Key Cipher.
|
||||
|
||||
:param key: The running key (long piece of text).
|
||||
:param plaintext: The plaintext to be encrypted.
|
||||
:return: The ciphertext.
|
||||
"""
|
||||
plaintext = plaintext.replace(" ", "").upper()
|
||||
key = key.replace(" ", "").upper()
|
||||
key_length = len(key)
|
||||
ciphertext = []
|
||||
ord_a = ord("A")
|
||||
|
||||
for i, char in enumerate(plaintext):
|
||||
p = ord(char) - ord_a
|
||||
k = ord(key[i % key_length]) - ord_a
|
||||
c = (p + k) % 26
|
||||
ciphertext.append(chr(c + ord_a))
|
||||
|
||||
return "".join(ciphertext)
|
||||
|
||||
|
||||
def running_key_decrypt(key: str, ciphertext: str) -> str:
|
||||
"""
|
||||
Decrypts the ciphertext using the Running Key Cipher.
|
||||
|
||||
:param key: The running key (long piece of text).
|
||||
:param ciphertext: The ciphertext to be decrypted.
|
||||
:return: The plaintext.
|
||||
"""
|
||||
ciphertext = ciphertext.replace(" ", "").upper()
|
||||
key = key.replace(" ", "").upper()
|
||||
key_length = len(key)
|
||||
plaintext = []
|
||||
ord_a = ord("A")
|
||||
|
||||
for i, char in enumerate(ciphertext):
|
||||
c = ord(char) - ord_a
|
||||
k = ord(key[i % key_length]) - ord_a
|
||||
p = (c - k) % 26
|
||||
plaintext.append(chr(p + ord_a))
|
||||
|
||||
return "".join(plaintext)
|
||||
|
||||
|
||||
def test_running_key_encrypt() -> None:
|
||||
"""
|
||||
>>> key = "How does the duck know that? said Victor"
|
||||
>>> ciphertext = running_key_encrypt(key, "DEFEND THIS")
|
||||
>>> running_key_decrypt(key, ciphertext) == "DEFENDTHIS"
|
||||
True
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
test_running_key_encrypt()
|
||||
|
||||
plaintext = input("Enter the plaintext: ").upper()
|
||||
print(f"\n{plaintext = }")
|
||||
|
||||
key = "How does the duck know that? said Victor"
|
||||
encrypted_text = running_key_encrypt(key, plaintext)
|
||||
print(f"{encrypted_text = }")
|
||||
|
||||
decrypted_text = running_key_decrypt(key, encrypted_text)
|
||||
print(f"{decrypted_text = }")
|
|
@ -1,135 +0,0 @@
|
|||
# https://en.wikipedia.org/wiki/Trifid_cipher
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str:
|
||||
one, two, three = "", "", ""
|
||||
tmp = []
|
||||
|
||||
for character in message_part:
|
||||
tmp.append(character_to_number[character])
|
||||
|
||||
for each in tmp:
|
||||
one += each[0]
|
||||
two += each[1]
|
||||
three += each[2]
|
||||
|
||||
return one + two + three
|
||||
|
||||
|
||||
def __decrypt_part(
|
||||
message_part: str, character_to_number: dict[str, str]
|
||||
) -> tuple[str, str, str]:
|
||||
tmp, this_part = "", ""
|
||||
result = []
|
||||
|
||||
for character in message_part:
|
||||
this_part += character_to_number[character]
|
||||
|
||||
for digit in this_part:
|
||||
tmp += digit
|
||||
if len(tmp) == len(message_part):
|
||||
result.append(tmp)
|
||||
tmp = ""
|
||||
|
||||
return result[0], result[1], result[2]
|
||||
|
||||
|
||||
def __prepare(
|
||||
message: str, alphabet: str
|
||||
) -> tuple[str, str, dict[str, str], dict[str, str]]:
|
||||
# Validate message and alphabet, set to upper and remove spaces
|
||||
alphabet = alphabet.replace(" ", "").upper()
|
||||
message = message.replace(" ", "").upper()
|
||||
|
||||
# Check length and characters
|
||||
if len(alphabet) != 27:
|
||||
raise KeyError("Length of alphabet has to be 27.")
|
||||
for each in message:
|
||||
if each not in alphabet:
|
||||
raise ValueError("Each message character has to be included in alphabet!")
|
||||
|
||||
# Generate dictionares
|
||||
numbers = (
|
||||
"111",
|
||||
"112",
|
||||
"113",
|
||||
"121",
|
||||
"122",
|
||||
"123",
|
||||
"131",
|
||||
"132",
|
||||
"133",
|
||||
"211",
|
||||
"212",
|
||||
"213",
|
||||
"221",
|
||||
"222",
|
||||
"223",
|
||||
"231",
|
||||
"232",
|
||||
"233",
|
||||
"311",
|
||||
"312",
|
||||
"313",
|
||||
"321",
|
||||
"322",
|
||||
"323",
|
||||
"331",
|
||||
"332",
|
||||
"333",
|
||||
)
|
||||
character_to_number = {}
|
||||
number_to_character = {}
|
||||
for letter, number in zip(alphabet, numbers):
|
||||
character_to_number[letter] = number
|
||||
number_to_character[number] = letter
|
||||
|
||||
return message, alphabet, character_to_number, number_to_character
|
||||
|
||||
|
||||
def encrypt_message(
|
||||
message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5
|
||||
) -> str:
|
||||
message, alphabet, character_to_number, number_to_character = __prepare(
|
||||
message, alphabet
|
||||
)
|
||||
encrypted, encrypted_numeric = "", ""
|
||||
|
||||
for i in range(0, len(message) + 1, period):
|
||||
encrypted_numeric += __encrypt_part(
|
||||
message[i : i + period], character_to_number
|
||||
)
|
||||
|
||||
for i in range(0, len(encrypted_numeric), 3):
|
||||
encrypted += number_to_character[encrypted_numeric[i : i + 3]]
|
||||
|
||||
return encrypted
|
||||
|
||||
|
||||
def decrypt_message(
|
||||
message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5
|
||||
) -> str:
|
||||
message, alphabet, character_to_number, number_to_character = __prepare(
|
||||
message, alphabet
|
||||
)
|
||||
decrypted_numeric = []
|
||||
decrypted = ""
|
||||
|
||||
for i in range(0, len(message) + 1, period):
|
||||
a, b, c = __decrypt_part(message[i : i + period], character_to_number)
|
||||
|
||||
for j in range(len(a)):
|
||||
decrypted_numeric.append(a[j] + b[j] + c[j])
|
||||
|
||||
for each in decrypted_numeric:
|
||||
decrypted += number_to_character[each]
|
||||
|
||||
return decrypted
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
msg = "DEFEND THE EAST WALL OF THE CASTLE."
|
||||
encrypted = encrypt_message(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ")
|
||||
decrypted = decrypt_message(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ")
|
||||
print(f"Encrypted: {encrypted}\nDecrypted: {decrypted}")
|
|
@ -6,8 +6,8 @@ from . import transposition_cipher as trans_cipher
|
|||
|
||||
|
||||
def main() -> None:
|
||||
input_file = "Prehistoric Men.txt"
|
||||
output_file = "Output.txt"
|
||||
input_file = "./prehistoric_men.txt"
|
||||
output_file = "./Output.txt"
|
||||
key = int(input("Enter key: "))
|
||||
mode = input("Encrypt/Decrypt [e/d]: ")
|
||||
|
||||
|
|
210
ciphers/trifid_cipher.py
Normal file
210
ciphers/trifid_cipher.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
"""
|
||||
The trifid cipher uses a table to fractionate each plaintext letter into a trigram,
|
||||
mixes the constituents of the trigrams, and then applies the table in reverse to turn
|
||||
these mixed trigrams into ciphertext letters.
|
||||
|
||||
https://en.wikipedia.org/wiki/Trifid_cipher
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
# fmt: off
|
||||
TEST_CHARACTER_TO_NUMBER = {
|
||||
"A": "111", "B": "112", "C": "113", "D": "121", "E": "122", "F": "123", "G": "131",
|
||||
"H": "132", "I": "133", "J": "211", "K": "212", "L": "213", "M": "221", "N": "222",
|
||||
"O": "223", "P": "231", "Q": "232", "R": "233", "S": "311", "T": "312", "U": "313",
|
||||
"V": "321", "W": "322", "X": "323", "Y": "331", "Z": "332", "+": "333",
|
||||
}
|
||||
# fmt: off
|
||||
|
||||
TEST_NUMBER_TO_CHARACTER = {val: key for key, val in TEST_CHARACTER_TO_NUMBER.items()}
|
||||
|
||||
|
||||
def __encrypt_part(message_part: str, character_to_number: dict[str, str]) -> str:
|
||||
"""
|
||||
Arrange the triagram value of each letter of 'message_part' vertically and join
|
||||
them horizontally.
|
||||
|
||||
>>> __encrypt_part('ASK', TEST_CHARACTER_TO_NUMBER)
|
||||
'132111112'
|
||||
"""
|
||||
one, two, three = "", "", ""
|
||||
for each in (character_to_number[character] for character in message_part):
|
||||
one += each[0]
|
||||
two += each[1]
|
||||
three += each[2]
|
||||
|
||||
return one + two + three
|
||||
|
||||
|
||||
def __decrypt_part(
|
||||
message_part: str, character_to_number: dict[str, str]
|
||||
) -> tuple[str, str, str]:
|
||||
"""
|
||||
Convert each letter of the input string into their respective trigram values, join
|
||||
them and split them into three equal groups of strings which are returned.
|
||||
|
||||
>>> __decrypt_part('ABCDE', TEST_CHARACTER_TO_NUMBER)
|
||||
('11111', '21131', '21122')
|
||||
"""
|
||||
this_part = "".join(character_to_number[character] for character in message_part)
|
||||
result = []
|
||||
tmp = ""
|
||||
for digit in this_part:
|
||||
tmp += digit
|
||||
if len(tmp) == len(message_part):
|
||||
result.append(tmp)
|
||||
tmp = ""
|
||||
|
||||
return result[0], result[1], result[2]
|
||||
|
||||
|
||||
def __prepare(
|
||||
message: str, alphabet: str
|
||||
) -> tuple[str, str, dict[str, str], dict[str, str]]:
|
||||
"""
|
||||
A helper function that generates the triagrams and assigns each letter of the
|
||||
alphabet to its corresponding triagram and stores this in a dictionary
|
||||
("character_to_number" and "number_to_character") after confirming if the
|
||||
alphabet's length is 27.
|
||||
|
||||
>>> test = __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxYZ+')
|
||||
>>> expected = ('IAMABOY','ABCDEFGHIJKLMNOPQRSTUVWXYZ+',
|
||||
... TEST_CHARACTER_TO_NUMBER, TEST_NUMBER_TO_CHARACTER)
|
||||
>>> test == expected
|
||||
True
|
||||
|
||||
Testing with incomplete alphabet
|
||||
>>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVw')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'Length of alphabet has to be 27.'
|
||||
|
||||
Testing with extra long alphabets
|
||||
>>> __prepare('I aM a BOy','abCdeFghijkLmnopqrStuVwxyzzwwtyyujjgfd')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'Length of alphabet has to be 27.'
|
||||
|
||||
Testing with punctuations that are not in the given alphabet
|
||||
>>> __prepare('am i a boy?','abCdeFghijkLmnopqrStuVwxYZ+')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each message character has to be included in alphabet!
|
||||
|
||||
Testing with numbers
|
||||
>>> __prepare(500,'abCdeFghijkLmnopqrStuVwxYZ+')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: 'int' object has no attribute 'replace'
|
||||
"""
|
||||
# Validate message and alphabet, set to upper and remove spaces
|
||||
alphabet = alphabet.replace(" ", "").upper()
|
||||
message = message.replace(" ", "").upper()
|
||||
|
||||
# Check length and characters
|
||||
if len(alphabet) != 27:
|
||||
raise KeyError("Length of alphabet has to be 27.")
|
||||
if any(char not in alphabet for char in message):
|
||||
raise ValueError("Each message character has to be included in alphabet!")
|
||||
|
||||
# Generate dictionares
|
||||
character_to_number = dict(zip(alphabet, TEST_CHARACTER_TO_NUMBER.values()))
|
||||
number_to_character = {
|
||||
number: letter for letter, number in character_to_number.items()
|
||||
}
|
||||
|
||||
return message, alphabet, character_to_number, number_to_character
|
||||
|
||||
|
||||
def encrypt_message(
|
||||
message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5
|
||||
) -> str:
|
||||
"""
|
||||
encrypt_message
|
||||
===============
|
||||
|
||||
Encrypts a message using the trifid_cipher. Any punctuatuions that
|
||||
would be used should be added to the alphabet.
|
||||
|
||||
PARAMETERS
|
||||
----------
|
||||
|
||||
* message: The message you want to encrypt.
|
||||
* alphabet (optional): The characters to be used for the cipher .
|
||||
* period (optional): The number of characters you want in a group whilst
|
||||
encrypting.
|
||||
|
||||
>>> encrypt_message('I am a boy')
|
||||
'BCDGBQY'
|
||||
|
||||
>>> encrypt_message(' ')
|
||||
''
|
||||
|
||||
>>> encrypt_message(' aide toi le c iel ta id era ',
|
||||
... 'FELIXMARDSTBCGHJKNOPQUVWYZ+',5)
|
||||
'FMJFVOISSUFTFPUFEQQC'
|
||||
|
||||
"""
|
||||
message, alphabet, character_to_number, number_to_character = __prepare(
|
||||
message, alphabet
|
||||
)
|
||||
|
||||
encrypted_numeric = ""
|
||||
for i in range(0, len(message) + 1, period):
|
||||
encrypted_numeric += __encrypt_part(
|
||||
message[i : i + period], character_to_number
|
||||
)
|
||||
|
||||
encrypted = ""
|
||||
for i in range(0, len(encrypted_numeric), 3):
|
||||
encrypted += number_to_character[encrypted_numeric[i : i + 3]]
|
||||
return encrypted
|
||||
|
||||
|
||||
def decrypt_message(
|
||||
message: str, alphabet: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ.", period: int = 5
|
||||
) -> str:
|
||||
"""
|
||||
decrypt_message
|
||||
===============
|
||||
|
||||
Decrypts a trifid_cipher encrypted message .
|
||||
|
||||
PARAMETERS
|
||||
----------
|
||||
|
||||
* message: The message you want to decrypt .
|
||||
* alphabet (optional): The characters used for the cipher.
|
||||
* period (optional): The number of characters used in grouping when it
|
||||
was encrypted.
|
||||
|
||||
>>> decrypt_message('BCDGBQY')
|
||||
'IAMABOY'
|
||||
|
||||
Decrypting with your own alphabet and period
|
||||
>>> decrypt_message('FMJFVOISSUFTFPUFEQQC','FELIXMARDSTBCGHJKNOPQUVWYZ+',5)
|
||||
'AIDETOILECIELTAIDERA'
|
||||
"""
|
||||
message, alphabet, character_to_number, number_to_character = __prepare(
|
||||
message, alphabet
|
||||
)
|
||||
|
||||
decrypted_numeric = []
|
||||
for i in range(0, len(message), period):
|
||||
a, b, c = __decrypt_part(message[i : i + period], character_to_number)
|
||||
|
||||
for j in range(len(a)):
|
||||
decrypted_numeric.append(a[j] + b[j] + c[j])
|
||||
|
||||
return "".join(number_to_character[each] for each in decrypted_numeric)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
msg = "DEFEND THE EAST WALL OF THE CASTLE."
|
||||
encrypted = encrypt_message(msg, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ")
|
||||
decrypted = decrypt_message(encrypted, "EPSDUCVWYM.ZLKXNBTFGORIJHAQ")
|
||||
print(f"Encrypted: {encrypted}\nDecrypted: {decrypted}")
|
42
ciphers/vernam_cipher.py
Normal file
42
ciphers/vernam_cipher.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
def vernam_encrypt(plaintext: str, key: str) -> str:
|
||||
"""
|
||||
>>> vernam_encrypt("HELLO","KEY")
|
||||
'RIJVS'
|
||||
"""
|
||||
ciphertext = ""
|
||||
for i in range(len(plaintext)):
|
||||
ct = ord(key[i % len(key)]) - 65 + ord(plaintext[i]) - 65
|
||||
while ct > 25:
|
||||
ct = ct - 26
|
||||
ciphertext += chr(65 + ct)
|
||||
return ciphertext
|
||||
|
||||
|
||||
def vernam_decrypt(ciphertext: str, key: str) -> str:
|
||||
"""
|
||||
>>> vernam_decrypt("RIJVS","KEY")
|
||||
'HELLO'
|
||||
"""
|
||||
decrypted_text = ""
|
||||
for i in range(len(ciphertext)):
|
||||
ct = ord(ciphertext[i]) - ord(key[i % len(key)])
|
||||
while ct < 0:
|
||||
ct = 26 + ct
|
||||
decrypted_text += chr(65 + ct)
|
||||
return decrypted_text
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
|
||||
# Example usage
|
||||
plaintext = "HELLO"
|
||||
key = "KEY"
|
||||
encrypted_text = vernam_encrypt(plaintext, key)
|
||||
decrypted_text = vernam_decrypt(encrypted_text, key)
|
||||
print("\n\n")
|
||||
print("Plaintext:", plaintext)
|
||||
print("Encrypted:", encrypted_text)
|
||||
print("Decrypted:", decrypted_text)
|
|
@ -35,15 +35,32 @@ class XORCipher:
|
|||
output: encrypted string 'content' as a list of chars
|
||||
if key not passed the method uses the key by the constructor.
|
||||
otherwise key = 1
|
||||
|
||||
Empty list
|
||||
>>> XORCipher().encrypt("", 5)
|
||||
[]
|
||||
|
||||
One key
|
||||
>>> XORCipher().encrypt("hallo welt", 1)
|
||||
['i', '`', 'm', 'm', 'n', '!', 'v', 'd', 'm', 'u']
|
||||
|
||||
Normal key
|
||||
>>> XORCipher().encrypt("HALLO WELT", 32)
|
||||
['h', 'a', 'l', 'l', 'o', '\\x00', 'w', 'e', 'l', 't']
|
||||
|
||||
Key greater than 255
|
||||
>>> XORCipher().encrypt("hallo welt", 256)
|
||||
['h', 'a', 'l', 'l', 'o', ' ', 'w', 'e', 'l', 't']
|
||||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(key, int) and isinstance(content, str)
|
||||
assert isinstance(key, int)
|
||||
assert isinstance(content, str)
|
||||
|
||||
key = key or self.__key or 1
|
||||
|
||||
# make sure key is an appropriate size
|
||||
key %= 255
|
||||
key %= 256
|
||||
|
||||
return [chr(ord(ch) ^ key) for ch in content]
|
||||
|
||||
|
@ -53,15 +70,32 @@ class XORCipher:
|
|||
output: decrypted string 'content' as a list of chars
|
||||
if key not passed the method uses the key by the constructor.
|
||||
otherwise key = 1
|
||||
|
||||
Empty list
|
||||
>>> XORCipher().decrypt("", 5)
|
||||
[]
|
||||
|
||||
One key
|
||||
>>> XORCipher().decrypt("hallo welt", 1)
|
||||
['i', '`', 'm', 'm', 'n', '!', 'v', 'd', 'm', 'u']
|
||||
|
||||
Normal key
|
||||
>>> XORCipher().decrypt("HALLO WELT", 32)
|
||||
['h', 'a', 'l', 'l', 'o', '\\x00', 'w', 'e', 'l', 't']
|
||||
|
||||
Key greater than 255
|
||||
>>> XORCipher().decrypt("hallo welt", 256)
|
||||
['h', 'a', 'l', 'l', 'o', ' ', 'w', 'e', 'l', 't']
|
||||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(key, int) and isinstance(content, list)
|
||||
assert isinstance(key, int)
|
||||
assert isinstance(content, str)
|
||||
|
||||
key = key or self.__key or 1
|
||||
|
||||
# make sure key is an appropriate size
|
||||
key %= 255
|
||||
key %= 256
|
||||
|
||||
return [chr(ord(ch) ^ key) for ch in content]
|
||||
|
||||
|
@ -71,16 +105,32 @@ class XORCipher:
|
|||
output: encrypted string 'content'
|
||||
if key not passed the method uses the key by the constructor.
|
||||
otherwise key = 1
|
||||
|
||||
Empty list
|
||||
>>> XORCipher().encrypt_string("", 5)
|
||||
''
|
||||
|
||||
One key
|
||||
>>> XORCipher().encrypt_string("hallo welt", 1)
|
||||
'i`mmn!vdmu'
|
||||
|
||||
Normal key
|
||||
>>> XORCipher().encrypt_string("HALLO WELT", 32)
|
||||
'hallo\\x00welt'
|
||||
|
||||
Key greater than 255
|
||||
>>> XORCipher().encrypt_string("hallo welt", 256)
|
||||
'hallo welt'
|
||||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(key, int) and isinstance(content, str)
|
||||
assert isinstance(key, int)
|
||||
assert isinstance(content, str)
|
||||
|
||||
key = key or self.__key or 1
|
||||
|
||||
# make sure key can be any size
|
||||
while key > 255:
|
||||
key -= 255
|
||||
# make sure key is an appropriate size
|
||||
key %= 256
|
||||
|
||||
# This will be returned
|
||||
ans = ""
|
||||
|
@ -96,16 +146,32 @@ class XORCipher:
|
|||
output: decrypted string 'content'
|
||||
if key not passed the method uses the key by the constructor.
|
||||
otherwise key = 1
|
||||
|
||||
Empty list
|
||||
>>> XORCipher().decrypt_string("", 5)
|
||||
''
|
||||
|
||||
One key
|
||||
>>> XORCipher().decrypt_string("hallo welt", 1)
|
||||
'i`mmn!vdmu'
|
||||
|
||||
Normal key
|
||||
>>> XORCipher().decrypt_string("HALLO WELT", 32)
|
||||
'hallo\\x00welt'
|
||||
|
||||
Key greater than 255
|
||||
>>> XORCipher().decrypt_string("hallo welt", 256)
|
||||
'hallo welt'
|
||||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(key, int) and isinstance(content, str)
|
||||
assert isinstance(key, int)
|
||||
assert isinstance(content, str)
|
||||
|
||||
key = key or self.__key or 1
|
||||
|
||||
# make sure key can be any size
|
||||
while key > 255:
|
||||
key -= 255
|
||||
# make sure key is an appropriate size
|
||||
key %= 256
|
||||
|
||||
# This will be returned
|
||||
ans = ""
|
||||
|
@ -125,7 +191,11 @@ class XORCipher:
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(file, str) and isinstance(key, int)
|
||||
assert isinstance(file, str)
|
||||
assert isinstance(key, int)
|
||||
|
||||
# make sure key is an appropriate size
|
||||
key %= 256
|
||||
|
||||
try:
|
||||
with open(file) as fin, open("encrypt.out", "w+") as fout:
|
||||
|
@ -148,7 +218,11 @@ class XORCipher:
|
|||
"""
|
||||
|
||||
# precondition
|
||||
assert isinstance(file, str) and isinstance(key, int)
|
||||
assert isinstance(file, str)
|
||||
assert isinstance(key, int)
|
||||
|
||||
# make sure key is an appropriate size
|
||||
key %= 256
|
||||
|
||||
try:
|
||||
with open(file) as fin, open("decrypt.out", "w+") as fout:
|
||||
|
@ -162,6 +236,11 @@ class XORCipher:
|
|||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
|
||||
# Tests
|
||||
# crypt = XORCipher()
|
||||
# key = 67
|
||||
|
|
|
@ -11,10 +11,10 @@ Download dataset from :
|
|||
https://lhncbc.nlm.nih.gov/LHC-publications/pubs/TuberculosisChestXrayImageDataSets.html
|
||||
|
||||
1. Download the dataset folder and create two folder training set and test set
|
||||
in the parent dataste folder
|
||||
in the parent dataset folder
|
||||
2. Move 30-40 image from both TB positive and TB Negative folder
|
||||
in the test set folder
|
||||
3. The labels of the iamges will be extracted from the folder name
|
||||
3. The labels of the images will be extracted from the folder name
|
||||
the image is present in.
|
||||
|
||||
"""
|
||||
|
|
|
@ -253,13 +253,13 @@ def matrix_concurrency(image: np.ndarray, coordinate: tuple[int, int]) -> np.nda
|
|||
|
||||
|
||||
def haralick_descriptors(matrix: np.ndarray) -> list[float]:
|
||||
"""Calculates all 8 Haralick descriptors based on co-occurence input matrix.
|
||||
"""Calculates all 8 Haralick descriptors based on co-occurrence input matrix.
|
||||
All descriptors are as follows:
|
||||
Maximum probability, Inverse Difference, Homogeneity, Entropy,
|
||||
Energy, Dissimilarity, Contrast and Correlation
|
||||
|
||||
Args:
|
||||
matrix: Co-occurence matrix to use as base for calculating descriptors.
|
||||
matrix: Co-occurrence matrix to use as base for calculating descriptors.
|
||||
|
||||
Returns:
|
||||
Reverse ordered list of resulting descriptors
|
||||
|
|
|
@ -8,7 +8,7 @@ from string import ascii_lowercase, digits
|
|||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# Parrameters
|
||||
# Parameters
|
||||
OUTPUT_SIZE = (720, 1280) # Height, Width
|
||||
SCALE_RANGE = (0.4, 0.6) # if height or width lower than this scale, drop it.
|
||||
FILTER_TINY_SCALE = 1 / 100
|
||||
|
|
|
@ -57,7 +57,8 @@ def decimal_to_hexadecimal(decimal: float) -> str:
|
|||
>>> decimal_to_hexadecimal(-256) == hex(-256)
|
||||
True
|
||||
"""
|
||||
assert type(decimal) in (int, float) and decimal == int(decimal)
|
||||
assert isinstance(decimal, (int, float))
|
||||
assert decimal == int(decimal)
|
||||
decimal = int(decimal)
|
||||
hexadecimal = ""
|
||||
negative = False
|
||||
|
|
85
conversions/ipv4_conversion.py
Normal file
85
conversions/ipv4_conversion.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
# https://www.geeksforgeeks.org/convert-ip-address-to-integer-and-vice-versa/
|
||||
|
||||
|
||||
def ipv4_to_decimal(ipv4_address: str) -> int:
|
||||
"""
|
||||
Convert an IPv4 address to its decimal representation.
|
||||
|
||||
Args:
|
||||
ip_address: A string representing an IPv4 address (e.g., "192.168.0.1").
|
||||
|
||||
Returns:
|
||||
int: The decimal representation of the IP address.
|
||||
|
||||
>>> ipv4_to_decimal("192.168.0.1")
|
||||
3232235521
|
||||
>>> ipv4_to_decimal("10.0.0.255")
|
||||
167772415
|
||||
>>> ipv4_to_decimal("10.0.255")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid IPv4 address format
|
||||
>>> ipv4_to_decimal("10.0.0.256")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid IPv4 octet 256
|
||||
"""
|
||||
|
||||
octets = [int(octet) for octet in ipv4_address.split(".")]
|
||||
if len(octets) != 4:
|
||||
raise ValueError("Invalid IPv4 address format")
|
||||
|
||||
decimal_ipv4 = 0
|
||||
for octet in octets:
|
||||
if not 0 <= octet <= 255:
|
||||
raise ValueError(f"Invalid IPv4 octet {octet}") # noqa: EM102
|
||||
decimal_ipv4 = (decimal_ipv4 << 8) + int(octet)
|
||||
|
||||
return decimal_ipv4
|
||||
|
||||
|
||||
def alt_ipv4_to_decimal(ipv4_address: str) -> int:
|
||||
"""
|
||||
>>> alt_ipv4_to_decimal("192.168.0.1")
|
||||
3232235521
|
||||
>>> alt_ipv4_to_decimal("10.0.0.255")
|
||||
167772415
|
||||
"""
|
||||
return int("0x" + "".join(f"{int(i):02x}" for i in ipv4_address.split(".")), 16)
|
||||
|
||||
|
||||
def decimal_to_ipv4(decimal_ipv4: int) -> str:
|
||||
"""
|
||||
Convert a decimal representation of an IP address to its IPv4 format.
|
||||
|
||||
Args:
|
||||
decimal_ipv4: An integer representing the decimal IP address.
|
||||
|
||||
Returns:
|
||||
The IPv4 representation of the decimal IP address.
|
||||
|
||||
>>> decimal_to_ipv4(3232235521)
|
||||
'192.168.0.1'
|
||||
>>> decimal_to_ipv4(167772415)
|
||||
'10.0.0.255'
|
||||
>>> decimal_to_ipv4(-1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid decimal IPv4 address
|
||||
"""
|
||||
|
||||
if not (0 <= decimal_ipv4 <= 4294967295):
|
||||
raise ValueError("Invalid decimal IPv4 address")
|
||||
|
||||
ip_parts = []
|
||||
for _ in range(4):
|
||||
ip_parts.append(str(decimal_ipv4 & 255))
|
||||
decimal_ipv4 >>= 8
|
||||
|
||||
return ".".join(reversed(ip_parts))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
65
conversions/octal_to_hexadecimal.py
Normal file
65
conversions/octal_to_hexadecimal.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
def octal_to_hex(octal: str) -> str:
|
||||
"""
|
||||
Convert an Octal number to Hexadecimal number.
|
||||
For more information: https://en.wikipedia.org/wiki/Octal
|
||||
|
||||
>>> octal_to_hex("100")
|
||||
'0x40'
|
||||
>>> octal_to_hex("235")
|
||||
'0x9D'
|
||||
>>> octal_to_hex(17)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Expected a string as input
|
||||
>>> octal_to_hex("Av")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Not a Valid Octal Number
|
||||
>>> octal_to_hex("")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Empty string was passed to the function
|
||||
"""
|
||||
|
||||
if not isinstance(octal, str):
|
||||
raise TypeError("Expected a string as input")
|
||||
if octal.startswith("0o"):
|
||||
octal = octal[2:]
|
||||
if octal == "":
|
||||
raise ValueError("Empty string was passed to the function")
|
||||
if any(char not in "01234567" for char in octal):
|
||||
raise ValueError("Not a Valid Octal Number")
|
||||
|
||||
decimal = 0
|
||||
for char in octal:
|
||||
decimal <<= 3
|
||||
decimal |= int(char)
|
||||
|
||||
hex_char = "0123456789ABCDEF"
|
||||
|
||||
revhex = ""
|
||||
while decimal:
|
||||
revhex += hex_char[decimal & 15]
|
||||
decimal >>= 4
|
||||
|
||||
return "0x" + revhex[::-1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
nums = ["030", "100", "247", "235", "007"]
|
||||
|
||||
## Main Tests
|
||||
|
||||
for num in nums:
|
||||
hexadecimal = octal_to_hex(num)
|
||||
expected = "0x" + hex(int(num, 8))[2:].upper()
|
||||
|
||||
assert hexadecimal == expected
|
||||
|
||||
print(f"Hex of '0o{num}' is: {hexadecimal}")
|
||||
print(f"Expected was: {expected}")
|
||||
print("---")
|
71
conversions/rgb_cmyk_conversion.py
Normal file
71
conversions/rgb_cmyk_conversion.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
def rgb_to_cmyk(r_input: int, g_input: int, b_input: int) -> tuple[int, int, int, int]:
|
||||
"""
|
||||
Simple RGB to CMYK conversion. Returns percentages of CMYK paint.
|
||||
https://www.programmingalgorithms.com/algorithm/rgb-to-cmyk/
|
||||
|
||||
Note: this is a very popular algorithm that converts colors linearly and gives
|
||||
only approximate results. Actual preparation for printing requires advanced color
|
||||
conversion considering the color profiles and parameters of the target device.
|
||||
|
||||
>>> rgb_to_cmyk(255, 200, "a")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Expected int, found (<class 'int'>, <class 'int'>, <class 'str'>)
|
||||
|
||||
>>> rgb_to_cmyk(255, 255, 999)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Expected int of the range 0..255
|
||||
|
||||
>>> rgb_to_cmyk(255, 255, 255) # white
|
||||
(0, 0, 0, 0)
|
||||
|
||||
>>> rgb_to_cmyk(128, 128, 128) # gray
|
||||
(0, 0, 0, 50)
|
||||
|
||||
>>> rgb_to_cmyk(0, 0, 0) # black
|
||||
(0, 0, 0, 100)
|
||||
|
||||
>>> rgb_to_cmyk(255, 0, 0) # red
|
||||
(0, 100, 100, 0)
|
||||
|
||||
>>> rgb_to_cmyk(0, 255, 0) # green
|
||||
(100, 0, 100, 0)
|
||||
|
||||
>>> rgb_to_cmyk(0, 0, 255) # blue
|
||||
(100, 100, 0, 0)
|
||||
"""
|
||||
|
||||
if (
|
||||
not isinstance(r_input, int)
|
||||
or not isinstance(g_input, int)
|
||||
or not isinstance(b_input, int)
|
||||
):
|
||||
msg = f"Expected int, found {type(r_input), type(g_input), type(b_input)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
if not 0 <= r_input < 256 or not 0 <= g_input < 256 or not 0 <= b_input < 256:
|
||||
raise ValueError("Expected int of the range 0..255")
|
||||
|
||||
# changing range from 0..255 to 0..1
|
||||
r = r_input / 255
|
||||
g = g_input / 255
|
||||
b = b_input / 255
|
||||
|
||||
k = 1 - max(r, g, b)
|
||||
|
||||
if k == 1: # pure black
|
||||
return 0, 0, 0, 100
|
||||
|
||||
c = round(100 * (1 - r - k) / (1 - k))
|
||||
m = round(100 * (1 - g - k) / (1 - k))
|
||||
y = round(100 * (1 - b - k) / (1 - k))
|
||||
k = round(100 * k)
|
||||
|
||||
return c, m, y, k
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
86
conversions/time_conversions.py
Normal file
86
conversions/time_conversions.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
"""
|
||||
A unit of time is any particular time interval, used as a standard way of measuring or
|
||||
expressing duration. The base unit of time in the International System of Units (SI),
|
||||
and by extension most of the Western world, is the second, defined as about 9 billion
|
||||
oscillations of the caesium atom.
|
||||
|
||||
https://en.wikipedia.org/wiki/Unit_of_time
|
||||
"""
|
||||
|
||||
time_chart: dict[str, float] = {
|
||||
"seconds": 1.0,
|
||||
"minutes": 60.0, # 1 minute = 60 sec
|
||||
"hours": 3600.0, # 1 hour = 60 minutes = 3600 seconds
|
||||
"days": 86400.0, # 1 day = 24 hours = 1440 min = 86400 sec
|
||||
"weeks": 604800.0, # 1 week=7d=168hr=10080min = 604800 sec
|
||||
"months": 2629800.0, # Approximate value for a month in seconds
|
||||
"years": 31557600.0, # Approximate value for a year in seconds
|
||||
}
|
||||
|
||||
time_chart_inverse: dict[str, float] = {
|
||||
key: 1 / value for key, value in time_chart.items()
|
||||
}
|
||||
|
||||
|
||||
def convert_time(time_value: float, unit_from: str, unit_to: str) -> float:
|
||||
"""
|
||||
Convert time from one unit to another using the time_chart above.
|
||||
|
||||
>>> convert_time(3600, "seconds", "hours")
|
||||
1.0
|
||||
>>> convert_time(3500, "Seconds", "Hours")
|
||||
0.972
|
||||
>>> convert_time(1, "DaYs", "hours")
|
||||
24.0
|
||||
>>> convert_time(120, "minutes", "SeCoNdS")
|
||||
7200.0
|
||||
>>> convert_time(2, "WEEKS", "days")
|
||||
14.0
|
||||
>>> convert_time(0.5, "hours", "MINUTES")
|
||||
30.0
|
||||
>>> convert_time(-3600, "seconds", "hours")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'time_value' must be a non-negative number.
|
||||
>>> convert_time("Hello", "hours", "minutes")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'time_value' must be a non-negative number.
|
||||
>>> convert_time([0, 1, 2], "weeks", "days")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'time_value' must be a non-negative number.
|
||||
>>> convert_time(1, "cool", "century") # doctest: +ELLIPSIS
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid unit cool is not in seconds, minutes, hours, days, weeks, ...
|
||||
>>> convert_time(1, "seconds", "hot") # doctest: +ELLIPSIS
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid unit hot is not in seconds, minutes, hours, days, weeks, ...
|
||||
"""
|
||||
if not isinstance(time_value, (int, float)) or time_value < 0:
|
||||
msg = "'time_value' must be a non-negative number."
|
||||
raise ValueError(msg)
|
||||
|
||||
unit_from = unit_from.lower()
|
||||
unit_to = unit_to.lower()
|
||||
if unit_from not in time_chart or unit_to not in time_chart:
|
||||
invalid_unit = unit_from if unit_from not in time_chart else unit_to
|
||||
msg = f"Invalid unit {invalid_unit} is not in {', '.join(time_chart)}."
|
||||
raise ValueError(msg)
|
||||
|
||||
return round(
|
||||
time_value * time_chart[unit_from] * time_chart_inverse[unit_to],
|
||||
3,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
print(f"{convert_time(3600,'seconds', 'hours') = :,}")
|
||||
print(f"{convert_time(360, 'days', 'months') = :,}")
|
||||
print(f"{convert_time(360, 'months', 'years') = :,}")
|
||||
print(f"{convert_time(1, 'years', 'seconds') = :,}")
|
58
data_structures/arrays/equilibrium_index_in_array.py
Normal file
58
data_structures/arrays/equilibrium_index_in_array.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
"""
|
||||
Find the Equilibrium Index of an Array.
|
||||
Reference: https://www.geeksforgeeks.org/equilibrium-index-of-an-array/
|
||||
|
||||
Python doctest can be run with the following command:
|
||||
python -m doctest -v equilibrium_index.py
|
||||
|
||||
Given a sequence arr[] of size n, this function returns
|
||||
an equilibrium index (if any) or -1 if no equilibrium index exists.
|
||||
|
||||
The equilibrium index of an array is an index such that the sum of
|
||||
elements at lower indexes is equal to the sum of elements at higher indexes.
|
||||
|
||||
|
||||
|
||||
Example Input:
|
||||
arr = [-7, 1, 5, 2, -4, 3, 0]
|
||||
Output: 3
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def equilibrium_index(arr: list[int]) -> int:
|
||||
"""
|
||||
Find the equilibrium index of an array.
|
||||
|
||||
Args:
|
||||
arr (list[int]): The input array of integers.
|
||||
|
||||
Returns:
|
||||
int: The equilibrium index or -1 if no equilibrium index exists.
|
||||
|
||||
Examples:
|
||||
>>> equilibrium_index([-7, 1, 5, 2, -4, 3, 0])
|
||||
3
|
||||
>>> equilibrium_index([1, 2, 3, 4, 5])
|
||||
-1
|
||||
>>> equilibrium_index([1, 1, 1, 1, 1])
|
||||
2
|
||||
>>> equilibrium_index([2, 4, 6, 8, 10, 3])
|
||||
-1
|
||||
"""
|
||||
total_sum = sum(arr)
|
||||
left_sum = 0
|
||||
|
||||
for i, value in enumerate(arr):
|
||||
total_sum -= value
|
||||
if left_sum == total_sum:
|
||||
return i
|
||||
left_sum += value
|
||||
|
||||
return -1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
87
data_structures/arrays/find_triplets_with_0_sum.py
Normal file
87
data_structures/arrays/find_triplets_with_0_sum.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
from itertools import combinations
|
||||
|
||||
|
||||
def find_triplets_with_0_sum(nums: list[int]) -> list[list[int]]:
|
||||
"""
|
||||
Given a list of integers, return elements a, b, c such that a + b + c = 0.
|
||||
Args:
|
||||
nums: list of integers
|
||||
Returns:
|
||||
list of lists of integers where sum(each_list) == 0
|
||||
Examples:
|
||||
>>> find_triplets_with_0_sum([-1, 0, 1, 2, -1, -4])
|
||||
[[-1, -1, 2], [-1, 0, 1]]
|
||||
>>> find_triplets_with_0_sum([])
|
||||
[]
|
||||
>>> find_triplets_with_0_sum([0, 0, 0])
|
||||
[[0, 0, 0]]
|
||||
>>> find_triplets_with_0_sum([1, 2, 3, 0, -1, -2, -3])
|
||||
[[-3, 0, 3], [-3, 1, 2], [-2, -1, 3], [-2, 0, 2], [-1, 0, 1]]
|
||||
"""
|
||||
return [
|
||||
list(x)
|
||||
for x in sorted({abc for abc in combinations(sorted(nums), 3) if not sum(abc)})
|
||||
]
|
||||
|
||||
|
||||
def find_triplets_with_0_sum_hashing(arr: list[int]) -> list[list[int]]:
|
||||
"""
|
||||
Function for finding the triplets with a given sum in the array using hashing.
|
||||
|
||||
Given a list of integers, return elements a, b, c such that a + b + c = 0.
|
||||
|
||||
Args:
|
||||
nums: list of integers
|
||||
Returns:
|
||||
list of lists of integers where sum(each_list) == 0
|
||||
Examples:
|
||||
>>> find_triplets_with_0_sum_hashing([-1, 0, 1, 2, -1, -4])
|
||||
[[-1, 0, 1], [-1, -1, 2]]
|
||||
>>> find_triplets_with_0_sum_hashing([])
|
||||
[]
|
||||
>>> find_triplets_with_0_sum_hashing([0, 0, 0])
|
||||
[[0, 0, 0]]
|
||||
>>> find_triplets_with_0_sum_hashing([1, 2, 3, 0, -1, -2, -3])
|
||||
[[-1, 0, 1], [-3, 1, 2], [-2, 0, 2], [-2, -1, 3], [-3, 0, 3]]
|
||||
|
||||
Time complexity: O(N^2)
|
||||
Auxiliary Space: O(N)
|
||||
|
||||
"""
|
||||
target_sum = 0
|
||||
|
||||
# Initialize the final output array with blank.
|
||||
output_arr = []
|
||||
|
||||
# Set the initial element as arr[i].
|
||||
for index, item in enumerate(arr[:-2]):
|
||||
# to store second elements that can complement the final sum.
|
||||
set_initialize = set()
|
||||
|
||||
# current sum needed for reaching the target sum
|
||||
current_sum = target_sum - item
|
||||
|
||||
# Traverse the subarray arr[i+1:].
|
||||
for other_item in arr[index + 1 :]:
|
||||
# required value for the second element
|
||||
required_value = current_sum - other_item
|
||||
|
||||
# Verify if the desired value exists in the set.
|
||||
if required_value in set_initialize:
|
||||
# finding triplet elements combination.
|
||||
combination_array = sorted([item, other_item, required_value])
|
||||
if combination_array not in output_arr:
|
||||
output_arr.append(combination_array)
|
||||
|
||||
# Include the current element in the set
|
||||
# for subsequent complement verification.
|
||||
set_initialize.add(other_item)
|
||||
|
||||
# Return all the triplet combinations.
|
||||
return output_arr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
105
data_structures/arrays/index_2d_array_in_1d.py
Normal file
105
data_structures/arrays/index_2d_array_in_1d.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
"""
|
||||
Retrieves the value of an 0-indexed 1D index from a 2D array.
|
||||
There are two ways to retrieve value(s):
|
||||
|
||||
1. Index2DArrayIterator(matrix) -> Iterator[int]
|
||||
This iterator allows you to iterate through a 2D array by passing in the matrix and
|
||||
calling next(your_iterator). You can also use the iterator in a loop.
|
||||
Examples:
|
||||
list(Index2DArrayIterator(matrix))
|
||||
set(Index2DArrayIterator(matrix))
|
||||
tuple(Index2DArrayIterator(matrix))
|
||||
sum(Index2DArrayIterator(matrix))
|
||||
-5 in Index2DArrayIterator(matrix)
|
||||
|
||||
2. index_2d_array_in_1d(array: list[int], index: int) -> int
|
||||
This function allows you to provide a 2D array and a 0-indexed 1D integer index,
|
||||
and retrieves the integer value at that index.
|
||||
|
||||
Python doctests can be run using this command:
|
||||
python3 -m doctest -v index_2d_array_in_1d.py
|
||||
"""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Index2DArrayIterator:
|
||||
matrix: list[list[int]]
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
"""
|
||||
>>> tuple(Index2DArrayIterator([[5], [-523], [-1], [34], [0]]))
|
||||
(5, -523, -1, 34, 0)
|
||||
>>> tuple(Index2DArrayIterator([[5, -523, -1], [34, 0]]))
|
||||
(5, -523, -1, 34, 0)
|
||||
>>> tuple(Index2DArrayIterator([[5, -523, -1, 34, 0]]))
|
||||
(5, -523, -1, 34, 0)
|
||||
>>> t = Index2DArrayIterator([[5, 2, 25], [23, 14, 5], [324, -1, 0]])
|
||||
>>> tuple(t)
|
||||
(5, 2, 25, 23, 14, 5, 324, -1, 0)
|
||||
>>> list(t)
|
||||
[5, 2, 25, 23, 14, 5, 324, -1, 0]
|
||||
>>> sorted(t)
|
||||
[-1, 0, 2, 5, 5, 14, 23, 25, 324]
|
||||
>>> tuple(t)[3]
|
||||
23
|
||||
>>> sum(t)
|
||||
397
|
||||
>>> -1 in t
|
||||
True
|
||||
>>> t = iter(Index2DArrayIterator([[5], [-523], [-1], [34], [0]]))
|
||||
>>> next(t)
|
||||
5
|
||||
>>> next(t)
|
||||
-523
|
||||
"""
|
||||
for row in self.matrix:
|
||||
yield from row
|
||||
|
||||
|
||||
def index_2d_array_in_1d(array: list[list[int]], index: int) -> int:
|
||||
"""
|
||||
Retrieves the value of the one-dimensional index from a two-dimensional array.
|
||||
|
||||
Args:
|
||||
array: A 2D array of integers where all rows are the same size and all
|
||||
columns are the same size.
|
||||
index: A 1D index.
|
||||
|
||||
Returns:
|
||||
int: The 0-indexed value of the 1D index in the array.
|
||||
|
||||
Examples:
|
||||
>>> index_2d_array_in_1d([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], 5)
|
||||
5
|
||||
>>> index_2d_array_in_1d([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], -1)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: index out of range
|
||||
>>> index_2d_array_in_1d([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], 12)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: index out of range
|
||||
>>> index_2d_array_in_1d([[]], 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: no items in array
|
||||
"""
|
||||
rows = len(array)
|
||||
cols = len(array[0])
|
||||
|
||||
if rows == 0 or cols == 0:
|
||||
raise ValueError("no items in array")
|
||||
|
||||
if index < 0 or index >= rows * cols:
|
||||
raise ValueError("index out of range")
|
||||
|
||||
return array[index // cols][index % cols]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
117
data_structures/arrays/kth_largest_element.py
Normal file
117
data_structures/arrays/kth_largest_element.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
"""
|
||||
Given an array of integers and an integer k, find the kth largest element in the array.
|
||||
|
||||
https://stackoverflow.com/questions/251781
|
||||
"""
|
||||
|
||||
|
||||
def partition(arr: list[int], low: int, high: int) -> int:
|
||||
"""
|
||||
Partitions list based on the pivot element.
|
||||
|
||||
This function rearranges the elements in the input list 'elements' such that
|
||||
all elements greater than or equal to the chosen pivot are on the right side
|
||||
of the pivot, and all elements smaller than the pivot are on the left side.
|
||||
|
||||
Args:
|
||||
arr: The list to be partitioned
|
||||
low: The lower index of the list
|
||||
high: The higher index of the list
|
||||
|
||||
Returns:
|
||||
int: The index of pivot element after partitioning
|
||||
|
||||
Examples:
|
||||
>>> partition([3, 1, 4, 5, 9, 2, 6, 5, 3, 5], 0, 9)
|
||||
4
|
||||
>>> partition([7, 1, 4, 5, 9, 2, 6, 5, 8], 0, 8)
|
||||
1
|
||||
>>> partition(['apple', 'cherry', 'date', 'banana'], 0, 3)
|
||||
2
|
||||
>>> partition([3.1, 1.2, 5.6, 4.7], 0, 3)
|
||||
1
|
||||
"""
|
||||
pivot = arr[high]
|
||||
i = low - 1
|
||||
for j in range(low, high):
|
||||
if arr[j] >= pivot:
|
||||
i += 1
|
||||
arr[i], arr[j] = arr[j], arr[i]
|
||||
arr[i + 1], arr[high] = arr[high], arr[i + 1]
|
||||
return i + 1
|
||||
|
||||
|
||||
def kth_largest_element(arr: list[int], position: int) -> int:
|
||||
"""
|
||||
Finds the kth largest element in a list.
|
||||
Should deliver similar results to:
|
||||
```python
|
||||
def kth_largest_element(arr, position):
|
||||
return sorted(arr)[-position]
|
||||
```
|
||||
|
||||
Args:
|
||||
nums: The list of numbers.
|
||||
k: The position of the desired kth largest element.
|
||||
|
||||
Returns:
|
||||
int: The kth largest element.
|
||||
|
||||
Examples:
|
||||
>>> kth_largest_element([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5], 3)
|
||||
5
|
||||
>>> kth_largest_element([2, 5, 6, 1, 9, 3, 8, 4, 7, 3, 5], 1)
|
||||
9
|
||||
>>> kth_largest_element([2, 5, 6, 1, 9, 3, 8, 4, 7, 3, 5], -2)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid value of 'position'
|
||||
>>> kth_largest_element([9, 1, 3, 6, 7, 9, 8, 4, 2, 4, 9], 110)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid value of 'position'
|
||||
>>> kth_largest_element([1, 2, 4, 3, 5, 9, 7, 6, 5, 9, 3], 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Invalid value of 'position'
|
||||
>>> kth_largest_element(['apple', 'cherry', 'date', 'banana'], 2)
|
||||
'cherry'
|
||||
>>> kth_largest_element([3.1, 1.2, 5.6, 4.7,7.9,5,0], 2)
|
||||
5.6
|
||||
>>> kth_largest_element([-2, -5, -4, -1], 1)
|
||||
-1
|
||||
>>> kth_largest_element([], 1)
|
||||
-1
|
||||
>>> kth_largest_element([3.1, 1.2, 5.6, 4.7, 7.9, 5, 0], 1.5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: The position should be an integer
|
||||
>>> kth_largest_element((4, 6, 1, 2), 4)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: 'tuple' object does not support item assignment
|
||||
"""
|
||||
if not arr:
|
||||
return -1
|
||||
if not isinstance(position, int):
|
||||
raise ValueError("The position should be an integer")
|
||||
if not 1 <= position <= len(arr):
|
||||
raise ValueError("Invalid value of 'position'")
|
||||
low, high = 0, len(arr) - 1
|
||||
while low <= high:
|
||||
if low > len(arr) - 1 or high < 0:
|
||||
return -1
|
||||
pivot_index = partition(arr, low, high)
|
||||
if pivot_index == position - 1:
|
||||
return arr[pivot_index]
|
||||
elif pivot_index > position - 1:
|
||||
high = pivot_index - 1
|
||||
else:
|
||||
low = pivot_index + 1
|
||||
return -1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
61
data_structures/arrays/median_two_array.py
Normal file
61
data_structures/arrays/median_two_array.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
"""
|
||||
https://www.enjoyalgorithms.com/blog/median-of-two-sorted-arrays
|
||||
"""
|
||||
|
||||
|
||||
def find_median_sorted_arrays(nums1: list[int], nums2: list[int]) -> float:
|
||||
"""
|
||||
Find the median of two arrays.
|
||||
|
||||
Args:
|
||||
nums1: The first array.
|
||||
nums2: The second array.
|
||||
|
||||
Returns:
|
||||
The median of the two arrays.
|
||||
|
||||
Examples:
|
||||
>>> find_median_sorted_arrays([1, 3], [2])
|
||||
2.0
|
||||
|
||||
>>> find_median_sorted_arrays([1, 2], [3, 4])
|
||||
2.5
|
||||
|
||||
>>> find_median_sorted_arrays([0, 0], [0, 0])
|
||||
0.0
|
||||
|
||||
>>> find_median_sorted_arrays([], [])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Both input arrays are empty.
|
||||
|
||||
>>> find_median_sorted_arrays([], [1])
|
||||
1.0
|
||||
|
||||
>>> find_median_sorted_arrays([-1000], [1000])
|
||||
0.0
|
||||
|
||||
>>> find_median_sorted_arrays([-1.1, -2.2], [-3.3, -4.4])
|
||||
-2.75
|
||||
"""
|
||||
if not nums1 and not nums2:
|
||||
raise ValueError("Both input arrays are empty.")
|
||||
|
||||
# Merge the arrays into a single sorted array.
|
||||
merged = sorted(nums1 + nums2)
|
||||
total = len(merged)
|
||||
|
||||
if total % 2 == 1: # If the total number of elements is odd
|
||||
return float(merged[total // 2]) # then return the middle element
|
||||
|
||||
# If the total number of elements is even, calculate
|
||||
# the average of the two middle elements as the median.
|
||||
middle1 = merged[total // 2 - 1]
|
||||
middle2 = merged[total // 2]
|
||||
return (float(middle1) + float(middle2)) / 2.0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
23
data_structures/arrays/monotonic_array.py
Normal file
23
data_structures/arrays/monotonic_array.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# https://leetcode.com/problems/monotonic-array/
|
||||
def is_monotonic(nums: list[int]) -> bool:
|
||||
"""
|
||||
Check if a list is monotonic.
|
||||
|
||||
>>> is_monotonic([1, 2, 2, 3])
|
||||
True
|
||||
>>> is_monotonic([6, 5, 4, 4])
|
||||
True
|
||||
>>> is_monotonic([1, 3, 2])
|
||||
False
|
||||
"""
|
||||
return all(nums[i] <= nums[i + 1] for i in range(len(nums) - 1)) or all(
|
||||
nums[i] >= nums[i + 1] for i in range(len(nums) - 1)
|
||||
)
|
||||
|
||||
|
||||
# Test the function with your examples
|
||||
if __name__ == "__main__":
|
||||
# Test the function with your examples
|
||||
print(is_monotonic([1, 2, 2, 3])) # Output: True
|
||||
print(is_monotonic([6, 5, 4, 4])) # Output: True
|
||||
print(is_monotonic([1, 3, 2])) # Output: False
|
28
data_structures/arrays/pairs_with_given_sum.py
Normal file
28
data_structures/arrays/pairs_with_given_sum.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Given an array of integers and an integer req_sum, find the number of pairs of array
|
||||
elements whose sum is equal to req_sum.
|
||||
|
||||
https://practice.geeksforgeeks.org/problems/count-pairs-with-given-sum5022/0
|
||||
"""
|
||||
from itertools import combinations
|
||||
|
||||
|
||||
def pairs_with_sum(arr: list, req_sum: int) -> int:
|
||||
"""
|
||||
Return the no. of pairs with sum "sum"
|
||||
>>> pairs_with_sum([1, 5, 7, 1], 6)
|
||||
2
|
||||
>>> pairs_with_sum([1, 1, 1, 1, 1, 1, 1, 1], 2)
|
||||
28
|
||||
>>> pairs_with_sum([1, 7, 6, 2, 5, 4, 3, 1, 9, 8], 7)
|
||||
4
|
||||
"""
|
||||
return len([1 for a, b in combinations(arr, 2) if a + b == req_sum])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
94
data_structures/arrays/sparse_table.py
Normal file
94
data_structures/arrays/sparse_table.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
"""
|
||||
Sparse table is a data structure that allows answering range queries on
|
||||
a static number list, i.e. the elements do not change throughout all the queries.
|
||||
|
||||
The implementation below will solve the problem of Range Minimum Query:
|
||||
Finding the minimum value of a subset [L..R] of a static number list.
|
||||
|
||||
Overall time complexity: O(nlogn)
|
||||
Overall space complexity: O(nlogn)
|
||||
|
||||
Wikipedia link: https://en.wikipedia.org/wiki/Range_minimum_query
|
||||
"""
|
||||
from math import log2
|
||||
|
||||
|
||||
def build_sparse_table(number_list: list[int]) -> list[list[int]]:
|
||||
"""
|
||||
Precompute range minimum queries with power of two length and store the precomputed
|
||||
values in a table.
|
||||
|
||||
>>> build_sparse_table([8, 1, 0, 3, 4, 9, 3])
|
||||
[[8, 1, 0, 3, 4, 9, 3], [1, 0, 0, 3, 4, 3, 0], [0, 0, 0, 3, 0, 0, 0]]
|
||||
>>> build_sparse_table([3, 1, 9])
|
||||
[[3, 1, 9], [1, 1, 0]]
|
||||
>>> build_sparse_table([])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: empty number list not allowed
|
||||
"""
|
||||
if not number_list:
|
||||
raise ValueError("empty number list not allowed")
|
||||
|
||||
length = len(number_list)
|
||||
# Initialise sparse_table -- sparse_table[j][i] represents the minimum value of the
|
||||
# subset of length (2 ** j) of number_list, starting from index i.
|
||||
|
||||
# smallest power of 2 subset length that fully covers number_list
|
||||
row = int(log2(length)) + 1
|
||||
sparse_table = [[0 for i in range(length)] for j in range(row)]
|
||||
|
||||
# minimum of subset of length 1 is that value itself
|
||||
for i, value in enumerate(number_list):
|
||||
sparse_table[0][i] = value
|
||||
j = 1
|
||||
|
||||
# compute the minimum value for all intervals with size (2 ** j)
|
||||
while (1 << j) <= length:
|
||||
i = 0
|
||||
# while subset starting from i still have at least (2 ** j) elements
|
||||
while (i + (1 << j) - 1) < length:
|
||||
# split range [i, i + 2 ** j] and find minimum of 2 halves
|
||||
sparse_table[j][i] = min(
|
||||
sparse_table[j - 1][i + (1 << (j - 1))], sparse_table[j - 1][i]
|
||||
)
|
||||
i += 1
|
||||
j += 1
|
||||
return sparse_table
|
||||
|
||||
|
||||
def query(sparse_table: list[list[int]], left_bound: int, right_bound: int) -> int:
|
||||
"""
|
||||
>>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 4)
|
||||
0
|
||||
>>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 4, 6)
|
||||
3
|
||||
>>> query(build_sparse_table([3, 1, 9]), 2, 2)
|
||||
9
|
||||
>>> query(build_sparse_table([3, 1, 9]), 0, 1)
|
||||
1
|
||||
>>> query(build_sparse_table([8, 1, 0, 3, 4, 9, 3]), 0, 11)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: list index out of range
|
||||
>>> query(build_sparse_table([]), 0, 0)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: empty number list not allowed
|
||||
"""
|
||||
if left_bound < 0 or right_bound >= len(sparse_table[0]):
|
||||
raise IndexError("list index out of range")
|
||||
|
||||
# highest subset length of power of 2 that is within range [left_bound, right_bound]
|
||||
j = int(log2(right_bound - left_bound + 1))
|
||||
|
||||
# minimum of 2 overlapping smaller subsets:
|
||||
# [left_bound, left_bound + 2 ** j - 1] and [right_bound - 2 ** j + 1, right_bound]
|
||||
return min(sparse_table[j][right_bound - (1 << j) + 1], sparse_table[j][left_bound])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
print(f"{query(build_sparse_table([3, 1, 9]), 2, 2) = }")
|
220
data_structures/arrays/sudoku_solver.py
Normal file
220
data_structures/arrays/sudoku_solver.py
Normal file
|
@ -0,0 +1,220 @@
|
|||
"""
|
||||
Please do not modify this file! It is published at https://norvig.com/sudoku.html with
|
||||
only minimal changes to work with modern versions of Python. If you have improvements,
|
||||
please make them in a separate file.
|
||||
"""
|
||||
import random
|
||||
import time
|
||||
|
||||
|
||||
def cross(items_a, items_b):
|
||||
"Cross product of elements in A and elements in B."
|
||||
return [a + b for a in items_a for b in items_b]
|
||||
|
||||
|
||||
digits = "123456789"
|
||||
rows = "ABCDEFGHI"
|
||||
cols = digits
|
||||
squares = cross(rows, cols)
|
||||
unitlist = (
|
||||
[cross(rows, c) for c in cols]
|
||||
+ [cross(r, cols) for r in rows]
|
||||
+ [cross(rs, cs) for rs in ("ABC", "DEF", "GHI") for cs in ("123", "456", "789")]
|
||||
)
|
||||
units = {s: [u for u in unitlist if s in u] for s in squares}
|
||||
peers = {s: set(sum(units[s], [])) - {s} for s in squares}
|
||||
|
||||
|
||||
def test():
|
||||
"A set of unit tests."
|
||||
assert len(squares) == 81
|
||||
assert len(unitlist) == 27
|
||||
assert all(len(units[s]) == 3 for s in squares)
|
||||
assert all(len(peers[s]) == 20 for s in squares)
|
||||
assert units["C2"] == [
|
||||
["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "I2"],
|
||||
["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9"],
|
||||
["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"],
|
||||
]
|
||||
# fmt: off
|
||||
assert peers["C2"] == {
|
||||
"A2", "B2", "D2", "E2", "F2", "G2", "H2", "I2", "C1", "C3",
|
||||
"C4", "C5", "C6", "C7", "C8", "C9", "A1", "A3", "B1", "B3"
|
||||
}
|
||||
# fmt: on
|
||||
print("All tests pass.")
|
||||
|
||||
|
||||
def parse_grid(grid):
|
||||
"""Convert grid to a dict of possible values, {square: digits}, or
|
||||
return False if a contradiction is detected."""
|
||||
## To start, every square can be any digit; then assign values from the grid.
|
||||
values = {s: digits for s in squares}
|
||||
for s, d in grid_values(grid).items():
|
||||
if d in digits and not assign(values, s, d):
|
||||
return False ## (Fail if we can't assign d to square s.)
|
||||
return values
|
||||
|
||||
|
||||
def grid_values(grid):
|
||||
"Convert grid into a dict of {square: char} with '0' or '.' for empties."
|
||||
chars = [c for c in grid if c in digits or c in "0."]
|
||||
assert len(chars) == 81
|
||||
return dict(zip(squares, chars))
|
||||
|
||||
|
||||
def assign(values, s, d):
|
||||
"""Eliminate all the other values (except d) from values[s] and propagate.
|
||||
Return values, except return False if a contradiction is detected."""
|
||||
other_values = values[s].replace(d, "")
|
||||
if all(eliminate(values, s, d2) for d2 in other_values):
|
||||
return values
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def eliminate(values, s, d):
|
||||
"""Eliminate d from values[s]; propagate when values or places <= 2.
|
||||
Return values, except return False if a contradiction is detected."""
|
||||
if d not in values[s]:
|
||||
return values ## Already eliminated
|
||||
values[s] = values[s].replace(d, "")
|
||||
## (1) If a square s is reduced to one value d2, then eliminate d2 from the peers.
|
||||
if len(values[s]) == 0:
|
||||
return False ## Contradiction: removed last value
|
||||
elif len(values[s]) == 1:
|
||||
d2 = values[s]
|
||||
if not all(eliminate(values, s2, d2) for s2 in peers[s]):
|
||||
return False
|
||||
## (2) If a unit u is reduced to only one place for a value d, then put it there.
|
||||
for u in units[s]:
|
||||
dplaces = [s for s in u if d in values[s]]
|
||||
if len(dplaces) == 0:
|
||||
return False ## Contradiction: no place for this value
|
||||
elif len(dplaces) == 1:
|
||||
# d can only be in one place in unit; assign it there
|
||||
if not assign(values, dplaces[0], d):
|
||||
return False
|
||||
return values
|
||||
|
||||
|
||||
def display(values):
|
||||
"Display these values as a 2-D grid."
|
||||
width = 1 + max(len(values[s]) for s in squares)
|
||||
line = "+".join(["-" * (width * 3)] * 3)
|
||||
for r in rows:
|
||||
print(
|
||||
"".join(
|
||||
values[r + c].center(width) + ("|" if c in "36" else "") for c in cols
|
||||
)
|
||||
)
|
||||
if r in "CF":
|
||||
print(line)
|
||||
print()
|
||||
|
||||
|
||||
def solve(grid):
|
||||
return search(parse_grid(grid))
|
||||
|
||||
|
||||
def some(seq):
|
||||
"Return some element of seq that is true."
|
||||
for e in seq:
|
||||
if e:
|
||||
return e
|
||||
return False
|
||||
|
||||
|
||||
def search(values):
|
||||
"Using depth-first search and propagation, try all possible values."
|
||||
if values is False:
|
||||
return False ## Failed earlier
|
||||
if all(len(values[s]) == 1 for s in squares):
|
||||
return values ## Solved!
|
||||
## Chose the unfilled square s with the fewest possibilities
|
||||
n, s = min((len(values[s]), s) for s in squares if len(values[s]) > 1)
|
||||
return some(search(assign(values.copy(), s, d)) for d in values[s])
|
||||
|
||||
|
||||
def solve_all(grids, name="", showif=0.0):
|
||||
"""Attempt to solve a sequence of grids. Report results.
|
||||
When showif is a number of seconds, display puzzles that take longer.
|
||||
When showif is None, don't display any puzzles."""
|
||||
|
||||
def time_solve(grid):
|
||||
start = time.monotonic()
|
||||
values = solve(grid)
|
||||
t = time.monotonic() - start
|
||||
## Display puzzles that take long enough
|
||||
if showif is not None and t > showif:
|
||||
display(grid_values(grid))
|
||||
if values:
|
||||
display(values)
|
||||
print("(%.5f seconds)\n" % t)
|
||||
return (t, solved(values))
|
||||
|
||||
times, results = zip(*[time_solve(grid) for grid in grids])
|
||||
if (n := len(grids)) > 1:
|
||||
print(
|
||||
"Solved %d of %d %s puzzles (avg %.2f secs (%d Hz), max %.2f secs)."
|
||||
% (sum(results), n, name, sum(times) / n, n / sum(times), max(times))
|
||||
)
|
||||
|
||||
|
||||
def solved(values):
|
||||
"A puzzle is solved if each unit is a permutation of the digits 1 to 9."
|
||||
|
||||
def unitsolved(unit):
|
||||
return {values[s] for s in unit} == set(digits)
|
||||
|
||||
return values is not False and all(unitsolved(unit) for unit in unitlist)
|
||||
|
||||
|
||||
def from_file(filename, sep="\n"):
|
||||
"Parse a file into a list of strings, separated by sep."
|
||||
return open(filename).read().strip().split(sep) # noqa: SIM115
|
||||
|
||||
|
||||
def random_puzzle(assignments=17):
|
||||
"""Make a random puzzle with N or more assignments. Restart on contradictions.
|
||||
Note the resulting puzzle is not guaranteed to be solvable, but empirically
|
||||
about 99.8% of them are solvable. Some have multiple solutions."""
|
||||
values = {s: digits for s in squares}
|
||||
for s in shuffled(squares):
|
||||
if not assign(values, s, random.choice(values[s])):
|
||||
break
|
||||
ds = [values[s] for s in squares if len(values[s]) == 1]
|
||||
if len(ds) >= assignments and len(set(ds)) >= 8:
|
||||
return "".join(values[s] if len(values[s]) == 1 else "." for s in squares)
|
||||
return random_puzzle(assignments) ## Give up and make a new puzzle
|
||||
|
||||
|
||||
def shuffled(seq):
|
||||
"Return a randomly shuffled copy of the input sequence."
|
||||
seq = list(seq)
|
||||
random.shuffle(seq)
|
||||
return seq
|
||||
|
||||
|
||||
grid1 = (
|
||||
"003020600900305001001806400008102900700000008006708200002609500800203009005010300"
|
||||
)
|
||||
grid2 = (
|
||||
"4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......"
|
||||
)
|
||||
hard1 = (
|
||||
".....6....59.....82....8....45........3........6..3.54...325..6.................."
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
||||
# solve_all(from_file("easy50.txt", '========'), "easy", None)
|
||||
# solve_all(from_file("top95.txt"), "hard", None)
|
||||
# solve_all(from_file("hardest.txt"), "hardest", None)
|
||||
solve_all([random_puzzle() for _ in range(99)], "random", 100.0)
|
||||
for puzzle in (grid1, grid2): # , hard1): # Takes 22 sec to solve on my M1 Mac.
|
||||
display(parse_grid(puzzle))
|
||||
start = time.monotonic()
|
||||
solve(puzzle)
|
||||
t = time.monotonic() - start
|
||||
print("Solved: %.5f sec" % t)
|
|
@ -1,101 +1,110 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
"""
|
||||
A Node has data variable and pointers to Nodes to its left and right.
|
||||
"""
|
||||
data: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
def __init__(self, data: int) -> None:
|
||||
self.data = data
|
||||
self.left: Node | None = None
|
||||
self.right: Node | None = None
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
if self.left:
|
||||
yield from self.left
|
||||
yield self.data
|
||||
if self.right:
|
||||
yield from self.right
|
||||
|
||||
def __len__(self) -> int:
|
||||
return sum(1 for _ in self)
|
||||
|
||||
def is_full(self) -> bool:
|
||||
if not self or (not self.left and not self.right):
|
||||
return True
|
||||
if self.left and self.right:
|
||||
return self.left.is_full() and self.right.is_full()
|
||||
return False
|
||||
|
||||
|
||||
def display(tree: Node | None) -> None: # In Order traversal of the tree
|
||||
"""
|
||||
>>> root = Node(1)
|
||||
>>> root.left = Node(0)
|
||||
>>> root.right = Node(2)
|
||||
>>> display(root)
|
||||
0
|
||||
1
|
||||
2
|
||||
>>> display(root.right)
|
||||
2
|
||||
"""
|
||||
if tree:
|
||||
display(tree.left)
|
||||
print(tree.data)
|
||||
display(tree.right)
|
||||
@dataclass
|
||||
class BinaryTree:
|
||||
root: Node
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
return iter(self.root)
|
||||
|
||||
def depth_of_tree(tree: Node | None) -> int:
|
||||
"""
|
||||
Recursive function that returns the depth of a binary tree.
|
||||
def __len__(self) -> int:
|
||||
return len(self.root)
|
||||
|
||||
>>> root = Node(0)
|
||||
>>> depth_of_tree(root)
|
||||
1
|
||||
>>> root.left = Node(0)
|
||||
>>> depth_of_tree(root)
|
||||
2
|
||||
>>> root.right = Node(0)
|
||||
>>> depth_of_tree(root)
|
||||
2
|
||||
>>> root.left.right = Node(0)
|
||||
>>> depth_of_tree(root)
|
||||
3
|
||||
>>> depth_of_tree(root.left)
|
||||
2
|
||||
"""
|
||||
return 1 + max(depth_of_tree(tree.left), depth_of_tree(tree.right)) if tree else 0
|
||||
@classmethod
|
||||
def small_tree(cls) -> BinaryTree:
|
||||
"""
|
||||
Return a small binary tree with 3 nodes.
|
||||
>>> binary_tree = BinaryTree.small_tree()
|
||||
>>> len(binary_tree)
|
||||
3
|
||||
>>> list(binary_tree)
|
||||
[1, 2, 3]
|
||||
"""
|
||||
binary_tree = BinaryTree(Node(2))
|
||||
binary_tree.root.left = Node(1)
|
||||
binary_tree.root.right = Node(3)
|
||||
return binary_tree
|
||||
|
||||
@classmethod
|
||||
def medium_tree(cls) -> BinaryTree:
|
||||
"""
|
||||
Return a medium binary tree with 3 nodes.
|
||||
>>> binary_tree = BinaryTree.medium_tree()
|
||||
>>> len(binary_tree)
|
||||
7
|
||||
>>> list(binary_tree)
|
||||
[1, 2, 3, 4, 5, 6, 7]
|
||||
"""
|
||||
binary_tree = BinaryTree(Node(4))
|
||||
binary_tree.root.left = two = Node(2)
|
||||
two.left = Node(1)
|
||||
two.right = Node(3)
|
||||
binary_tree.root.right = five = Node(5)
|
||||
five.right = six = Node(6)
|
||||
six.right = Node(7)
|
||||
return binary_tree
|
||||
|
||||
def is_full_binary_tree(tree: Node) -> bool:
|
||||
"""
|
||||
Returns True if this is a full binary tree
|
||||
def depth(self) -> int:
|
||||
"""
|
||||
Returns the depth of the tree
|
||||
|
||||
>>> root = Node(0)
|
||||
>>> is_full_binary_tree(root)
|
||||
True
|
||||
>>> root.left = Node(0)
|
||||
>>> is_full_binary_tree(root)
|
||||
False
|
||||
>>> root.right = Node(0)
|
||||
>>> is_full_binary_tree(root)
|
||||
True
|
||||
>>> root.left.left = Node(0)
|
||||
>>> is_full_binary_tree(root)
|
||||
False
|
||||
>>> root.right.right = Node(0)
|
||||
>>> is_full_binary_tree(root)
|
||||
False
|
||||
"""
|
||||
if not tree:
|
||||
return True
|
||||
if tree.left and tree.right:
|
||||
return is_full_binary_tree(tree.left) and is_full_binary_tree(tree.right)
|
||||
else:
|
||||
return not tree.left and not tree.right
|
||||
>>> BinaryTree(Node(1)).depth()
|
||||
1
|
||||
>>> BinaryTree.small_tree().depth()
|
||||
2
|
||||
>>> BinaryTree.medium_tree().depth()
|
||||
4
|
||||
"""
|
||||
return self._depth(self.root)
|
||||
|
||||
def _depth(self, node: Node | None) -> int: # noqa: UP007
|
||||
if not node:
|
||||
return 0
|
||||
return 1 + max(self._depth(node.left), self._depth(node.right))
|
||||
|
||||
def main() -> None: # Main function for testing.
|
||||
tree = Node(1)
|
||||
tree.left = Node(2)
|
||||
tree.right = Node(3)
|
||||
tree.left.left = Node(4)
|
||||
tree.left.right = Node(5)
|
||||
tree.left.right.left = Node(6)
|
||||
tree.right.left = Node(7)
|
||||
tree.right.left.left = Node(8)
|
||||
tree.right.left.left.right = Node(9)
|
||||
def is_full(self) -> bool:
|
||||
"""
|
||||
Returns True if the tree is full
|
||||
|
||||
print(is_full_binary_tree(tree))
|
||||
print(depth_of_tree(tree))
|
||||
print("Tree is: ")
|
||||
display(tree)
|
||||
>>> BinaryTree(Node(1)).is_full()
|
||||
True
|
||||
>>> BinaryTree.small_tree().is_full()
|
||||
True
|
||||
>>> BinaryTree.medium_tree().is_full()
|
||||
False
|
||||
"""
|
||||
return self.root.is_full()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -10,10 +10,19 @@ Example
|
|||
/ \ /
|
||||
4 7 13
|
||||
|
||||
>>> t = BinarySearchTree()
|
||||
>>> t.insert(8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
>>> t = BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
>>> print(" ".join(repr(i.value) for i in t.traversal_tree()))
|
||||
8 3 1 6 4 7 10 14 13
|
||||
|
||||
>>> tuple(i.value for i in t.traversal_tree(inorder))
|
||||
(1, 3, 4, 6, 7, 8, 10, 13, 14)
|
||||
>>> tuple(t)
|
||||
(1, 3, 4, 6, 7, 8, 10, 13, 14)
|
||||
>>> t.find_kth_smallest(3, t.root)
|
||||
4
|
||||
>>> tuple(t)[3-1]
|
||||
4
|
||||
|
||||
>>> print(" ".join(repr(i.value) for i in t.traversal_tree(postorder)))
|
||||
1 4 7 6 3 13 14 10 8
|
||||
>>> t.remove(20)
|
||||
|
@ -30,7 +39,16 @@ Other example:
|
|||
>>> testlist = (8, 3, 6, 1, 10, 14, 13, 4, 7)
|
||||
>>> t = BinarySearchTree()
|
||||
>>> for i in testlist:
|
||||
... t.insert(i)
|
||||
... t.insert(i) # doctest: +ELLIPSIS
|
||||
BinarySearchTree(root=8)
|
||||
BinarySearchTree(root={'8': (3, None)})
|
||||
BinarySearchTree(root={'8': ({'3': (None, 6)}, None)})
|
||||
BinarySearchTree(root={'8': ({'3': (1, 6)}, None)})
|
||||
BinarySearchTree(root={'8': ({'3': (1, 6)}, 10)})
|
||||
BinarySearchTree(root={'8': ({'3': (1, 6)}, {'10': (None, 14)})})
|
||||
BinarySearchTree(root={'8': ({'3': (1, 6)}, {'10': (None, {'14': (13, None)})})})
|
||||
BinarySearchTree(root={'8': ({'3': (1, {'6': (4, None)})}, {'10': (None, {'14': ...
|
||||
BinarySearchTree(root={'8': ({'3': (1, {'6': (4, 7)})}, {'10': (None, {'14': (13, ...
|
||||
|
||||
Prints all the elements of the list in order traversal
|
||||
>>> print(t)
|
||||
|
@ -39,8 +57,12 @@ Prints all the elements of the list in order traversal
|
|||
Test existence
|
||||
>>> t.search(6) is not None
|
||||
True
|
||||
>>> 6 in t
|
||||
True
|
||||
>>> t.search(-1) is not None
|
||||
False
|
||||
>>> -1 in t
|
||||
False
|
||||
|
||||
>>> t.search(6).is_right
|
||||
True
|
||||
|
@ -49,26 +71,47 @@ False
|
|||
|
||||
>>> t.get_max().value
|
||||
14
|
||||
>>> max(t)
|
||||
14
|
||||
>>> t.get_min().value
|
||||
1
|
||||
>>> min(t)
|
||||
1
|
||||
>>> t.empty()
|
||||
False
|
||||
>>> not t
|
||||
False
|
||||
>>> for i in testlist:
|
||||
... t.remove(i)
|
||||
>>> t.empty()
|
||||
True
|
||||
>>> not t
|
||||
True
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from typing import Any
|
||||
from collections.abc import Iterable, Iterator
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Self
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
def __init__(self, value: int | None = None):
|
||||
self.value = value
|
||||
self.parent: Node | None = None # Added in order to delete a node easier
|
||||
self.left: Node | None = None
|
||||
self.right: Node | None = None
|
||||
value: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
parent: Node | None = None # Added in order to delete a node easier
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
"""
|
||||
>>> list(Node(0))
|
||||
[0]
|
||||
>>> list(Node(0, Node(-1), Node(1), None))
|
||||
[-1, 0, 1]
|
||||
"""
|
||||
yield from self.left or []
|
||||
yield self.value
|
||||
yield from self.right or []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
from pprint import pformat
|
||||
|
@ -79,12 +122,18 @@ class Node:
|
|||
|
||||
@property
|
||||
def is_right(self) -> bool:
|
||||
return self.parent is not None and self is self.parent.right
|
||||
return bool(self.parent and self is self.parent.right)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BinarySearchTree:
|
||||
def __init__(self, root: Node | None = None):
|
||||
self.root = root
|
||||
root: Node | None = None
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.root)
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
yield from self.root or []
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
|
@ -104,7 +153,18 @@ class BinarySearchTree:
|
|||
self.root = new_children
|
||||
|
||||
def empty(self) -> bool:
|
||||
return self.root is None
|
||||
"""
|
||||
Returns True if the tree does not have any element(s).
|
||||
False if the tree has element(s).
|
||||
|
||||
>>> BinarySearchTree().empty()
|
||||
True
|
||||
>>> BinarySearchTree().insert(1).empty()
|
||||
False
|
||||
>>> BinarySearchTree().insert(8, 3, 6, 1, 10, 14, 13, 4, 7).empty()
|
||||
False
|
||||
"""
|
||||
return not self.root
|
||||
|
||||
def __insert(self, value) -> None:
|
||||
"""
|
||||
|
@ -132,11 +192,36 @@ class BinarySearchTree:
|
|||
parent_node = parent_node.right
|
||||
new_node.parent = parent_node
|
||||
|
||||
def insert(self, *values) -> None:
|
||||
def insert(self, *values) -> Self:
|
||||
for value in values:
|
||||
self.__insert(value)
|
||||
return self
|
||||
|
||||
def search(self, value) -> Node | None:
|
||||
"""
|
||||
>>> tree = BinarySearchTree().insert(10, 20, 30, 40, 50)
|
||||
>>> tree.search(10)
|
||||
{'10': (None, {'20': (None, {'30': (None, {'40': (None, 50)})})})}
|
||||
>>> tree.search(20)
|
||||
{'20': (None, {'30': (None, {'40': (None, 50)})})}
|
||||
>>> tree.search(30)
|
||||
{'30': (None, {'40': (None, 50)})}
|
||||
>>> tree.search(40)
|
||||
{'40': (None, 50)}
|
||||
>>> tree.search(50)
|
||||
50
|
||||
>>> tree.search(5) is None # element not present
|
||||
True
|
||||
>>> tree.search(0) is None # element not present
|
||||
True
|
||||
>>> tree.search(-5) is None # element not present
|
||||
True
|
||||
>>> BinarySearchTree().search(10)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
IndexError: Warning: Tree is empty! please use another.
|
||||
"""
|
||||
|
||||
if self.empty():
|
||||
raise IndexError("Warning: Tree is empty! please use another.")
|
||||
else:
|
||||
|
@ -149,6 +234,15 @@ class BinarySearchTree:
|
|||
def get_max(self, node: Node | None = None) -> Node | None:
|
||||
"""
|
||||
We go deep on the right branch
|
||||
|
||||
>>> BinarySearchTree().insert(10, 20, 30, 40, 50).get_max()
|
||||
50
|
||||
>>> BinarySearchTree().insert(-5, -1, 0.1, -0.3, -4.5).get_max()
|
||||
{'0.1': (-0.3, None)}
|
||||
>>> BinarySearchTree().insert(1, 78.3, 30, 74.0, 1).get_max()
|
||||
{'78.3': ({'30': (1, 74.0)}, None)}
|
||||
>>> BinarySearchTree().insert(1, 783, 30, 740, 1).get_max()
|
||||
{'783': ({'30': (1, 740)}, None)}
|
||||
"""
|
||||
if node is None:
|
||||
if self.root is None:
|
||||
|
@ -163,6 +257,15 @@ class BinarySearchTree:
|
|||
def get_min(self, node: Node | None = None) -> Node | None:
|
||||
"""
|
||||
We go deep on the left branch
|
||||
|
||||
>>> BinarySearchTree().insert(10, 20, 30, 40, 50).get_min()
|
||||
{'10': (None, {'20': (None, {'30': (None, {'40': (None, 50)})})})}
|
||||
>>> BinarySearchTree().insert(-5, -1, 0, -0.3, -4.5).get_min()
|
||||
{'-5': (None, {'-1': (-4.5, {'0': (-0.3, None)})})}
|
||||
>>> BinarySearchTree().insert(1, 78.3, 30, 74.0, 1).get_min()
|
||||
{'1': (None, {'78.3': ({'30': (1, 74.0)}, None)})}
|
||||
>>> BinarySearchTree().insert(1, 783, 30, 740, 1).get_min()
|
||||
{'1': (None, {'783': ({'30': (1, 740)}, None)})}
|
||||
"""
|
||||
if node is None:
|
||||
node = self.root
|
||||
|
@ -227,6 +330,16 @@ class BinarySearchTree:
|
|||
return arr[k - 1]
|
||||
|
||||
|
||||
def inorder(curr_node: Node | None) -> list[Node]:
|
||||
"""
|
||||
inorder (left, self, right)
|
||||
"""
|
||||
node_list = []
|
||||
if curr_node is not None:
|
||||
node_list = inorder(curr_node.left) + [curr_node] + inorder(curr_node.right)
|
||||
return node_list
|
||||
|
||||
|
||||
def postorder(curr_node: Node | None) -> list[Node]:
|
||||
"""
|
||||
postOrder (left, right, self)
|
||||
|
|
|
@ -12,6 +12,8 @@ from __future__ import annotations
|
|||
import unittest
|
||||
from collections.abc import Iterator
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class Node:
|
||||
def __init__(self, label: int, parent: Node | None) -> None:
|
||||
|
@ -78,7 +80,7 @@ class BinarySearchTree:
|
|||
node.right = self._put(node.right, label, node)
|
||||
else:
|
||||
msg = f"Node with label {label} already exists"
|
||||
raise Exception(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
return node
|
||||
|
||||
|
@ -95,14 +97,14 @@ class BinarySearchTree:
|
|||
>>> node = t.search(3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: Node with label 3 does not exist
|
||||
ValueError: Node with label 3 does not exist
|
||||
"""
|
||||
return self._search(self.root, label)
|
||||
|
||||
def _search(self, node: Node | None, label: int) -> Node:
|
||||
if node is None:
|
||||
msg = f"Node with label {label} does not exist"
|
||||
raise Exception(msg)
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
if label < node.label:
|
||||
node = self._search(node.left, label)
|
||||
|
@ -124,7 +126,7 @@ class BinarySearchTree:
|
|||
>>> t.remove(3)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: Node with label 3 does not exist
|
||||
ValueError: Node with label 3 does not exist
|
||||
"""
|
||||
node = self.search(label)
|
||||
if node.right and node.left:
|
||||
|
@ -179,7 +181,7 @@ class BinarySearchTree:
|
|||
try:
|
||||
self.search(label)
|
||||
return True
|
||||
except Exception:
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def get_max_label(self) -> int:
|
||||
|
@ -190,7 +192,7 @@ class BinarySearchTree:
|
|||
>>> t.get_max_label()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: Binary search tree is empty
|
||||
ValueError: Binary search tree is empty
|
||||
|
||||
>>> t.put(8)
|
||||
>>> t.put(10)
|
||||
|
@ -198,7 +200,7 @@ class BinarySearchTree:
|
|||
10
|
||||
"""
|
||||
if self.root is None:
|
||||
raise Exception("Binary search tree is empty")
|
||||
raise ValueError("Binary search tree is empty")
|
||||
|
||||
node = self.root
|
||||
while node.right is not None:
|
||||
|
@ -214,7 +216,7 @@ class BinarySearchTree:
|
|||
>>> t.get_min_label()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Exception: Binary search tree is empty
|
||||
ValueError: Binary search tree is empty
|
||||
|
||||
>>> t.put(8)
|
||||
>>> t.put(10)
|
||||
|
@ -222,7 +224,7 @@ class BinarySearchTree:
|
|||
8
|
||||
"""
|
||||
if self.root is None:
|
||||
raise Exception("Binary search tree is empty")
|
||||
raise ValueError("Binary search tree is empty")
|
||||
|
||||
node = self.root
|
||||
while node.left is not None:
|
||||
|
@ -359,7 +361,7 @@ class BinarySearchTreeTest(unittest.TestCase):
|
|||
assert t.root.left.left.parent == t.root.left
|
||||
assert t.root.left.left.label == 1
|
||||
|
||||
with self.assertRaises(Exception): # noqa: B017
|
||||
with pytest.raises(ValueError):
|
||||
t.put(1)
|
||||
|
||||
def test_search(self) -> None:
|
||||
|
@ -371,7 +373,7 @@ class BinarySearchTreeTest(unittest.TestCase):
|
|||
node = t.search(13)
|
||||
assert node.label == 13
|
||||
|
||||
with self.assertRaises(Exception): # noqa: B017
|
||||
with pytest.raises(ValueError):
|
||||
t.search(2)
|
||||
|
||||
def test_remove(self) -> None:
|
||||
|
@ -517,7 +519,7 @@ class BinarySearchTreeTest(unittest.TestCase):
|
|||
assert t.get_max_label() == 14
|
||||
|
||||
t.empty()
|
||||
with self.assertRaises(Exception): # noqa: B017
|
||||
with pytest.raises(ValueError):
|
||||
t.get_max_label()
|
||||
|
||||
def test_get_min_label(self) -> None:
|
||||
|
@ -526,7 +528,7 @@ class BinarySearchTreeTest(unittest.TestCase):
|
|||
assert t.get_min_label() == 1
|
||||
|
||||
t.empty()
|
||||
with self.assertRaises(Exception): # noqa: B017
|
||||
with pytest.raises(ValueError):
|
||||
t.get_min_label()
|
||||
|
||||
def test_inorder_traversal(self) -> None:
|
||||
|
|
72
data_structures/binary_tree/diameter_of_binary_tree.py
Normal file
72
data_structures/binary_tree/diameter_of_binary_tree.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
"""
|
||||
The diameter/width of a tree is defined as the number of nodes on the longest path
|
||||
between two end nodes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
data: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
def depth(self) -> int:
|
||||
"""
|
||||
>>> root = Node(1)
|
||||
>>> root.depth()
|
||||
1
|
||||
>>> root.left = Node(2)
|
||||
>>> root.depth()
|
||||
2
|
||||
>>> root.left.depth()
|
||||
1
|
||||
>>> root.right = Node(3)
|
||||
>>> root.depth()
|
||||
2
|
||||
"""
|
||||
left_depth = self.left.depth() if self.left else 0
|
||||
right_depth = self.right.depth() if self.right else 0
|
||||
return max(left_depth, right_depth) + 1
|
||||
|
||||
def diameter(self) -> int:
|
||||
"""
|
||||
>>> root = Node(1)
|
||||
>>> root.diameter()
|
||||
1
|
||||
>>> root.left = Node(2)
|
||||
>>> root.diameter()
|
||||
2
|
||||
>>> root.left.diameter()
|
||||
1
|
||||
>>> root.right = Node(3)
|
||||
>>> root.diameter()
|
||||
3
|
||||
"""
|
||||
left_depth = self.left.depth() if self.left else 0
|
||||
right_depth = self.right.depth() if self.right else 0
|
||||
return left_depth + right_depth + 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
root = Node(1)
|
||||
root.left = Node(2)
|
||||
root.right = Node(3)
|
||||
root.left.left = Node(4)
|
||||
root.left.right = Node(5)
|
||||
r"""
|
||||
Constructed binary tree is
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \
|
||||
4 5
|
||||
"""
|
||||
print(f"{root.diameter() = }") # 4
|
||||
print(f"{root.left.diameter() = }") # 3
|
||||
print(f"{root.right.diameter() = }") # 1
|
138
data_structures/binary_tree/flatten_binarytree_to_linkedlist.py
Normal file
138
data_structures/binary_tree/flatten_binarytree_to_linkedlist.py
Normal file
|
@ -0,0 +1,138 @@
|
|||
"""
|
||||
Binary Tree Flattening Algorithm
|
||||
|
||||
This code defines an algorithm to flatten a binary tree into a linked list
|
||||
represented using the right pointers of the tree nodes. It uses in-place
|
||||
flattening and demonstrates the flattening process along with a display
|
||||
function to visualize the flattened linked list.
|
||||
https://www.geeksforgeeks.org/flatten-a-binary-tree-into-linked-list
|
||||
|
||||
Author: Arunkumar A
|
||||
Date: 04/09/2023
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
class TreeNode:
|
||||
"""
|
||||
A TreeNode has data variable and pointers to TreeNode objects
|
||||
for its left and right children.
|
||||
"""
|
||||
|
||||
def __init__(self, data: int) -> None:
|
||||
self.data = data
|
||||
self.left: TreeNode | None = None
|
||||
self.right: TreeNode | None = None
|
||||
|
||||
|
||||
def build_tree() -> TreeNode:
|
||||
"""
|
||||
Build and return a sample binary tree.
|
||||
|
||||
Returns:
|
||||
TreeNode: The root of the binary tree.
|
||||
|
||||
Examples:
|
||||
>>> root = build_tree()
|
||||
>>> root.data
|
||||
1
|
||||
>>> root.left.data
|
||||
2
|
||||
>>> root.right.data
|
||||
5
|
||||
>>> root.left.left.data
|
||||
3
|
||||
>>> root.left.right.data
|
||||
4
|
||||
>>> root.right.right.data
|
||||
6
|
||||
"""
|
||||
root = TreeNode(1)
|
||||
root.left = TreeNode(2)
|
||||
root.right = TreeNode(5)
|
||||
root.left.left = TreeNode(3)
|
||||
root.left.right = TreeNode(4)
|
||||
root.right.right = TreeNode(6)
|
||||
return root
|
||||
|
||||
|
||||
def flatten(root: TreeNode | None) -> None:
|
||||
"""
|
||||
Flatten a binary tree into a linked list in-place, where the linked list is
|
||||
represented using the right pointers of the tree nodes.
|
||||
|
||||
Args:
|
||||
root (TreeNode): The root of the binary tree to be flattened.
|
||||
|
||||
Examples:
|
||||
>>> root = TreeNode(1)
|
||||
>>> root.left = TreeNode(2)
|
||||
>>> root.right = TreeNode(5)
|
||||
>>> root.left.left = TreeNode(3)
|
||||
>>> root.left.right = TreeNode(4)
|
||||
>>> root.right.right = TreeNode(6)
|
||||
>>> flatten(root)
|
||||
>>> root.data
|
||||
1
|
||||
>>> root.right.right is None
|
||||
False
|
||||
>>> root.right.right = TreeNode(3)
|
||||
>>> root.right.right.right is None
|
||||
True
|
||||
"""
|
||||
if not root:
|
||||
return
|
||||
|
||||
# Flatten the left subtree
|
||||
flatten(root.left)
|
||||
|
||||
# Save the right subtree
|
||||
right_subtree = root.right
|
||||
|
||||
# Make the left subtree the new right subtree
|
||||
root.right = root.left
|
||||
root.left = None
|
||||
|
||||
# Find the end of the new right subtree
|
||||
current = root
|
||||
while current.right:
|
||||
current = current.right
|
||||
|
||||
# Append the original right subtree to the end
|
||||
current.right = right_subtree
|
||||
|
||||
# Flatten the updated right subtree
|
||||
flatten(right_subtree)
|
||||
|
||||
|
||||
def display_linked_list(root: TreeNode | None) -> None:
|
||||
"""
|
||||
Display the flattened linked list.
|
||||
|
||||
Args:
|
||||
root (TreeNode | None): The root of the flattened linked list.
|
||||
|
||||
Examples:
|
||||
>>> root = TreeNode(1)
|
||||
>>> root.right = TreeNode(2)
|
||||
>>> root.right.right = TreeNode(3)
|
||||
>>> display_linked_list(root)
|
||||
1 2 3
|
||||
>>> root = None
|
||||
>>> display_linked_list(root)
|
||||
|
||||
"""
|
||||
current = root
|
||||
while current:
|
||||
if current.right is None:
|
||||
print(current.data, end="")
|
||||
break
|
||||
print(current.data, end=" ")
|
||||
current = current.right
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Flattened Linked List:")
|
||||
root = build_tree()
|
||||
flatten(root)
|
||||
display_linked_list(root)
|
87
data_structures/binary_tree/floor_and_ceiling.py
Normal file
87
data_structures/binary_tree/floor_and_ceiling.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
"""
|
||||
In a binary search tree (BST):
|
||||
* The floor of key 'k' is the maximum value that is smaller than or equal to 'k'.
|
||||
* The ceiling of key 'k' is the minimum value that is greater than or equal to 'k'.
|
||||
|
||||
Reference:
|
||||
https://bit.ly/46uB0a2
|
||||
|
||||
Author : Arunkumar
|
||||
Date : 14th October 2023
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
key: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
if self.left:
|
||||
yield from self.left
|
||||
yield self.key
|
||||
if self.right:
|
||||
yield from self.right
|
||||
|
||||
def __len__(self) -> int:
|
||||
return sum(1 for _ in self)
|
||||
|
||||
|
||||
def floor_ceiling(root: Node | None, key: int) -> tuple[int | None, int | None]:
|
||||
"""
|
||||
Find the floor and ceiling values for a given key in a Binary Search Tree (BST).
|
||||
|
||||
Args:
|
||||
root: The root of the binary search tree.
|
||||
key: The key for which to find the floor and ceiling.
|
||||
|
||||
Returns:
|
||||
A tuple containing the floor and ceiling values, respectively.
|
||||
|
||||
Examples:
|
||||
>>> root = Node(10)
|
||||
>>> root.left = Node(5)
|
||||
>>> root.right = Node(20)
|
||||
>>> root.left.left = Node(3)
|
||||
>>> root.left.right = Node(7)
|
||||
>>> root.right.left = Node(15)
|
||||
>>> root.right.right = Node(25)
|
||||
>>> tuple(root)
|
||||
(3, 5, 7, 10, 15, 20, 25)
|
||||
>>> floor_ceiling(root, 8)
|
||||
(7, 10)
|
||||
>>> floor_ceiling(root, 14)
|
||||
(10, 15)
|
||||
>>> floor_ceiling(root, -1)
|
||||
(None, 3)
|
||||
>>> floor_ceiling(root, 30)
|
||||
(25, None)
|
||||
"""
|
||||
floor_val = None
|
||||
ceiling_val = None
|
||||
|
||||
while root:
|
||||
if root.key == key:
|
||||
floor_val = root.key
|
||||
ceiling_val = root.key
|
||||
break
|
||||
|
||||
if key < root.key:
|
||||
ceiling_val = root.key
|
||||
root = root.left
|
||||
else:
|
||||
floor_val = root.key
|
||||
root = root.right
|
||||
|
||||
return floor_val, ceiling_val
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
|
@ -1,131 +0,0 @@
|
|||
"""
|
||||
Author : Alexander Pantyukhin
|
||||
Date : November 2, 2022
|
||||
|
||||
Task:
|
||||
Given the root of a binary tree, determine if it is a valid binary search
|
||||
tree (BST).
|
||||
|
||||
A valid binary search tree is defined as follows:
|
||||
|
||||
- The left subtree of a node contains only nodes with keys less than the node's key.
|
||||
- The right subtree of a node contains only nodes with keys greater than the node's key.
|
||||
- Both the left and right subtrees must also be binary search trees.
|
||||
|
||||
Implementation notes:
|
||||
Depth-first search approach.
|
||||
|
||||
leetcode: https://leetcode.com/problems/validate-binary-search-tree/
|
||||
|
||||
Let n is the number of nodes in tree
|
||||
Runtime: O(n)
|
||||
Space: O(1)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class TreeNode:
|
||||
data: float
|
||||
left: TreeNode | None = None
|
||||
right: TreeNode | None = None
|
||||
|
||||
|
||||
def is_binary_search_tree(root: TreeNode | None) -> bool:
|
||||
"""
|
||||
>>> is_binary_search_tree(TreeNode(data=2,
|
||||
... left=TreeNode(data=1),
|
||||
... right=TreeNode(data=3))
|
||||
... )
|
||||
True
|
||||
|
||||
>>> is_binary_search_tree(TreeNode(data=0,
|
||||
... left=TreeNode(data=-11),
|
||||
... right=TreeNode(data=3))
|
||||
... )
|
||||
True
|
||||
|
||||
>>> is_binary_search_tree(TreeNode(data=5,
|
||||
... left=TreeNode(data=1),
|
||||
... right=TreeNode(data=4, left=TreeNode(data=3)))
|
||||
... )
|
||||
False
|
||||
|
||||
>>> is_binary_search_tree(TreeNode(data='a',
|
||||
... left=TreeNode(data=1),
|
||||
... right=TreeNode(data=4, left=TreeNode(data=3)))
|
||||
... )
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each node should be type of TreeNode and data should be float.
|
||||
|
||||
>>> is_binary_search_tree(TreeNode(data=2,
|
||||
... left=TreeNode([]),
|
||||
... right=TreeNode(data=4, left=TreeNode(data=3)))
|
||||
... )
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Each node should be type of TreeNode and data should be float.
|
||||
"""
|
||||
|
||||
# Validation
|
||||
def is_valid_tree(node: TreeNode | None) -> bool:
|
||||
"""
|
||||
>>> is_valid_tree(None)
|
||||
True
|
||||
>>> is_valid_tree('abc')
|
||||
False
|
||||
>>> is_valid_tree(TreeNode(data='not a float'))
|
||||
False
|
||||
>>> is_valid_tree(TreeNode(data=1, left=TreeNode('123')))
|
||||
False
|
||||
"""
|
||||
if node is None:
|
||||
return True
|
||||
|
||||
if not isinstance(node, TreeNode):
|
||||
return False
|
||||
|
||||
try:
|
||||
float(node.data)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
|
||||
return is_valid_tree(node.left) and is_valid_tree(node.right)
|
||||
|
||||
if not is_valid_tree(root):
|
||||
raise ValueError(
|
||||
"Each node should be type of TreeNode and data should be float."
|
||||
)
|
||||
|
||||
def is_binary_search_tree_recursive_check(
|
||||
node: TreeNode | None, left_bound: float, right_bound: float
|
||||
) -> bool:
|
||||
"""
|
||||
>>> is_binary_search_tree_recursive_check(None)
|
||||
True
|
||||
>>> is_binary_search_tree_recursive_check(TreeNode(data=1), 10, 20)
|
||||
False
|
||||
"""
|
||||
|
||||
if node is None:
|
||||
return True
|
||||
|
||||
return (
|
||||
left_bound < node.data < right_bound
|
||||
and is_binary_search_tree_recursive_check(node.left, left_bound, node.data)
|
||||
and is_binary_search_tree_recursive_check(
|
||||
node.right, node.data, right_bound
|
||||
)
|
||||
)
|
||||
|
||||
return is_binary_search_tree_recursive_check(root, -float("inf"), float("inf"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
97
data_structures/binary_tree/is_sorted.py
Normal file
97
data_structures/binary_tree/is_sorted.py
Normal file
|
@ -0,0 +1,97 @@
|
|||
"""
|
||||
Given the root of a binary tree, determine if it is a valid binary search tree (BST).
|
||||
|
||||
A valid binary search tree is defined as follows:
|
||||
- The left subtree of a node contains only nodes with keys less than the node's key.
|
||||
- The right subtree of a node contains only nodes with keys greater than the node's key.
|
||||
- Both the left and right subtrees must also be binary search trees.
|
||||
|
||||
In effect, a binary tree is a valid BST if its nodes are sorted in ascending order.
|
||||
leetcode: https://leetcode.com/problems/validate-binary-search-tree/
|
||||
|
||||
If n is the number of nodes in the tree then:
|
||||
Runtime: O(n)
|
||||
Space: O(1)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
data: float
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
def __iter__(self) -> Iterator[float]:
|
||||
"""
|
||||
>>> root = Node(data=2.1)
|
||||
>>> list(root)
|
||||
[2.1]
|
||||
>>> root.left=Node(data=2.0)
|
||||
>>> list(root)
|
||||
[2.0, 2.1]
|
||||
>>> root.right=Node(data=2.2)
|
||||
>>> list(root)
|
||||
[2.0, 2.1, 2.2]
|
||||
"""
|
||||
if self.left:
|
||||
yield from self.left
|
||||
yield self.data
|
||||
if self.right:
|
||||
yield from self.right
|
||||
|
||||
@property
|
||||
def is_sorted(self) -> bool:
|
||||
"""
|
||||
>>> Node(data='abc').is_sorted
|
||||
True
|
||||
>>> Node(data=2,
|
||||
... left=Node(data=1.999),
|
||||
... right=Node(data=3)).is_sorted
|
||||
True
|
||||
>>> Node(data=0,
|
||||
... left=Node(data=0),
|
||||
... right=Node(data=0)).is_sorted
|
||||
True
|
||||
>>> Node(data=0,
|
||||
... left=Node(data=-11),
|
||||
... right=Node(data=3)).is_sorted
|
||||
True
|
||||
>>> Node(data=5,
|
||||
... left=Node(data=1),
|
||||
... right=Node(data=4, left=Node(data=3))).is_sorted
|
||||
False
|
||||
>>> Node(data='a',
|
||||
... left=Node(data=1),
|
||||
... right=Node(data=4, left=Node(data=3))).is_sorted
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: '<' not supported between instances of 'str' and 'int'
|
||||
>>> Node(data=2,
|
||||
... left=Node([]),
|
||||
... right=Node(data=4, left=Node(data=3))).is_sorted
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: '<' not supported between instances of 'int' and 'list'
|
||||
"""
|
||||
if self.left and (self.data < self.left.data or not self.left.is_sorted):
|
||||
return False
|
||||
if self.right and (self.data > self.right.data or not self.right.is_sorted):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
tree = Node(data=2.1, left=Node(data=2.0), right=Node(data=2.2))
|
||||
print(f"Tree {list(tree)} is sorted: {tree.is_sorted = }.")
|
||||
assert tree.right
|
||||
tree.right.data = 2.0
|
||||
print(f"Tree {list(tree)} is sorted: {tree.is_sorted = }.")
|
||||
tree.right.data = 2.1
|
||||
print(f"Tree {list(tree)} is sorted: {tree.is_sorted = }.")
|
161
data_structures/binary_tree/is_sum_tree.py
Normal file
161
data_structures/binary_tree/is_sum_tree.py
Normal file
|
@ -0,0 +1,161 @@
|
|||
"""
|
||||
Is a binary tree a sum tree where the value of every non-leaf node is equal to the sum
|
||||
of the values of its left and right subtrees?
|
||||
https://www.geeksforgeeks.org/check-if-a-given-binary-tree-is-sumtree
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
data: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
"""
|
||||
>>> root = Node(2)
|
||||
>>> list(root)
|
||||
[2]
|
||||
>>> root.left = Node(1)
|
||||
>>> tuple(root)
|
||||
(1, 2)
|
||||
"""
|
||||
if self.left:
|
||||
yield from self.left
|
||||
yield self.data
|
||||
if self.right:
|
||||
yield from self.right
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
>>> root = Node(2)
|
||||
>>> len(root)
|
||||
1
|
||||
>>> root.left = Node(1)
|
||||
>>> len(root)
|
||||
2
|
||||
"""
|
||||
return sum(1 for _ in self)
|
||||
|
||||
@property
|
||||
def is_sum_node(self) -> bool:
|
||||
"""
|
||||
>>> root = Node(3)
|
||||
>>> root.is_sum_node
|
||||
True
|
||||
>>> root.left = Node(1)
|
||||
>>> root.is_sum_node
|
||||
False
|
||||
>>> root.right = Node(2)
|
||||
>>> root.is_sum_node
|
||||
True
|
||||
"""
|
||||
if not self.left and not self.right:
|
||||
return True # leaf nodes are considered sum nodes
|
||||
left_sum = sum(self.left) if self.left else 0
|
||||
right_sum = sum(self.right) if self.right else 0
|
||||
return all(
|
||||
(
|
||||
self.data == left_sum + right_sum,
|
||||
self.left.is_sum_node if self.left else True,
|
||||
self.right.is_sum_node if self.right else True,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BinaryTree:
|
||||
root: Node
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
"""
|
||||
>>> list(BinaryTree.build_a_tree())
|
||||
[1, 2, 7, 11, 15, 29, 35, 40]
|
||||
"""
|
||||
return iter(self.root)
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
>>> len(BinaryTree.build_a_tree())
|
||||
8
|
||||
"""
|
||||
return len(self.root)
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Returns a string representation of the inorder traversal of the binary tree.
|
||||
|
||||
>>> str(list(BinaryTree.build_a_tree()))
|
||||
'[1, 2, 7, 11, 15, 29, 35, 40]'
|
||||
"""
|
||||
return str(list(self))
|
||||
|
||||
@property
|
||||
def is_sum_tree(self) -> bool:
|
||||
"""
|
||||
>>> BinaryTree.build_a_tree().is_sum_tree
|
||||
False
|
||||
>>> BinaryTree.build_a_sum_tree().is_sum_tree
|
||||
True
|
||||
"""
|
||||
return self.root.is_sum_node
|
||||
|
||||
@classmethod
|
||||
def build_a_tree(cls) -> BinaryTree:
|
||||
r"""
|
||||
Create a binary tree with the specified structure:
|
||||
11
|
||||
/ \
|
||||
2 29
|
||||
/ \ / \
|
||||
1 7 15 40
|
||||
\
|
||||
35
|
||||
>>> list(BinaryTree.build_a_tree())
|
||||
[1, 2, 7, 11, 15, 29, 35, 40]
|
||||
"""
|
||||
tree = BinaryTree(Node(11))
|
||||
root = tree.root
|
||||
root.left = Node(2)
|
||||
root.right = Node(29)
|
||||
root.left.left = Node(1)
|
||||
root.left.right = Node(7)
|
||||
root.right.left = Node(15)
|
||||
root.right.right = Node(40)
|
||||
root.right.right.left = Node(35)
|
||||
return tree
|
||||
|
||||
@classmethod
|
||||
def build_a_sum_tree(cls) -> BinaryTree:
|
||||
r"""
|
||||
Create a binary tree with the specified structure:
|
||||
26
|
||||
/ \
|
||||
10 3
|
||||
/ \ \
|
||||
4 6 3
|
||||
>>> list(BinaryTree.build_a_sum_tree())
|
||||
[4, 10, 6, 26, 3, 3]
|
||||
"""
|
||||
tree = BinaryTree(Node(26))
|
||||
root = tree.root
|
||||
root.left = Node(10)
|
||||
root.right = Node(3)
|
||||
root.left.left = Node(4)
|
||||
root.left.right = Node(6)
|
||||
root.right.right = Node(3)
|
||||
return tree
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
tree = BinaryTree.build_a_tree()
|
||||
print(f"{tree} has {len(tree)} nodes and {tree.is_sum_tree = }.")
|
||||
tree = BinaryTree.build_a_sum_tree()
|
||||
print(f"{tree} has {len(tree)} nodes and {tree.is_sum_tree = }.")
|
153
data_structures/binary_tree/mirror_binary_tree.py
Normal file
153
data_structures/binary_tree/mirror_binary_tree.py
Normal file
|
@ -0,0 +1,153 @@
|
|||
"""
|
||||
Given the root of a binary tree, mirror the tree, and return its root.
|
||||
|
||||
Leetcode problem reference: https://leetcode.com/problems/mirror-binary-tree/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
"""
|
||||
A Node has value variable and pointers to Nodes to its left and right.
|
||||
"""
|
||||
|
||||
value: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
if self.left:
|
||||
yield from self.left
|
||||
yield self.value
|
||||
if self.right:
|
||||
yield from self.right
|
||||
|
||||
def __len__(self) -> int:
|
||||
return sum(1 for _ in self)
|
||||
|
||||
def mirror(self) -> Node:
|
||||
"""
|
||||
Mirror the binary tree rooted at this node by swapping left and right children.
|
||||
|
||||
>>> tree = Node(0)
|
||||
>>> list(tree)
|
||||
[0]
|
||||
>>> list(tree.mirror())
|
||||
[0]
|
||||
>>> tree = Node(1, Node(0), Node(3, Node(2), Node(4, None, Node(5))))
|
||||
>>> tuple(tree)
|
||||
(0, 1, 2, 3, 4, 5)
|
||||
>>> tuple(tree.mirror())
|
||||
(5, 4, 3, 2, 1, 0)
|
||||
"""
|
||||
self.left, self.right = self.right, self.left
|
||||
if self.left:
|
||||
self.left.mirror()
|
||||
if self.right:
|
||||
self.right.mirror()
|
||||
return self
|
||||
|
||||
|
||||
def make_tree_seven() -> Node:
|
||||
r"""
|
||||
Return a binary tree with 7 nodes that looks like this:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ / \
|
||||
4 5 6 7
|
||||
|
||||
>>> tree_seven = make_tree_seven()
|
||||
>>> len(tree_seven)
|
||||
7
|
||||
>>> list(tree_seven)
|
||||
[4, 2, 5, 1, 6, 3, 7]
|
||||
"""
|
||||
tree = Node(1)
|
||||
tree.left = Node(2)
|
||||
tree.right = Node(3)
|
||||
tree.left.left = Node(4)
|
||||
tree.left.right = Node(5)
|
||||
tree.right.left = Node(6)
|
||||
tree.right.right = Node(7)
|
||||
return tree
|
||||
|
||||
|
||||
def make_tree_nine() -> Node:
|
||||
r"""
|
||||
Return a binary tree with 9 nodes that looks like this:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ \
|
||||
4 5 6
|
||||
/ \ \
|
||||
7 8 9
|
||||
|
||||
>>> tree_nine = make_tree_nine()
|
||||
>>> len(tree_nine)
|
||||
9
|
||||
>>> list(tree_nine)
|
||||
[7, 4, 8, 2, 5, 9, 1, 3, 6]
|
||||
"""
|
||||
tree = Node(1)
|
||||
tree.left = Node(2)
|
||||
tree.right = Node(3)
|
||||
tree.left.left = Node(4)
|
||||
tree.left.right = Node(5)
|
||||
tree.right.right = Node(6)
|
||||
tree.left.left.left = Node(7)
|
||||
tree.left.left.right = Node(8)
|
||||
tree.left.right.right = Node(9)
|
||||
return tree
|
||||
|
||||
|
||||
def main() -> None:
|
||||
r"""
|
||||
Mirror binary trees with the given root and returns the root
|
||||
|
||||
>>> tree = make_tree_nine()
|
||||
>>> tuple(tree)
|
||||
(7, 4, 8, 2, 5, 9, 1, 3, 6)
|
||||
>>> tuple(tree.mirror())
|
||||
(6, 3, 1, 9, 5, 2, 8, 4, 7)
|
||||
|
||||
nine_tree:
|
||||
1
|
||||
/ \
|
||||
2 3
|
||||
/ \ \
|
||||
4 5 6
|
||||
/ \ \
|
||||
7 8 9
|
||||
|
||||
The mirrored tree looks like this:
|
||||
1
|
||||
/ \
|
||||
3 2
|
||||
/ / \
|
||||
6 5 4
|
||||
/ / \
|
||||
9 8 7
|
||||
"""
|
||||
trees = {"zero": Node(0), "seven": make_tree_seven(), "nine": make_tree_nine()}
|
||||
for name, tree in trees.items():
|
||||
print(f" The {name} tree: {tuple(tree)}")
|
||||
# (0,)
|
||||
# (4, 2, 5, 1, 6, 3, 7)
|
||||
# (7, 4, 8, 2, 5, 9, 1, 3, 6)
|
||||
print(f"Mirror of {name} tree: {tuple(tree.mirror())}")
|
||||
# (0,)
|
||||
# (7, 3, 6, 1, 5, 2, 4)
|
||||
# (6, 3, 1, 9, 5, 2, 8, 4, 7)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
main()
|
|
@ -3,7 +3,8 @@ import math
|
|||
|
||||
class SegmentTree:
|
||||
def __init__(self, a):
|
||||
self.N = len(a)
|
||||
self.A = a
|
||||
self.N = len(self.A)
|
||||
self.st = [0] * (
|
||||
4 * self.N
|
||||
) # approximate the overall size of segment tree with array N
|
||||
|
@ -11,14 +12,32 @@ class SegmentTree:
|
|||
self.build(1, 0, self.N - 1)
|
||||
|
||||
def left(self, idx):
|
||||
"""
|
||||
Returns the left child index for a given index in a binary tree.
|
||||
|
||||
>>> s = SegmentTree([1, 2, 3])
|
||||
>>> s.left(1)
|
||||
2
|
||||
>>> s.left(2)
|
||||
4
|
||||
"""
|
||||
return idx * 2
|
||||
|
||||
def right(self, idx):
|
||||
"""
|
||||
Returns the right child index for a given index in a binary tree.
|
||||
|
||||
>>> s = SegmentTree([1, 2, 3])
|
||||
>>> s.right(1)
|
||||
3
|
||||
>>> s.right(2)
|
||||
5
|
||||
"""
|
||||
return idx * 2 + 1
|
||||
|
||||
def build(self, idx, l, r): # noqa: E741
|
||||
if l == r:
|
||||
self.st[idx] = A[l]
|
||||
self.st[idx] = self.A[l]
|
||||
else:
|
||||
mid = (l + r) // 2
|
||||
self.build(self.left(idx), l, mid)
|
||||
|
@ -26,6 +45,15 @@ class SegmentTree:
|
|||
self.st[idx] = max(self.st[self.left(idx)], self.st[self.right(idx)])
|
||||
|
||||
def update(self, a, b, val):
|
||||
"""
|
||||
Update the values in the segment tree in the range [a,b] with the given value.
|
||||
|
||||
>>> s = SegmentTree([1, 2, 3, 4, 5])
|
||||
>>> s.update(2, 4, 10)
|
||||
True
|
||||
>>> s.query(1, 5)
|
||||
10
|
||||
"""
|
||||
return self.update_recursive(1, 0, self.N - 1, a - 1, b - 1, val)
|
||||
|
||||
def update_recursive(self, idx, l, r, a, b, val): # noqa: E741
|
||||
|
@ -44,6 +72,15 @@ class SegmentTree:
|
|||
return True
|
||||
|
||||
def query(self, a, b):
|
||||
"""
|
||||
Query the maximum value in the range [a,b].
|
||||
|
||||
>>> s = SegmentTree([1, 2, 3, 4, 5])
|
||||
>>> s.query(1, 3)
|
||||
3
|
||||
>>> s.query(1, 5)
|
||||
5
|
||||
"""
|
||||
return self.query_recursive(1, 0, self.N - 1, a - 1, b - 1)
|
||||
|
||||
def query_recursive(self, idx, l, r, a, b): # noqa: E741
|
||||
|
|
140
data_structures/binary_tree/serialize_deserialize_binary_tree.py
Normal file
140
data_structures/binary_tree/serialize_deserialize_binary_tree.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class TreeNode:
|
||||
"""
|
||||
A binary tree node has a value, left child, and right child.
|
||||
|
||||
Props:
|
||||
value: The value of the node.
|
||||
left: The left child of the node.
|
||||
right: The right child of the node.
|
||||
"""
|
||||
|
||||
value: int = 0
|
||||
left: TreeNode | None = None
|
||||
right: TreeNode | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not isinstance(self.value, int):
|
||||
raise TypeError("Value must be an integer.")
|
||||
|
||||
def __iter__(self) -> Iterator[TreeNode]:
|
||||
"""
|
||||
Iterate through the tree in preorder.
|
||||
|
||||
Returns:
|
||||
An iterator of the tree nodes.
|
||||
|
||||
>>> list(TreeNode(1))
|
||||
[1,null,null]
|
||||
>>> tuple(TreeNode(1, TreeNode(2), TreeNode(3)))
|
||||
(1,2,null,null,3,null,null, 2,null,null, 3,null,null)
|
||||
"""
|
||||
yield self
|
||||
yield from self.left or ()
|
||||
yield from self.right or ()
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Count the number of nodes in the tree.
|
||||
|
||||
Returns:
|
||||
The number of nodes in the tree.
|
||||
|
||||
>>> len(TreeNode(1))
|
||||
1
|
||||
>>> len(TreeNode(1, TreeNode(2), TreeNode(3)))
|
||||
3
|
||||
"""
|
||||
return sum(1 for _ in self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Represent the tree as a string.
|
||||
|
||||
Returns:
|
||||
A string representation of the tree.
|
||||
|
||||
>>> repr(TreeNode(1))
|
||||
'1,null,null'
|
||||
>>> repr(TreeNode(1, TreeNode(2), TreeNode(3)))
|
||||
'1,2,null,null,3,null,null'
|
||||
>>> repr(TreeNode(1, TreeNode(2), TreeNode(3, TreeNode(4), TreeNode(5))))
|
||||
'1,2,null,null,3,4,null,null,5,null,null'
|
||||
"""
|
||||
return f"{self.value},{self.left!r},{self.right!r}".replace("None", "null")
|
||||
|
||||
@classmethod
|
||||
def five_tree(cls) -> TreeNode:
|
||||
"""
|
||||
>>> repr(TreeNode.five_tree())
|
||||
'1,2,null,null,3,4,null,null,5,null,null'
|
||||
"""
|
||||
root = TreeNode(1)
|
||||
root.left = TreeNode(2)
|
||||
root.right = TreeNode(3)
|
||||
root.right.left = TreeNode(4)
|
||||
root.right.right = TreeNode(5)
|
||||
return root
|
||||
|
||||
|
||||
def deserialize(data: str) -> TreeNode | None:
|
||||
"""
|
||||
Deserialize a string to a binary tree.
|
||||
|
||||
Args:
|
||||
data(str): The serialized string.
|
||||
|
||||
Returns:
|
||||
The root of the binary tree.
|
||||
|
||||
>>> root = TreeNode.five_tree()
|
||||
>>> serialzed_data = repr(root)
|
||||
>>> deserialized = deserialize(serialzed_data)
|
||||
>>> root == deserialized
|
||||
True
|
||||
>>> root is deserialized # two separate trees
|
||||
False
|
||||
>>> root.right.right.value = 6
|
||||
>>> root == deserialized
|
||||
False
|
||||
>>> serialzed_data = repr(root)
|
||||
>>> deserialized = deserialize(serialzed_data)
|
||||
>>> root == deserialized
|
||||
True
|
||||
>>> deserialize("")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: Data cannot be empty.
|
||||
"""
|
||||
|
||||
if not data:
|
||||
raise ValueError("Data cannot be empty.")
|
||||
|
||||
# Split the serialized string by a comma to get node values
|
||||
nodes = data.split(",")
|
||||
|
||||
def build_tree() -> TreeNode | None:
|
||||
# Get the next value from the list
|
||||
value = nodes.pop(0)
|
||||
|
||||
if value == "null":
|
||||
return None
|
||||
|
||||
node = TreeNode(int(value))
|
||||
node.left = build_tree() # Recursively build left subtree
|
||||
node.right = build_tree() # Recursively build right subtree
|
||||
return node
|
||||
|
||||
return build_tree()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
101
data_structures/binary_tree/symmetric_tree.py
Normal file
101
data_structures/binary_tree/symmetric_tree.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
Given the root of a binary tree, check whether it is a mirror of itself
|
||||
(i.e., symmetric around its center).
|
||||
|
||||
Leetcode reference: https://leetcode.com/problems/symmetric-tree/
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
"""
|
||||
A Node has data variable and pointers to Nodes to its left and right.
|
||||
"""
|
||||
|
||||
data: int
|
||||
left: Node | None = None
|
||||
right: Node | None = None
|
||||
|
||||
|
||||
def make_symmetric_tree() -> Node:
|
||||
r"""
|
||||
Create a symmetric tree for testing.
|
||||
The tree looks like this:
|
||||
1
|
||||
/ \
|
||||
2 2
|
||||
/ \ / \
|
||||
3 4 4 3
|
||||
"""
|
||||
root = Node(1)
|
||||
root.left = Node(2)
|
||||
root.right = Node(2)
|
||||
root.left.left = Node(3)
|
||||
root.left.right = Node(4)
|
||||
root.right.left = Node(4)
|
||||
root.right.right = Node(3)
|
||||
return root
|
||||
|
||||
|
||||
def make_asymmetric_tree() -> Node:
|
||||
r"""
|
||||
Create a asymmetric tree for testing.
|
||||
The tree looks like this:
|
||||
1
|
||||
/ \
|
||||
2 2
|
||||
/ \ / \
|
||||
3 4 3 4
|
||||
"""
|
||||
root = Node(1)
|
||||
root.left = Node(2)
|
||||
root.right = Node(2)
|
||||
root.left.left = Node(3)
|
||||
root.left.right = Node(4)
|
||||
root.right.left = Node(3)
|
||||
root.right.right = Node(4)
|
||||
return root
|
||||
|
||||
|
||||
def is_symmetric_tree(tree: Node) -> bool:
|
||||
"""
|
||||
Test cases for is_symmetric_tree function
|
||||
>>> is_symmetric_tree(make_symmetric_tree())
|
||||
True
|
||||
>>> is_symmetric_tree(make_asymmetric_tree())
|
||||
False
|
||||
"""
|
||||
if tree:
|
||||
return is_mirror(tree.left, tree.right)
|
||||
return True # An empty tree is considered symmetric.
|
||||
|
||||
|
||||
def is_mirror(left: Node | None, right: Node | None) -> bool:
|
||||
"""
|
||||
>>> tree1 = make_symmetric_tree()
|
||||
>>> tree1.right.right = Node(3)
|
||||
>>> is_mirror(tree1.left, tree1.right)
|
||||
True
|
||||
>>> tree2 = make_asymmetric_tree()
|
||||
>>> is_mirror(tree2.left, tree2.right)
|
||||
False
|
||||
"""
|
||||
if left is None and right is None:
|
||||
# Both sides are empty, which is symmetric.
|
||||
return True
|
||||
if left is None or right is None:
|
||||
# One side is empty while the other is not, which is not symmetric.
|
||||
return False
|
||||
if left.data == right.data:
|
||||
# The values match, so check the subtree
|
||||
return is_mirror(left.left, right.right) and is_mirror(left.right, right.left)
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
|
@ -35,6 +35,33 @@ class DoubleHash(HashTable):
|
|||
return (increment * self.__hash_function_2(key, data)) % self.size_table
|
||||
|
||||
def _collision_resolution(self, key, data=None):
|
||||
"""
|
||||
Examples:
|
||||
|
||||
1. Try to add three data elements when the size is three
|
||||
>>> dh = DoubleHash(3)
|
||||
>>> dh.insert_data(10)
|
||||
>>> dh.insert_data(20)
|
||||
>>> dh.insert_data(30)
|
||||
>>> dh.keys()
|
||||
{1: 10, 2: 20, 0: 30}
|
||||
|
||||
2. Try to add three data elements when the size is two
|
||||
>>> dh = DoubleHash(2)
|
||||
>>> dh.insert_data(10)
|
||||
>>> dh.insert_data(20)
|
||||
>>> dh.insert_data(30)
|
||||
>>> dh.keys()
|
||||
{10: 10, 9: 20, 8: 30}
|
||||
|
||||
3. Try to add three data elements when the size is four
|
||||
>>> dh = DoubleHash(4)
|
||||
>>> dh.insert_data(10)
|
||||
>>> dh.insert_data(20)
|
||||
>>> dh.insert_data(30)
|
||||
>>> dh.keys()
|
||||
{9: 20, 10: 10, 8: 30}
|
||||
"""
|
||||
i = 1
|
||||
new_key = self.hash_function(data)
|
||||
|
||||
|
@ -50,3 +77,9 @@ class DoubleHash(HashTable):
|
|||
i += 1
|
||||
|
||||
return new_key
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -54,6 +54,14 @@ class HashMap(MutableMapping[KEY, VAL]):
|
|||
Get next index.
|
||||
|
||||
Implements linear open addressing.
|
||||
>>> HashMap(5)._get_next_ind(3)
|
||||
4
|
||||
>>> HashMap(5)._get_next_ind(5)
|
||||
1
|
||||
>>> HashMap(5)._get_next_ind(6)
|
||||
2
|
||||
>>> HashMap(5)._get_next_ind(9)
|
||||
0
|
||||
"""
|
||||
return (ind + 1) % len(self._buckets)
|
||||
|
||||
|
@ -82,6 +90,14 @@ class HashMap(MutableMapping[KEY, VAL]):
|
|||
Return true if we have reached safe capacity.
|
||||
|
||||
So we need to increase the number of buckets to avoid collisions.
|
||||
|
||||
>>> hm = HashMap(2)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm._add_item(2, 20)
|
||||
>>> hm._is_full()
|
||||
True
|
||||
>>> HashMap(2)._is_full()
|
||||
False
|
||||
"""
|
||||
limit = len(self._buckets) * self._capacity_factor
|
||||
return len(self) >= int(limit)
|
||||
|
@ -114,17 +130,104 @@ class HashMap(MutableMapping[KEY, VAL]):
|
|||
ind = self._get_next_ind(ind)
|
||||
|
||||
def _add_item(self, key: KEY, val: VAL) -> None:
|
||||
"""
|
||||
Try to add 3 elements when the size is 5
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm._add_item(2, 20)
|
||||
>>> hm._add_item(3, 30)
|
||||
>>> hm
|
||||
HashMap(1: 10, 2: 20, 3: 30)
|
||||
|
||||
Try to add 3 elements when the size is 5
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(-5, 10)
|
||||
>>> hm._add_item(6, 30)
|
||||
>>> hm._add_item(-7, 20)
|
||||
>>> hm
|
||||
HashMap(-5: 10, 6: 30, -7: 20)
|
||||
|
||||
Try to add 3 elements when size is 1
|
||||
>>> hm = HashMap(1)
|
||||
>>> hm._add_item(10, 13.2)
|
||||
>>> hm._add_item(6, 5.26)
|
||||
>>> hm._add_item(7, 5.155)
|
||||
>>> hm
|
||||
HashMap(10: 13.2)
|
||||
|
||||
Trying to add an element with a key that is a floating point value
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1.5, 10)
|
||||
>>> hm
|
||||
HashMap(1.5: 10)
|
||||
|
||||
5. Trying to add an item with the same key
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm._add_item(1, 20)
|
||||
>>> hm
|
||||
HashMap(1: 20)
|
||||
"""
|
||||
for ind in self._iterate_buckets(key):
|
||||
if self._try_set(ind, key, val):
|
||||
break
|
||||
|
||||
def __setitem__(self, key: KEY, val: VAL) -> None:
|
||||
"""
|
||||
1. Changing value of item whose key is present
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm.__setitem__(1, 20)
|
||||
>>> hm
|
||||
HashMap(1: 20)
|
||||
|
||||
2. Changing value of item whose key is not present
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm.__setitem__(0, 20)
|
||||
>>> hm
|
||||
HashMap(0: 20, 1: 10)
|
||||
|
||||
3. Changing the value of the same item multiple times
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm.__setitem__(1, 20)
|
||||
>>> hm.__setitem__(1, 30)
|
||||
>>> hm
|
||||
HashMap(1: 30)
|
||||
"""
|
||||
if self._is_full():
|
||||
self._size_up()
|
||||
|
||||
self._add_item(key, val)
|
||||
|
||||
def __delitem__(self, key: KEY) -> None:
|
||||
"""
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm._add_item(2, 20)
|
||||
>>> hm._add_item(3, 30)
|
||||
>>> hm.__delitem__(3)
|
||||
>>> hm
|
||||
HashMap(1: 10, 2: 20)
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(-5, 10)
|
||||
>>> hm._add_item(6, 30)
|
||||
>>> hm._add_item(-7, 20)
|
||||
>>> hm.__delitem__(-5)
|
||||
>>> hm
|
||||
HashMap(6: 30, -7: 20)
|
||||
|
||||
# Trying to remove a non-existing item
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm._add_item(2, 20)
|
||||
>>> hm._add_item(3, 30)
|
||||
>>> hm.__delitem__(4)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 4
|
||||
"""
|
||||
for ind in self._iterate_buckets(key):
|
||||
item = self._buckets[ind]
|
||||
if item is None:
|
||||
|
@ -139,6 +242,25 @@ class HashMap(MutableMapping[KEY, VAL]):
|
|||
self._size_down()
|
||||
|
||||
def __getitem__(self, key: KEY) -> VAL:
|
||||
"""
|
||||
Returns the item at the given key
|
||||
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm.__getitem__(1)
|
||||
10
|
||||
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(10, -10)
|
||||
>>> hm._add_item(20, -20)
|
||||
>>> hm.__getitem__(20)
|
||||
-20
|
||||
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(-1, 10)
|
||||
>>> hm.__getitem__(-1)
|
||||
10
|
||||
"""
|
||||
for ind in self._iterate_buckets(key):
|
||||
item = self._buckets[ind]
|
||||
if item is None:
|
||||
|
@ -150,13 +272,33 @@ class HashMap(MutableMapping[KEY, VAL]):
|
|||
raise KeyError(key)
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Returns the number of items present in hashmap
|
||||
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm._add_item(1, 10)
|
||||
>>> hm._add_item(2, 20)
|
||||
>>> hm._add_item(3, 30)
|
||||
>>> hm.__len__()
|
||||
3
|
||||
|
||||
>>> hm = HashMap(5)
|
||||
>>> hm.__len__()
|
||||
0
|
||||
"""
|
||||
return self._len
|
||||
|
||||
def __iter__(self) -> Iterator[KEY]:
|
||||
yield from (item.key for item in self._buckets if item)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
val_string = " ,".join(
|
||||
val_string = ", ".join(
|
||||
f"{item.key}: {item.val}" for item in self._buckets if item
|
||||
)
|
||||
return f"HashMap({val_string})"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -21,6 +21,29 @@ class HashTable:
|
|||
self._keys: dict = {}
|
||||
|
||||
def keys(self):
|
||||
"""
|
||||
The keys function returns a dictionary containing the key value pairs.
|
||||
key being the index number in hash table and value being the data value.
|
||||
|
||||
Examples:
|
||||
1. creating HashTable with size 10 and inserting 3 elements
|
||||
>>> ht = HashTable(10)
|
||||
>>> ht.insert_data(10)
|
||||
>>> ht.insert_data(20)
|
||||
>>> ht.insert_data(30)
|
||||
>>> ht.keys()
|
||||
{0: 10, 1: 20, 2: 30}
|
||||
|
||||
2. creating HashTable with size 5 and inserting 5 elements
|
||||
>>> ht = HashTable(5)
|
||||
>>> ht.insert_data(5)
|
||||
>>> ht.insert_data(4)
|
||||
>>> ht.insert_data(3)
|
||||
>>> ht.insert_data(2)
|
||||
>>> ht.insert_data(1)
|
||||
>>> ht.keys()
|
||||
{0: 5, 4: 4, 3: 3, 2: 2, 1: 1}
|
||||
"""
|
||||
return self._keys
|
||||
|
||||
def balanced_factor(self):
|
||||
|
@ -29,6 +52,30 @@ class HashTable:
|
|||
)
|
||||
|
||||
def hash_function(self, key):
|
||||
"""
|
||||
Generates hash for the given key value
|
||||
|
||||
Examples:
|
||||
|
||||
Creating HashTable with size 5
|
||||
>>> ht = HashTable(5)
|
||||
>>> ht.hash_function(10)
|
||||
0
|
||||
>>> ht.hash_function(20)
|
||||
0
|
||||
>>> ht.hash_function(4)
|
||||
4
|
||||
>>> ht.hash_function(18)
|
||||
3
|
||||
>>> ht.hash_function(-18)
|
||||
2
|
||||
>>> ht.hash_function(18.5)
|
||||
3.5
|
||||
>>> ht.hash_function(0)
|
||||
0
|
||||
>>> ht.hash_function(-0)
|
||||
0
|
||||
"""
|
||||
return key % self.size_table
|
||||
|
||||
def _step_by_step(self, step_ord):
|
||||
|
@ -37,6 +84,43 @@ class HashTable:
|
|||
print(self.values)
|
||||
|
||||
def bulk_insert(self, values):
|
||||
"""
|
||||
bulk_insert is used for entering more than one element at a time
|
||||
in the HashTable.
|
||||
|
||||
Examples:
|
||||
1.
|
||||
>>> ht = HashTable(5)
|
||||
>>> ht.bulk_insert((10,20,30))
|
||||
step 1
|
||||
[0, 1, 2, 3, 4]
|
||||
[10, None, None, None, None]
|
||||
step 2
|
||||
[0, 1, 2, 3, 4]
|
||||
[10, 20, None, None, None]
|
||||
step 3
|
||||
[0, 1, 2, 3, 4]
|
||||
[10, 20, 30, None, None]
|
||||
|
||||
2.
|
||||
>>> ht = HashTable(5)
|
||||
>>> ht.bulk_insert([5,4,3,2,1])
|
||||
step 1
|
||||
[0, 1, 2, 3, 4]
|
||||
[5, None, None, None, None]
|
||||
step 2
|
||||
[0, 1, 2, 3, 4]
|
||||
[5, None, None, None, 4]
|
||||
step 3
|
||||
[0, 1, 2, 3, 4]
|
||||
[5, None, None, 3, 4]
|
||||
step 4
|
||||
[0, 1, 2, 3, 4]
|
||||
[5, None, 2, 3, 4]
|
||||
step 5
|
||||
[0, 1, 2, 3, 4]
|
||||
[5, 1, 2, 3, 4]
|
||||
"""
|
||||
i = 1
|
||||
self.__aux_list = values
|
||||
for value in values:
|
||||
|
@ -45,10 +129,99 @@ class HashTable:
|
|||
i += 1
|
||||
|
||||
def _set_value(self, key, data):
|
||||
"""
|
||||
_set_value functions allows to update value at a particular hash
|
||||
|
||||
Examples:
|
||||
1. _set_value in HashTable of size 5
|
||||
>>> ht = HashTable(5)
|
||||
>>> ht.insert_data(10)
|
||||
>>> ht.insert_data(20)
|
||||
>>> ht.insert_data(30)
|
||||
>>> ht._set_value(0,15)
|
||||
>>> ht.keys()
|
||||
{0: 15, 1: 20, 2: 30}
|
||||
|
||||
2. _set_value in HashTable of size 2
|
||||
>>> ht = HashTable(2)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99)
|
||||
>>> ht._set_value(3,15)
|
||||
>>> ht.keys()
|
||||
{3: 15, 2: 17, 4: 99}
|
||||
|
||||
3. _set_value in HashTable when hash is not present
|
||||
>>> ht = HashTable(2)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99)
|
||||
>>> ht._set_value(0,15)
|
||||
>>> ht.keys()
|
||||
{3: 18, 2: 17, 4: 99, 0: 15}
|
||||
|
||||
4. _set_value in HashTable when multiple hash are not present
|
||||
>>> ht = HashTable(2)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99)
|
||||
>>> ht._set_value(0,15)
|
||||
>>> ht._set_value(1,20)
|
||||
>>> ht.keys()
|
||||
{3: 18, 2: 17, 4: 99, 0: 15, 1: 20}
|
||||
"""
|
||||
self.values[key] = data
|
||||
self._keys[key] = data
|
||||
|
||||
def _collision_resolution(self, key, data=None):
|
||||
"""
|
||||
This method is a type of open addressing which is used for handling collision.
|
||||
|
||||
In this implementation the concept of linear probing has been used.
|
||||
|
||||
The hash table is searched sequentially from the original location of the
|
||||
hash, if the new hash/location we get is already occupied we check for the next
|
||||
hash/location.
|
||||
|
||||
references:
|
||||
- https://en.wikipedia.org/wiki/Linear_probing
|
||||
|
||||
Examples:
|
||||
1. The collision will be with keys 18 & 99, so new hash will be created for 99
|
||||
>>> ht = HashTable(3)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99)
|
||||
>>> ht.keys()
|
||||
{2: 17, 0: 18, 1: 99}
|
||||
|
||||
2. The collision will be with keys 17 & 101, so new hash
|
||||
will be created for 101
|
||||
>>> ht = HashTable(4)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99)
|
||||
>>> ht.insert_data(101)
|
||||
>>> ht.keys()
|
||||
{1: 17, 2: 18, 3: 99, 0: 101}
|
||||
|
||||
2. The collision will be with all keys, so new hash will be created for all
|
||||
>>> ht = HashTable(1)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99)
|
||||
>>> ht.keys()
|
||||
{2: 17, 3: 18, 4: 99}
|
||||
|
||||
3. Trying to insert float key in hash
|
||||
>>> ht = HashTable(1)
|
||||
>>> ht.insert_data(17)
|
||||
>>> ht.insert_data(18)
|
||||
>>> ht.insert_data(99.99)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: list indices must be integers or slices, not float
|
||||
"""
|
||||
new_key = self.hash_function(key + 1)
|
||||
|
||||
while self.values[new_key] is not None and self.values[new_key] != key:
|
||||
|
@ -69,6 +242,21 @@ class HashTable:
|
|||
self.insert_data(value)
|
||||
|
||||
def insert_data(self, data):
|
||||
"""
|
||||
insert_data is used for inserting a single element at a time in the HashTable.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> ht = HashTable(3)
|
||||
>>> ht.insert_data(5)
|
||||
>>> ht.keys()
|
||||
{2: 5}
|
||||
>>> ht = HashTable(5)
|
||||
>>> ht.insert_data(30)
|
||||
>>> ht.insert_data(50)
|
||||
>>> ht.keys()
|
||||
{0: 30, 1: 50}
|
||||
"""
|
||||
key = self.hash_function(data)
|
||||
|
||||
if self.values[key] is None:
|
||||
|
@ -84,3 +272,9 @@ class HashTable:
|
|||
else:
|
||||
self.rehashing()
|
||||
self.insert_data(data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -12,6 +12,55 @@ class QuadraticProbing(HashTable):
|
|||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _collision_resolution(self, key, data=None):
|
||||
"""
|
||||
Quadratic probing is an open addressing scheme used for resolving
|
||||
collisions in hash table.
|
||||
|
||||
It works by taking the original hash index and adding successive
|
||||
values of an arbitrary quadratic polynomial until open slot is found.
|
||||
|
||||
Hash + 1², Hash + 2², Hash + 3² .... Hash + n²
|
||||
|
||||
reference:
|
||||
- https://en.wikipedia.org/wiki/Quadratic_probing
|
||||
e.g:
|
||||
1. Create hash table with size 7
|
||||
>>> qp = QuadraticProbing(7)
|
||||
>>> qp.insert_data(90)
|
||||
>>> qp.insert_data(340)
|
||||
>>> qp.insert_data(24)
|
||||
>>> qp.insert_data(45)
|
||||
>>> qp.insert_data(99)
|
||||
>>> qp.insert_data(73)
|
||||
>>> qp.insert_data(7)
|
||||
>>> qp.keys()
|
||||
{11: 45, 14: 99, 7: 24, 0: 340, 5: 73, 6: 90, 8: 7}
|
||||
|
||||
2. Create hash table with size 8
|
||||
>>> qp = QuadraticProbing(8)
|
||||
>>> qp.insert_data(0)
|
||||
>>> qp.insert_data(999)
|
||||
>>> qp.insert_data(111)
|
||||
>>> qp.keys()
|
||||
{0: 0, 7: 999, 3: 111}
|
||||
|
||||
3. Try to add three data elements when the size is two
|
||||
>>> qp = QuadraticProbing(2)
|
||||
>>> qp.insert_data(0)
|
||||
>>> qp.insert_data(999)
|
||||
>>> qp.insert_data(111)
|
||||
>>> qp.keys()
|
||||
{0: 0, 4: 999, 1: 111}
|
||||
|
||||
4. Try to add three data elements when the size is one
|
||||
>>> qp = QuadraticProbing(1)
|
||||
>>> qp.insert_data(0)
|
||||
>>> qp.insert_data(999)
|
||||
>>> qp.insert_data(111)
|
||||
>>> qp.keys()
|
||||
{4: 999, 1: 111}
|
||||
"""
|
||||
|
||||
i = 1
|
||||
new_key = self.hash_function(key + i * i)
|
||||
|
||||
|
@ -27,3 +76,9 @@ class QuadraticProbing(HashTable):
|
|||
break
|
||||
|
||||
return new_key
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -65,14 +65,14 @@ _add_with_resize_down = [
|
|||
|
||||
@pytest.mark.parametrize(
|
||||
"operations",
|
||||
(
|
||||
[
|
||||
pytest.param(_add_items, id="add items"),
|
||||
pytest.param(_overwrite_items, id="overwrite items"),
|
||||
pytest.param(_delete_items, id="delete items"),
|
||||
pytest.param(_access_absent_items, id="access absent items"),
|
||||
pytest.param(_add_with_resize_up, id="add with resize up"),
|
||||
pytest.param(_add_with_resize_down, id="add with resize down"),
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_hash_map_is_the_same_as_dict(operations):
|
||||
my = HashMap(initial_block_size=4)
|
||||
|
|
|
@ -53,7 +53,37 @@ class Heap(Generic[T]):
|
|||
return str(self.h)
|
||||
|
||||
def parent_index(self, child_idx: int) -> int | None:
|
||||
"""return the parent index of given child"""
|
||||
"""
|
||||
returns the parent index based on the given child index
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([103, 9, 1, 7, 11, 15, 25, 201, 209, 107, 5])
|
||||
>>> h
|
||||
[209, 201, 25, 103, 107, 15, 1, 9, 7, 11, 5]
|
||||
|
||||
>>> h.parent_index(-1) # returns none if index is <=0
|
||||
|
||||
>>> h.parent_index(0) # returns none if index is <=0
|
||||
|
||||
>>> h.parent_index(1)
|
||||
0
|
||||
>>> h.parent_index(2)
|
||||
0
|
||||
>>> h.parent_index(3)
|
||||
1
|
||||
>>> h.parent_index(4)
|
||||
1
|
||||
>>> h.parent_index(5)
|
||||
2
|
||||
>>> h.parent_index(10.5)
|
||||
4.0
|
||||
>>> h.parent_index(209.0)
|
||||
104.0
|
||||
>>> h.parent_index("Test")
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: '>' not supported between instances of 'str' and 'int'
|
||||
"""
|
||||
if child_idx > 0:
|
||||
return (child_idx - 1) // 2
|
||||
return None
|
||||
|
@ -81,6 +111,9 @@ class Heap(Generic[T]):
|
|||
def max_heapify(self, index: int) -> None:
|
||||
"""
|
||||
correct a single violation of the heap property in a subtree's root.
|
||||
|
||||
It is the function that is responsible for restoring the property
|
||||
of Max heap i.e the maximum element is always at top.
|
||||
"""
|
||||
if index < self.heap_size:
|
||||
violation: int = index
|
||||
|
@ -99,7 +132,29 @@ class Heap(Generic[T]):
|
|||
self.max_heapify(violation)
|
||||
|
||||
def build_max_heap(self, collection: Iterable[T]) -> None:
|
||||
"""build max heap from an unsorted array"""
|
||||
"""
|
||||
build max heap from an unsorted array
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([20,40,50,20,10])
|
||||
>>> h
|
||||
[50, 40, 20, 20, 10]
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([1,2,3,4,5,6,7,8,9,0])
|
||||
>>> h
|
||||
[9, 8, 7, 4, 5, 6, 3, 2, 1, 0]
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([514,5,61,57,8,99,105])
|
||||
>>> h
|
||||
[514, 57, 105, 5, 8, 99, 61]
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([514,5,61.6,57,8,9.9,105])
|
||||
>>> h
|
||||
[514, 57, 105, 5, 8, 9.9, 61.6]
|
||||
"""
|
||||
self.h = list(collection)
|
||||
self.heap_size = len(self.h)
|
||||
if self.heap_size > 1:
|
||||
|
@ -108,7 +163,24 @@ class Heap(Generic[T]):
|
|||
self.max_heapify(i)
|
||||
|
||||
def extract_max(self) -> T:
|
||||
"""get and remove max from heap"""
|
||||
"""
|
||||
get and remove max from heap
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([20,40,50,20,10])
|
||||
>>> h.extract_max()
|
||||
50
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([514,5,61,57,8,99,105])
|
||||
>>> h.extract_max()
|
||||
514
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.build_max_heap([1,2,3,4,5,6,7,8,9,0])
|
||||
>>> h.extract_max()
|
||||
9
|
||||
"""
|
||||
if self.heap_size >= 2:
|
||||
me = self.h[0]
|
||||
self.h[0] = self.h.pop(-1)
|
||||
|
@ -122,7 +194,34 @@ class Heap(Generic[T]):
|
|||
raise Exception("Empty heap")
|
||||
|
||||
def insert(self, value: T) -> None:
|
||||
"""insert a new value into the max heap"""
|
||||
"""
|
||||
insert a new value into the max heap
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.insert(10)
|
||||
>>> h
|
||||
[10]
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.insert(10)
|
||||
>>> h.insert(10)
|
||||
>>> h
|
||||
[10, 10]
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.insert(10)
|
||||
>>> h.insert(10.1)
|
||||
>>> h
|
||||
[10.1, 10]
|
||||
|
||||
>>> h = Heap()
|
||||
>>> h.insert(0.1)
|
||||
>>> h.insert(0)
|
||||
>>> h.insert(9)
|
||||
>>> h.insert(5)
|
||||
>>> h
|
||||
[9, 5, 0.1, 0]
|
||||
"""
|
||||
self.h.append(value)
|
||||
idx = (self.heap_size - 1) // 2
|
||||
self.heap_size += 1
|
||||
|
|
|
@ -22,14 +22,40 @@ class RandomizedHeapNode(Generic[T]):
|
|||
|
||||
@property
|
||||
def value(self) -> T:
|
||||
"""Return the value of the node."""
|
||||
"""
|
||||
Return the value of the node.
|
||||
|
||||
>>> rhn = RandomizedHeapNode(10)
|
||||
>>> rhn.value
|
||||
10
|
||||
>>> rhn = RandomizedHeapNode(-10)
|
||||
>>> rhn.value
|
||||
-10
|
||||
"""
|
||||
return self._value
|
||||
|
||||
@staticmethod
|
||||
def merge(
|
||||
root1: RandomizedHeapNode[T] | None, root2: RandomizedHeapNode[T] | None
|
||||
) -> RandomizedHeapNode[T] | None:
|
||||
"""Merge 2 nodes together."""
|
||||
"""
|
||||
Merge 2 nodes together.
|
||||
|
||||
>>> rhn1 = RandomizedHeapNode(10)
|
||||
>>> rhn2 = RandomizedHeapNode(20)
|
||||
>>> RandomizedHeapNode.merge(rhn1, rhn2).value
|
||||
10
|
||||
|
||||
>>> rhn1 = RandomizedHeapNode(20)
|
||||
>>> rhn2 = RandomizedHeapNode(10)
|
||||
>>> RandomizedHeapNode.merge(rhn1, rhn2).value
|
||||
10
|
||||
|
||||
>>> rhn1 = RandomizedHeapNode(5)
|
||||
>>> rhn2 = RandomizedHeapNode(0)
|
||||
>>> RandomizedHeapNode.merge(rhn1, rhn2).value
|
||||
0
|
||||
"""
|
||||
if not root1:
|
||||
return root2
|
||||
|
||||
|
|
|
@ -1,90 +1,156 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
def __init__(self, data: Any):
|
||||
self.data: Any = data
|
||||
self.next: Node | None = None
|
||||
data: Any
|
||||
next_node: Node | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class CircularLinkedList:
|
||||
def __init__(self):
|
||||
self.head = None
|
||||
self.tail = None
|
||||
head: Node | None = None # Reference to the head (first node)
|
||||
tail: Node | None = None # Reference to the tail (last node)
|
||||
|
||||
def __iter__(self) -> Iterator[Any]:
|
||||
"""
|
||||
Iterate through all nodes in the Circular Linked List yielding their data.
|
||||
Yields:
|
||||
The data of each node in the linked list.
|
||||
"""
|
||||
node = self.head
|
||||
while self.head:
|
||||
while node:
|
||||
yield node.data
|
||||
node = node.next
|
||||
node = node.next_node
|
||||
if node == self.head:
|
||||
break
|
||||
|
||||
def __len__(self) -> int:
|
||||
"""
|
||||
Get the length (number of nodes) in the Circular Linked List.
|
||||
"""
|
||||
return sum(1 for _ in self)
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
Generate a string representation of the Circular Linked List.
|
||||
Returns:
|
||||
A string of the format "1->2->....->N".
|
||||
"""
|
||||
return "->".join(str(item) for item in iter(self))
|
||||
|
||||
def insert_tail(self, data: Any) -> None:
|
||||
"""
|
||||
Insert a node with the given data at the end of the Circular Linked List.
|
||||
"""
|
||||
self.insert_nth(len(self), data)
|
||||
|
||||
def insert_head(self, data: Any) -> None:
|
||||
"""
|
||||
Insert a node with the given data at the beginning of the Circular Linked List.
|
||||
"""
|
||||
self.insert_nth(0, data)
|
||||
|
||||
def insert_nth(self, index: int, data: Any) -> None:
|
||||
"""
|
||||
Insert the data of the node at the nth pos in the Circular Linked List.
|
||||
Args:
|
||||
index: The index at which the data should be inserted.
|
||||
data: The data to be inserted.
|
||||
|
||||
Raises:
|
||||
IndexError: If the index is out of range.
|
||||
"""
|
||||
if index < 0 or index > len(self):
|
||||
raise IndexError("list index out of range.")
|
||||
new_node = Node(data)
|
||||
new_node: Node = Node(data)
|
||||
if self.head is None:
|
||||
new_node.next = new_node # first node points itself
|
||||
new_node.next_node = new_node # First node points to itself
|
||||
self.tail = self.head = new_node
|
||||
elif index == 0: # insert at head
|
||||
new_node.next = self.head
|
||||
self.head = self.tail.next = new_node
|
||||
elif index == 0: # Insert at the head
|
||||
new_node.next_node = self.head
|
||||
assert self.tail is not None # List is not empty, tail exists
|
||||
self.head = self.tail.next_node = new_node
|
||||
else:
|
||||
temp = self.head
|
||||
temp: Node | None = self.head
|
||||
for _ in range(index - 1):
|
||||
temp = temp.next
|
||||
new_node.next = temp.next
|
||||
temp.next = new_node
|
||||
if index == len(self) - 1: # insert at tail
|
||||
assert temp is not None
|
||||
temp = temp.next_node
|
||||
assert temp is not None
|
||||
new_node.next_node = temp.next_node
|
||||
temp.next_node = new_node
|
||||
if index == len(self) - 1: # Insert at the tail
|
||||
self.tail = new_node
|
||||
|
||||
def delete_front(self):
|
||||
def delete_front(self) -> Any:
|
||||
"""
|
||||
Delete and return the data of the node at the front of the Circular Linked List.
|
||||
Raises:
|
||||
IndexError: If the list is empty.
|
||||
"""
|
||||
return self.delete_nth(0)
|
||||
|
||||
def delete_tail(self) -> Any:
|
||||
"""
|
||||
Delete and return the data of the node at the end of the Circular Linked List.
|
||||
Returns:
|
||||
Any: The data of the deleted node.
|
||||
Raises:
|
||||
IndexError: If the index is out of range.
|
||||
"""
|
||||
return self.delete_nth(len(self) - 1)
|
||||
|
||||
def delete_nth(self, index: int = 0) -> Any:
|
||||
"""
|
||||
Delete and return the data of the node at the nth pos in Circular Linked List.
|
||||
Args:
|
||||
index (int): The index of the node to be deleted. Defaults to 0.
|
||||
Returns:
|
||||
Any: The data of the deleted node.
|
||||
Raises:
|
||||
IndexError: If the index is out of range.
|
||||
"""
|
||||
if not 0 <= index < len(self):
|
||||
raise IndexError("list index out of range.")
|
||||
delete_node = self.head
|
||||
if self.head == self.tail: # just one node
|
||||
|
||||
assert self.head is not None
|
||||
assert self.tail is not None
|
||||
delete_node: Node = self.head
|
||||
if self.head == self.tail: # Just one node
|
||||
self.head = self.tail = None
|
||||
elif index == 0: # delete head node
|
||||
self.tail.next = self.tail.next.next
|
||||
self.head = self.head.next
|
||||
elif index == 0: # Delete head node
|
||||
assert self.tail.next_node is not None
|
||||
self.tail.next_node = self.tail.next_node.next_node
|
||||
self.head = self.head.next_node
|
||||
else:
|
||||
temp = self.head
|
||||
temp: Node | None = self.head
|
||||
for _ in range(index - 1):
|
||||
temp = temp.next
|
||||
delete_node = temp.next
|
||||
temp.next = temp.next.next
|
||||
if index == len(self) - 1: # delete at tail
|
||||
assert temp is not None
|
||||
temp = temp.next_node
|
||||
assert temp is not None
|
||||
assert temp.next_node is not None
|
||||
delete_node = temp.next_node
|
||||
temp.next_node = temp.next_node.next_node
|
||||
if index == len(self) - 1: # Delete at tail
|
||||
self.tail = temp
|
||||
return delete_node.data
|
||||
|
||||
def is_empty(self) -> bool:
|
||||
"""
|
||||
Check if the Circular Linked List is empty.
|
||||
Returns:
|
||||
bool: True if the list is empty, False otherwise.
|
||||
"""
|
||||
return len(self) == 0
|
||||
|
||||
|
||||
def test_circular_linked_list() -> None:
|
||||
"""
|
||||
Test cases for the CircularLinkedList class.
|
||||
>>> test_circular_linked_list()
|
||||
"""
|
||||
circular_linked_list = CircularLinkedList()
|
||||
|
|
150
data_structures/linked_list/floyds_cycle_detection.py
Normal file
150
data_structures/linked_list/floyds_cycle_detection.py
Normal file
|
@ -0,0 +1,150 @@
|
|||
"""
|
||||
Floyd's cycle detection algorithm is a popular algorithm used to detect cycles
|
||||
in a linked list. It uses two pointers, a slow pointer and a fast pointer,
|
||||
to traverse the linked list. The slow pointer moves one node at a time while the fast
|
||||
pointer moves two nodes at a time. If there is a cycle in the linked list,
|
||||
the fast pointer will eventually catch up to the slow pointer and they will
|
||||
meet at the same node. If there is no cycle, the fast pointer will reach the end of
|
||||
the linked list and the algorithm will terminate.
|
||||
|
||||
For more information: https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare
|
||||
"""
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Self
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
"""
|
||||
A class representing a node in a singly linked list.
|
||||
"""
|
||||
|
||||
data: Any
|
||||
next_node: Self | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinkedList:
|
||||
"""
|
||||
A class representing a singly linked list.
|
||||
"""
|
||||
|
||||
head: Node | None = None
|
||||
|
||||
def __iter__(self) -> Iterator:
|
||||
"""
|
||||
Iterates through the linked list.
|
||||
|
||||
Returns:
|
||||
Iterator: An iterator over the linked list.
|
||||
|
||||
Examples:
|
||||
>>> linked_list = LinkedList()
|
||||
>>> list(linked_list)
|
||||
[]
|
||||
>>> linked_list.add_node(1)
|
||||
>>> tuple(linked_list)
|
||||
(1,)
|
||||
"""
|
||||
visited = []
|
||||
node = self.head
|
||||
while node:
|
||||
# Avoid infinite loop in there's a cycle
|
||||
if node in visited:
|
||||
return
|
||||
visited.append(node)
|
||||
yield node.data
|
||||
node = node.next_node
|
||||
|
||||
def add_node(self, data: Any) -> None:
|
||||
"""
|
||||
Adds a new node to the end of the linked list.
|
||||
|
||||
Args:
|
||||
data (Any): The data to be stored in the new node.
|
||||
|
||||
Examples:
|
||||
>>> linked_list = LinkedList()
|
||||
>>> linked_list.add_node(1)
|
||||
>>> linked_list.add_node(2)
|
||||
>>> linked_list.add_node(3)
|
||||
>>> linked_list.add_node(4)
|
||||
>>> tuple(linked_list)
|
||||
(1, 2, 3, 4)
|
||||
"""
|
||||
new_node = Node(data)
|
||||
|
||||
if self.head is None:
|
||||
self.head = new_node
|
||||
return
|
||||
|
||||
current_node = self.head
|
||||
while current_node.next_node is not None:
|
||||
current_node = current_node.next_node
|
||||
|
||||
current_node.next_node = new_node
|
||||
|
||||
def detect_cycle(self) -> bool:
|
||||
"""
|
||||
Detects if there is a cycle in the linked list using
|
||||
Floyd's cycle detection algorithm.
|
||||
|
||||
Returns:
|
||||
bool: True if there is a cycle, False otherwise.
|
||||
|
||||
Examples:
|
||||
>>> linked_list = LinkedList()
|
||||
>>> linked_list.add_node(1)
|
||||
>>> linked_list.add_node(2)
|
||||
>>> linked_list.add_node(3)
|
||||
>>> linked_list.add_node(4)
|
||||
|
||||
>>> linked_list.detect_cycle()
|
||||
False
|
||||
|
||||
# Create a cycle in the linked list
|
||||
>>> linked_list.head.next_node.next_node.next_node = linked_list.head.next_node
|
||||
|
||||
>>> linked_list.detect_cycle()
|
||||
True
|
||||
"""
|
||||
if self.head is None:
|
||||
return False
|
||||
|
||||
slow_pointer: Node | None = self.head
|
||||
fast_pointer: Node | None = self.head
|
||||
|
||||
while fast_pointer is not None and fast_pointer.next_node is not None:
|
||||
slow_pointer = slow_pointer.next_node if slow_pointer else None
|
||||
fast_pointer = fast_pointer.next_node.next_node
|
||||
if slow_pointer == fast_pointer:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
||||
linked_list = LinkedList()
|
||||
linked_list.add_node(1)
|
||||
linked_list.add_node(2)
|
||||
linked_list.add_node(3)
|
||||
linked_list.add_node(4)
|
||||
|
||||
# Create a cycle in the linked list
|
||||
# It first checks if the head, next_node, and next_node.next_node attributes of the
|
||||
# linked list are not None to avoid any potential type errors.
|
||||
if (
|
||||
linked_list.head
|
||||
and linked_list.head.next_node
|
||||
and linked_list.head.next_node.next_node
|
||||
):
|
||||
linked_list.head.next_node.next_node.next_node = linked_list.head.next_node
|
||||
|
||||
has_cycle = linked_list.detect_cycle()
|
||||
print(has_cycle) # Output: True
|
|
@ -1,65 +1,169 @@
|
|||
def is_palindrome(head):
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ListNode:
|
||||
val: int = 0
|
||||
next_node: ListNode | None = None
|
||||
|
||||
|
||||
def is_palindrome(head: ListNode | None) -> bool:
|
||||
"""
|
||||
Check if a linked list is a palindrome.
|
||||
|
||||
Args:
|
||||
head: The head of the linked list.
|
||||
|
||||
Returns:
|
||||
bool: True if the linked list is a palindrome, False otherwise.
|
||||
|
||||
Examples:
|
||||
>>> is_palindrome(None)
|
||||
True
|
||||
|
||||
>>> is_palindrome(ListNode(1))
|
||||
True
|
||||
|
||||
>>> is_palindrome(ListNode(1, ListNode(2)))
|
||||
False
|
||||
|
||||
>>> is_palindrome(ListNode(1, ListNode(2, ListNode(1))))
|
||||
True
|
||||
|
||||
>>> is_palindrome(ListNode(1, ListNode(2, ListNode(2, ListNode(1)))))
|
||||
True
|
||||
"""
|
||||
if not head:
|
||||
return True
|
||||
# split the list to two parts
|
||||
fast, slow = head.next, head
|
||||
while fast and fast.next:
|
||||
fast = fast.next.next
|
||||
slow = slow.next
|
||||
second = slow.next
|
||||
slow.next = None # Don't forget here! But forget still works!
|
||||
fast: ListNode | None = head.next_node
|
||||
slow: ListNode | None = head
|
||||
while fast and fast.next_node:
|
||||
fast = fast.next_node.next_node
|
||||
slow = slow.next_node if slow else None
|
||||
if slow:
|
||||
# slow will always be defined,
|
||||
# adding this check to resolve mypy static check
|
||||
second = slow.next_node
|
||||
slow.next_node = None # Don't forget here! But forget still works!
|
||||
# reverse the second part
|
||||
node = None
|
||||
node: ListNode | None = None
|
||||
while second:
|
||||
nxt = second.next
|
||||
second.next = node
|
||||
nxt = second.next_node
|
||||
second.next_node = node
|
||||
node = second
|
||||
second = nxt
|
||||
# compare two parts
|
||||
# second part has the same or one less node
|
||||
while node:
|
||||
while node and head:
|
||||
if node.val != head.val:
|
||||
return False
|
||||
node = node.next
|
||||
head = head.next
|
||||
node = node.next_node
|
||||
head = head.next_node
|
||||
return True
|
||||
|
||||
|
||||
def is_palindrome_stack(head):
|
||||
if not head or not head.next:
|
||||
def is_palindrome_stack(head: ListNode | None) -> bool:
|
||||
"""
|
||||
Check if a linked list is a palindrome using a stack.
|
||||
|
||||
Args:
|
||||
head (ListNode): The head of the linked list.
|
||||
|
||||
Returns:
|
||||
bool: True if the linked list is a palindrome, False otherwise.
|
||||
|
||||
Examples:
|
||||
>>> is_palindrome_stack(None)
|
||||
True
|
||||
|
||||
>>> is_palindrome_stack(ListNode(1))
|
||||
True
|
||||
|
||||
>>> is_palindrome_stack(ListNode(1, ListNode(2)))
|
||||
False
|
||||
|
||||
>>> is_palindrome_stack(ListNode(1, ListNode(2, ListNode(1))))
|
||||
True
|
||||
|
||||
>>> is_palindrome_stack(ListNode(1, ListNode(2, ListNode(2, ListNode(1)))))
|
||||
True
|
||||
"""
|
||||
if not head or not head.next_node:
|
||||
return True
|
||||
|
||||
# 1. Get the midpoint (slow)
|
||||
slow = fast = cur = head
|
||||
while fast and fast.next:
|
||||
fast, slow = fast.next.next, slow.next
|
||||
slow: ListNode | None = head
|
||||
fast: ListNode | None = head
|
||||
while fast and fast.next_node:
|
||||
fast = fast.next_node.next_node
|
||||
slow = slow.next_node if slow else None
|
||||
|
||||
# 2. Push the second half into the stack
|
||||
stack = [slow.val]
|
||||
while slow.next:
|
||||
slow = slow.next
|
||||
stack.append(slow.val)
|
||||
# slow will always be defined,
|
||||
# adding this check to resolve mypy static check
|
||||
if slow:
|
||||
stack = [slow.val]
|
||||
|
||||
# 3. Comparison
|
||||
while stack:
|
||||
if stack.pop() != cur.val:
|
||||
return False
|
||||
cur = cur.next
|
||||
# 2. Push the second half into the stack
|
||||
while slow.next_node:
|
||||
slow = slow.next_node
|
||||
stack.append(slow.val)
|
||||
|
||||
# 3. Comparison
|
||||
cur: ListNode | None = head
|
||||
while stack and cur:
|
||||
if stack.pop() != cur.val:
|
||||
return False
|
||||
cur = cur.next_node
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def is_palindrome_dict(head):
|
||||
if not head or not head.next:
|
||||
def is_palindrome_dict(head: ListNode | None) -> bool:
|
||||
"""
|
||||
Check if a linked list is a palindrome using a dictionary.
|
||||
|
||||
Args:
|
||||
head (ListNode): The head of the linked list.
|
||||
|
||||
Returns:
|
||||
bool: True if the linked list is a palindrome, False otherwise.
|
||||
|
||||
Examples:
|
||||
>>> is_palindrome_dict(None)
|
||||
True
|
||||
|
||||
>>> is_palindrome_dict(ListNode(1))
|
||||
True
|
||||
|
||||
>>> is_palindrome_dict(ListNode(1, ListNode(2)))
|
||||
False
|
||||
|
||||
>>> is_palindrome_dict(ListNode(1, ListNode(2, ListNode(1))))
|
||||
True
|
||||
|
||||
>>> is_palindrome_dict(ListNode(1, ListNode(2, ListNode(2, ListNode(1)))))
|
||||
True
|
||||
|
||||
>>> is_palindrome_dict(
|
||||
... ListNode(
|
||||
... 1, ListNode(2, ListNode(1, ListNode(3, ListNode(2, ListNode(1)))))
|
||||
... )
|
||||
... )
|
||||
False
|
||||
"""
|
||||
if not head or not head.next_node:
|
||||
return True
|
||||
d = {}
|
||||
d: dict[int, list[int]] = {}
|
||||
pos = 0
|
||||
while head:
|
||||
if head.val in d:
|
||||
d[head.val].append(pos)
|
||||
else:
|
||||
d[head.val] = [pos]
|
||||
head = head.next
|
||||
head = head.next_node
|
||||
pos += 1
|
||||
checksum = pos - 1
|
||||
middle = 0
|
||||
|
@ -75,3 +179,9 @@ def is_palindrome_dict(head):
|
|||
if middle > 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,22 +1,91 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable, Iterator
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Node:
|
||||
def __init__(self, data=None):
|
||||
self.data = data
|
||||
self.next = None
|
||||
|
||||
def __repr__(self):
|
||||
"""Returns a visual representation of the node and all its following nodes."""
|
||||
string_rep = []
|
||||
temp = self
|
||||
while temp:
|
||||
string_rep.append(f"{temp.data}")
|
||||
temp = temp.next
|
||||
return "->".join(string_rep)
|
||||
data: int
|
||||
next_node: Node | None = None
|
||||
|
||||
|
||||
def make_linked_list(elements_list: list):
|
||||
class LinkedList:
|
||||
"""A class to represent a Linked List.
|
||||
Use a tail pointer to speed up the append() operation.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize a LinkedList with the head node set to None.
|
||||
>>> linked_list = LinkedList()
|
||||
>>> (linked_list.head, linked_list.tail)
|
||||
(None, None)
|
||||
"""
|
||||
self.head: Node | None = None
|
||||
self.tail: Node | None = None # Speeds up the append() operation
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
"""Iterate the LinkedList yielding each Node's data.
|
||||
>>> linked_list = LinkedList()
|
||||
>>> items = (1, 2, 3, 4, 5)
|
||||
>>> linked_list.extend(items)
|
||||
>>> tuple(linked_list) == items
|
||||
True
|
||||
"""
|
||||
node = self.head
|
||||
while node:
|
||||
yield node.data
|
||||
node = node.next_node
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Returns a string representation of the LinkedList.
|
||||
>>> linked_list = LinkedList()
|
||||
>>> str(linked_list)
|
||||
''
|
||||
>>> linked_list.append(1)
|
||||
>>> str(linked_list)
|
||||
'1'
|
||||
>>> linked_list.extend([2, 3, 4, 5])
|
||||
>>> str(linked_list)
|
||||
'1 -> 2 -> 3 -> 4 -> 5'
|
||||
"""
|
||||
return " -> ".join([str(data) for data in self])
|
||||
|
||||
def append(self, data: int) -> None:
|
||||
"""Appends a new node with the given data to the end of the LinkedList.
|
||||
>>> linked_list = LinkedList()
|
||||
>>> str(linked_list)
|
||||
''
|
||||
>>> linked_list.append(1)
|
||||
>>> str(linked_list)
|
||||
'1'
|
||||
>>> linked_list.append(2)
|
||||
>>> str(linked_list)
|
||||
'1 -> 2'
|
||||
"""
|
||||
if self.tail:
|
||||
self.tail.next_node = self.tail = Node(data)
|
||||
else:
|
||||
self.head = self.tail = Node(data)
|
||||
|
||||
def extend(self, items: Iterable[int]) -> None:
|
||||
"""Appends each item to the end of the LinkedList.
|
||||
>>> linked_list = LinkedList()
|
||||
>>> linked_list.extend([])
|
||||
>>> str(linked_list)
|
||||
''
|
||||
>>> linked_list.extend([1, 2])
|
||||
>>> str(linked_list)
|
||||
'1 -> 2'
|
||||
>>> linked_list.extend([3,4])
|
||||
>>> str(linked_list)
|
||||
'1 -> 2 -> 3 -> 4'
|
||||
"""
|
||||
for item in items:
|
||||
self.append(item)
|
||||
|
||||
|
||||
def make_linked_list(elements_list: Iterable[int]) -> LinkedList:
|
||||
"""Creates a Linked List from the elements of the given sequence
|
||||
(list/tuple) and returns the head of the Linked List.
|
||||
>>> make_linked_list([])
|
||||
|
@ -28,43 +97,30 @@ def make_linked_list(elements_list: list):
|
|||
>>> make_linked_list(['abc'])
|
||||
abc
|
||||
>>> make_linked_list([7, 25])
|
||||
7->25
|
||||
7 -> 25
|
||||
"""
|
||||
if not elements_list:
|
||||
raise Exception("The Elements List is empty")
|
||||
|
||||
current = head = Node(elements_list[0])
|
||||
for i in range(1, len(elements_list)):
|
||||
current.next = Node(elements_list[i])
|
||||
current = current.next
|
||||
return head
|
||||
linked_list = LinkedList()
|
||||
linked_list.extend(elements_list)
|
||||
return linked_list
|
||||
|
||||
|
||||
def print_reverse(head_node: Node) -> None:
|
||||
def in_reverse(linked_list: LinkedList) -> str:
|
||||
"""Prints the elements of the given Linked List in reverse order
|
||||
>>> print_reverse([])
|
||||
>>> linked_list = make_linked_list([69, 88, 73])
|
||||
>>> print_reverse(linked_list)
|
||||
73
|
||||
88
|
||||
69
|
||||
>>> in_reverse(LinkedList())
|
||||
''
|
||||
>>> in_reverse(make_linked_list([69, 88, 73]))
|
||||
'73 <- 88 <- 69'
|
||||
"""
|
||||
if head_node is not None and isinstance(head_node, Node):
|
||||
print_reverse(head_node.next)
|
||||
print(head_node.data)
|
||||
|
||||
|
||||
def main():
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
|
||||
linked_list = make_linked_list([14, 52, 14, 12, 43])
|
||||
print("Linked List:")
|
||||
print(linked_list)
|
||||
print("Elements in Reverse:")
|
||||
print_reverse(linked_list)
|
||||
return " <- ".join(str(line) for line in reversed(tuple(linked_list)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
from doctest import testmod
|
||||
|
||||
testmod()
|
||||
linked_list = make_linked_list((14, 52, 14, 12, 43))
|
||||
print(f"Linked List: {linked_list}")
|
||||
print(f"Reverse List: {in_reverse(linked_list)}")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user