Merge branch 'TheAlgorithms:master' into naive-bayes-text-classifier

This commit is contained in:
Riccardo Cappi 2023-11-28 19:12:34 +01:00 committed by GitHub
commit 9433dce018
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
343 changed files with 18288 additions and 3960 deletions

2
.github/CODEOWNERS vendored
View File

@ -7,7 +7,7 @@
# Order is important. The last matching pattern has the most precedence.
/.* @cclauss @dhruvmanila
/.* @cclauss
# /arithmetic_analysis/

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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:')

View File

@ -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)

View File

@ -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>

View File

@ -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))

View File

@ -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)}")

View File

@ -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)}")

View File

@ -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())}")

View 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:")

View 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()

View File

@ -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)

View 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()

View File

@ -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))

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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):

View 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()

View File

@ -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")

View 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()

View 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()

View File

@ -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

View 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()

View File

@ -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()

View 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()

View 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")

View File

@ -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)

View File

@ -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()

View 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()

View 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]]) = }")

View 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()

View File

@ -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()

View 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()

View File

@ -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))

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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)

View 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)

View File

@ -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:

View File

@ -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])

View 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()

View File

@ -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

View File

@ -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

View 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 = }")

View File

@ -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}")

View File

@ -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
View 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
View 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)

View File

@ -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

View File

@ -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.
"""

View File

@ -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

View File

@ -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

View File

@ -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

View 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()

View 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("---")

View 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()

View 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') = :,}")

View 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()

View 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()

View 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()

View 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()

View 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()

View 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

View 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()

View 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) = }")

View 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)

View File

@ -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()

View File

@ -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)

View File

@ -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:

View 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

View 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)

View 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()

View File

@ -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()

View 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 = }.")

View 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 = }.")

View 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()

View File

@ -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

View 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()

View 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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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 +
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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()

View 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

View File

@ -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()

View File

@ -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