diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9e15d18ade8e..7273119302e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,7 +20,30 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip setuptools six wheel - python -m pip install pytest-cov -r requirements.txt + python -m pip install mypy pytest-cov -r requirements.txt + # FIXME: #4052 fix mypy errors in other directories and add them here + - run: mypy --ignore-missing-imports + backtracking + bit_manipulation + blockchain + boolean_algebra + cellular_automata + compression + computer_vision + divide_and_conquer + electronics + file_transfer + fractals + fuzzy_logic + genetic_algorithm + geodesy + knapsack + machine_learning + networking_flow + neural_network + quantum + scheduling + sorts - name: Run tests run: pytest --doctest-modules --ignore=project_euler/ --ignore=scripts/ --cov-report=term-missing:skip-covered --cov=. . - if: ${{ success() }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 96175cfecea5..17fdad1204e9 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -14,6 +14,7 @@ jobs: ~/.cache/pip key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} - uses: actions/setup-python@v2 + - uses: psf/black@stable - name: Install pre-commit run: | python -m pip install --upgrade pip diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3288e1c5eef..b48da86ee57d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.2.0 + rev: v3.4.0 hooks: - id: check-executables-have-shebangs - id: check-yaml @@ -13,17 +13,17 @@ repos: )$ - id: requirements-txt-fixer - repo: https://github.com/psf/black - rev: stable + rev: 20.8b1 hooks: - id: black - repo: https://github.com/PyCQA/isort - rev: 5.5.3 + rev: 5.7.0 hooks: - id: isort args: - --profile=black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.9.0 hooks: - id: flake8 args: @@ -38,17 +38,17 @@ repos: # args: # - --ignore-missing-imports - repo: https://github.com/codespell-project/codespell - rev: v1.17.1 + rev: v2.0.0 hooks: - id: codespell args: - - --ignore-words-list=ans,fo,followings,hist,iff,mater,secant,som,tim - - --skip="./.*,./other/dictionary.txt,./other/words,./project_euler/problem_022/p022_names.txt" + - --ignore-words-list=ans,crate,fo,followings,hist,iff,mater,secant,som,tim + - --skip="./.*,./strings/dictionary.txt,./strings/words.txt,./project_euler/problem_022/p022_names.txt" - --quiet-level=2 exclude: | (?x)^( - other/dictionary.txt | - other/words | + strings/dictionary.txt | + strings/words.txt | project_euler/problem_022/p022_names.txt )$ - repo: local diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e4c81a5ecd98..76ee1312f345 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -155,6 +155,8 @@ We want your work to be readable by others; therefore, we encourage you to note return a + b ``` + Instructions on how to install mypy can be found [here](https://github.com/python/mypy). Please use the command `mypy --ignore-missing-imports .` to test all files or `mypy --ignore-missing-imports path/to/file.py` to test a specific file. + - [__List comprehensions and generators__](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) are preferred over the use of `lambda`, `map`, `filter`, `reduce` but the important thing is to demonstrate the power of Python in code that is easy to read and maintain. - Avoid importing external libraries for basic algorithms. Only use those libraries for complicated algorithms. diff --git a/DIRECTORY.md b/DIRECTORY.md index 4f17cf9c03ed..42a6c49c735f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -29,7 +29,11 @@ * [Binary Count Setbits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_setbits.py) * [Binary Count Trailing Zeros](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_count_trailing_zeros.py) * [Binary Or Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_or_operator.py) + * [Binary Shifts](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_shifts.py) + * [Binary Twos Complement](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_twos_complement.py) * [Binary Xor Operator](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/binary_xor_operator.py) + * [Count Number Of One Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/count_number_of_one_bits.py) + * [Reverse Bits](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/reverse_bits.py) * [Single Bit Manipulation Operations](https://github.com/TheAlgorithms/Python/blob/master/bit_manipulation/single_bit_manipulation_operations.py) ## Blockchain @@ -42,6 +46,7 @@ ## Cellular Automata * [Conways Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/conways_game_of_life.py) + * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/game_of_life.py) * [One Dimensional](https://github.com/TheAlgorithms/Python/blob/master/cellular_automata/one_dimensional.py) ## Ciphers @@ -59,6 +64,7 @@ * [Decrypt Caesar With Chi Squared](https://github.com/TheAlgorithms/Python/blob/master/ciphers/decrypt_caesar_with_chi_squared.py) * [Deterministic Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/ciphers/deterministic_miller_rabin.py) * [Diffie](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie.py) + * [Diffie Hellman](https://github.com/TheAlgorithms/Python/blob/master/ciphers/diffie_hellman.py) * [Elgamal Key Generator](https://github.com/TheAlgorithms/Python/blob/master/ciphers/elgamal_key_generator.py) * [Enigma Machine2](https://github.com/TheAlgorithms/Python/blob/master/ciphers/enigma_machine2.py) * [Hill Cipher](https://github.com/TheAlgorithms/Python/blob/master/ciphers/hill_cipher.py) @@ -121,6 +127,7 @@ * [Fenwick Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/fenwick_tree.py) * [Lazy Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lazy_segment_tree.py) * [Lowest Common Ancestor](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/lowest_common_ancestor.py) + * [Merge Two Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/merge_two_binary_trees.py) * [Non Recursive Segment Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/non_recursive_segment_tree.py) * [Number Of Possible Binary Trees](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/number_of_possible_binary_trees.py) * [Red Black Tree](https://github.com/TheAlgorithms/Python/blob/master/data_structures/binary_tree/red_black_tree.py) @@ -224,7 +231,6 @@ * [Abbreviation](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/abbreviation.py) * [Bitmask](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/bitmask.py) * [Climbing Stairs](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/climbing_stairs.py) - * [Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/coin_change.py) * [Edit Distance](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/edit_distance.py) * [Factorial](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/factorial.py) * [Fast Fibonacci](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/fast_fibonacci.py) @@ -243,6 +249,7 @@ * [Max Non Adjacent Sum](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_non_adjacent_sum.py) * [Max Sub Array](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sub_array.py) * [Max Sum Contiguous Subsequence](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/max_sum_contiguous_subsequence.py) + * [Minimum Coin Change](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_coin_change.py) * [Minimum Cost Path](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_cost_path.py) * [Minimum Partition](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_partition.py) * [Minimum Steps To One](https://github.com/TheAlgorithms/Python/blob/master/dynamic_programming/minimum_steps_to_one.py) @@ -261,6 +268,11 @@ * Tests * [Test Send File](https://github.com/TheAlgorithms/Python/blob/master/file_transfer/tests/test_send_file.py) +## Fractals + * [Koch Snowflake](https://github.com/TheAlgorithms/Python/blob/master/fractals/koch_snowflake.py) + * [Mandelbrot](https://github.com/TheAlgorithms/Python/blob/master/fractals/mandelbrot.py) + * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/fractals/sierpinski_triangle.py) + ## Fuzzy Logic * [Fuzzy Operations](https://github.com/TheAlgorithms/Python/blob/master/fuzzy_logic/fuzzy_operations.py) @@ -311,6 +323,7 @@ * [Kahns Algorithm Long](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_long.py) * [Kahns Algorithm Topo](https://github.com/TheAlgorithms/Python/blob/master/graphs/kahns_algorithm_topo.py) * [Karger](https://github.com/TheAlgorithms/Python/blob/master/graphs/karger.py) + * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/graphs/markov_chain.py) * [Minimum Spanning Tree Boruvka](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_boruvka.py) * [Minimum Spanning Tree Kruskal](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal.py) * [Minimum Spanning Tree Kruskal2](https://github.com/TheAlgorithms/Python/blob/master/graphs/minimum_spanning_tree_kruskal2.py) @@ -396,6 +409,8 @@ * [Basic Maths](https://github.com/TheAlgorithms/Python/blob/master/maths/basic_maths.py) * [Binary Exp Mod](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exp_mod.py) * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation.py) + * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_2.py) + * [Binary Exponentiation 3](https://github.com/TheAlgorithms/Python/blob/master/maths/binary_exponentiation_3.py) * [Binomial Coefficient](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_coefficient.py) * [Binomial Distribution](https://github.com/TheAlgorithms/Python/blob/master/maths/binomial_distribution.py) * [Bisection](https://github.com/TheAlgorithms/Python/blob/master/maths/bisection.py) @@ -406,8 +421,9 @@ * [Decimal Isolate](https://github.com/TheAlgorithms/Python/blob/master/maths/decimal_isolate.py) * [Entropy](https://github.com/TheAlgorithms/Python/blob/master/maths/entropy.py) * [Euclidean Distance](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_distance.py) + * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/maths/euclidean_gcd.py) + * [Euler Method](https://github.com/TheAlgorithms/Python/blob/master/maths/euler_method.py) * [Eulers Totient](https://github.com/TheAlgorithms/Python/blob/master/maths/eulers_totient.py) - * [Explicit Euler](https://github.com/TheAlgorithms/Python/blob/master/maths/explicit_euler.py) * [Extended Euclidean Algorithm](https://github.com/TheAlgorithms/Python/blob/master/maths/extended_euclidean_algorithm.py) * [Factorial Iterative](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_iterative.py) * [Factorial Python](https://github.com/TheAlgorithms/Python/blob/master/maths/factorial_python.py) @@ -424,7 +440,9 @@ * [Gamma](https://github.com/TheAlgorithms/Python/blob/master/maths/gamma.py) * [Gaussian](https://github.com/TheAlgorithms/Python/blob/master/maths/gaussian.py) * [Greatest Common Divisor](https://github.com/TheAlgorithms/Python/blob/master/maths/greatest_common_divisor.py) + * [Greedy Coin Change](https://github.com/TheAlgorithms/Python/blob/master/maths/greedy_coin_change.py) * [Hardy Ramanujanalgo](https://github.com/TheAlgorithms/Python/blob/master/maths/hardy_ramanujanalgo.py) + * [Integration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/maths/integration_by_simpson_approx.py) * [Is Square Free](https://github.com/TheAlgorithms/Python/blob/master/maths/is_square_free.py) * [Jaccard Similarity](https://github.com/TheAlgorithms/Python/blob/master/maths/jaccard_similarity.py) * [Kadanes](https://github.com/TheAlgorithms/Python/blob/master/maths/kadanes.py) @@ -432,11 +450,14 @@ * [Krishnamurthy Number](https://github.com/TheAlgorithms/Python/blob/master/maths/krishnamurthy_number.py) * [Kth Lexicographic Permutation](https://github.com/TheAlgorithms/Python/blob/master/maths/kth_lexicographic_permutation.py) * [Largest Of Very Large Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_of_very_large_numbers.py) + * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/largest_subarray_sum.py) * [Least Common Multiple](https://github.com/TheAlgorithms/Python/blob/master/maths/least_common_multiple.py) * [Line Length](https://github.com/TheAlgorithms/Python/blob/master/maths/line_length.py) * [Lucas Lehmer Primality Test](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_lehmer_primality_test.py) * [Lucas Series](https://github.com/TheAlgorithms/Python/blob/master/maths/lucas_series.py) * [Matrix Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/maths/matrix_exponentiation.py) + * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/maths/max_sum_sliding_window.py) + * [Median Of Two Arrays](https://github.com/TheAlgorithms/Python/blob/master/maths/median_of_two_arrays.py) * [Miller Rabin](https://github.com/TheAlgorithms/Python/blob/master/maths/miller_rabin.py) * [Mobius Function](https://github.com/TheAlgorithms/Python/blob/master/maths/mobius_function.py) * [Modular Exponential](https://github.com/TheAlgorithms/Python/blob/master/maths/modular_exponential.py) @@ -455,6 +476,7 @@ * [Prime Factors](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_factors.py) * [Prime Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_numbers.py) * [Prime Sieve Eratosthenes](https://github.com/TheAlgorithms/Python/blob/master/maths/prime_sieve_eratosthenes.py) + * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/maths/primelib.py) * [Pythagoras](https://github.com/TheAlgorithms/Python/blob/master/maths/pythagoras.py) * [Qr Decomposition](https://github.com/TheAlgorithms/Python/blob/master/maths/qr_decomposition.py) * [Quadratic Equations Complex Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/quadratic_equations_complex_numbers.py) @@ -464,6 +486,8 @@ * [Runge Kutta](https://github.com/TheAlgorithms/Python/blob/master/maths/runge_kutta.py) * [Segmented Sieve](https://github.com/TheAlgorithms/Python/blob/master/maths/segmented_sieve.py) * Series + * [Arithmetic Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/arithmetic_mean.py) + * [Geometric Mean](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_mean.py) * [Geometric Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/geometric_series.py) * [Harmonic Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/harmonic_series.py) * [P Series](https://github.com/TheAlgorithms/Python/blob/master/maths/series/p_series.py) @@ -477,6 +501,9 @@ * [Sum Of Geometric Progression](https://github.com/TheAlgorithms/Python/blob/master/maths/sum_of_geometric_progression.py) * [Test Prime Check](https://github.com/TheAlgorithms/Python/blob/master/maths/test_prime_check.py) * [Trapezoidal Rule](https://github.com/TheAlgorithms/Python/blob/master/maths/trapezoidal_rule.py) + * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/triplet_sum.py) + * [Two Pointer](https://github.com/TheAlgorithms/Python/blob/master/maths/two_pointer.py) + * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/maths/two_sum.py) * [Ugly Numbers](https://github.com/TheAlgorithms/Python/blob/master/maths/ugly_numbers.py) * [Volume](https://github.com/TheAlgorithms/Python/blob/master/maths/volume.py) * [Zellers Congruence](https://github.com/TheAlgorithms/Python/blob/master/maths/zellers_congruence.py) @@ -506,42 +533,23 @@ ## Other * [Activity Selection](https://github.com/TheAlgorithms/Python/blob/master/other/activity_selection.py) - * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/other/anagrams.py) - * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/other/autocomplete_using_trie.py) - * [Binary Exponentiation](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation.py) - * [Binary Exponentiation 2](https://github.com/TheAlgorithms/Python/blob/master/other/binary_exponentiation_2.py) * [Davis–Putnam–Logemann–Loveland](https://github.com/TheAlgorithms/Python/blob/master/other/davis–putnam–logemann–loveland.py) - * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/other/detecting_english_programmatically.py) * [Dijkstra Bankers Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/dijkstra_bankers_algorithm.py) * [Doomsday](https://github.com/TheAlgorithms/Python/blob/master/other/doomsday.py) - * [Euclidean Gcd](https://github.com/TheAlgorithms/Python/blob/master/other/euclidean_gcd.py) * [Fischer Yates Shuffle](https://github.com/TheAlgorithms/Python/blob/master/other/fischer_yates_shuffle.py) - * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/other/frequency_finder.py) - * [Game Of Life](https://github.com/TheAlgorithms/Python/blob/master/other/game_of_life.py) * [Gauss Easter](https://github.com/TheAlgorithms/Python/blob/master/other/gauss_easter.py) + * [Graham Scan](https://github.com/TheAlgorithms/Python/blob/master/other/graham_scan.py) * [Greedy](https://github.com/TheAlgorithms/Python/blob/master/other/greedy.py) - * [Integeration By Simpson Approx](https://github.com/TheAlgorithms/Python/blob/master/other/integeration_by_simpson_approx.py) - * [Largest Subarray Sum](https://github.com/TheAlgorithms/Python/blob/master/other/largest_subarray_sum.py) * [Least Recently Used](https://github.com/TheAlgorithms/Python/blob/master/other/least_recently_used.py) * [Lfu Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lfu_cache.py) * [Linear Congruential Generator](https://github.com/TheAlgorithms/Python/blob/master/other/linear_congruential_generator.py) * [Lru Cache](https://github.com/TheAlgorithms/Python/blob/master/other/lru_cache.py) * [Magicdiamondpattern](https://github.com/TheAlgorithms/Python/blob/master/other/magicdiamondpattern.py) - * [Markov Chain](https://github.com/TheAlgorithms/Python/blob/master/other/markov_chain.py) - * [Max Sum Sliding Window](https://github.com/TheAlgorithms/Python/blob/master/other/max_sum_sliding_window.py) - * [Median Of Two Arrays](https://github.com/TheAlgorithms/Python/blob/master/other/median_of_two_arrays.py) * [Nested Brackets](https://github.com/TheAlgorithms/Python/blob/master/other/nested_brackets.py) - * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/other/palindrome.py) * [Password Generator](https://github.com/TheAlgorithms/Python/blob/master/other/password_generator.py) - * [Primelib](https://github.com/TheAlgorithms/Python/blob/master/other/primelib.py) * [Scoring Algorithm](https://github.com/TheAlgorithms/Python/blob/master/other/scoring_algorithm.py) * [Sdes](https://github.com/TheAlgorithms/Python/blob/master/other/sdes.py) - * [Sierpinski Triangle](https://github.com/TheAlgorithms/Python/blob/master/other/sierpinski_triangle.py) * [Tower Of Hanoi](https://github.com/TheAlgorithms/Python/blob/master/other/tower_of_hanoi.py) - * [Triplet Sum](https://github.com/TheAlgorithms/Python/blob/master/other/triplet_sum.py) - * [Two Pointer](https://github.com/TheAlgorithms/Python/blob/master/other/two_pointer.py) - * [Two Sum](https://github.com/TheAlgorithms/Python/blob/master/other/two_sum.py) - * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/other/word_patterns.py) ## Project Euler * Problem 001 @@ -751,6 +759,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_102/sol1.py) * Problem 107 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_107/sol1.py) + * Problem 109 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_109/sol1.py) * Problem 112 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_112/sol1.py) * Problem 113 @@ -759,6 +769,8 @@ * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_119/sol1.py) * Problem 120 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_120/sol1.py) + * Problem 121 + * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_121/sol1.py) * Problem 123 * [Sol1](https://github.com/TheAlgorithms/Python/blob/master/project_euler/problem_123/sol1.py) * Problem 125 @@ -805,6 +817,7 @@ ## Searches * [Binary Search](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_search.py) + * [Binary Tree Traversal](https://github.com/TheAlgorithms/Python/blob/master/searches/binary_tree_traversal.py) * [Double Linear Search](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search.py) * [Double Linear Search Recursion](https://github.com/TheAlgorithms/Python/blob/master/searches/double_linear_search_recursion.py) * [Fibonacci Search](https://github.com/TheAlgorithms/Python/blob/master/searches/fibonacci_search.py) @@ -839,6 +852,7 @@ * [Merge Insertion Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_insertion_sort.py) * [Merge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/merge_sort.py) * [Natural Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/natural_sort.py) + * [Odd Even Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_sort.py) * [Odd Even Transposition Parallel](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_parallel.py) * [Odd Even Transposition Single Threaded](https://github.com/TheAlgorithms/Python/blob/master/sorts/odd_even_transposition_single_threaded.py) * [Pancake Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/pancake_sort.py) @@ -855,6 +869,7 @@ * [Recursive Quick Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/recursive_quick_sort.py) * [Selection Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/selection_sort.py) * [Shell Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/shell_sort.py) + * [Slowsort](https://github.com/TheAlgorithms/Python/blob/master/sorts/slowsort.py) * [Stooge Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/stooge_sort.py) * [Strand Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/strand_sort.py) * [Tim Sort](https://github.com/TheAlgorithms/Python/blob/master/sorts/tim_sort.py) @@ -865,11 +880,15 @@ ## Strings * [Aho Corasick](https://github.com/TheAlgorithms/Python/blob/master/strings/aho_corasick.py) + * [Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/anagrams.py) + * [Autocomplete Using Trie](https://github.com/TheAlgorithms/Python/blob/master/strings/autocomplete_using_trie.py) * [Boyer Moore Search](https://github.com/TheAlgorithms/Python/blob/master/strings/boyer_moore_search.py) * [Can String Be Rearranged As Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/can_string_be_rearranged_as_palindrome.py) * [Capitalize](https://github.com/TheAlgorithms/Python/blob/master/strings/capitalize.py) * [Check Anagrams](https://github.com/TheAlgorithms/Python/blob/master/strings/check_anagrams.py) * [Check Pangram](https://github.com/TheAlgorithms/Python/blob/master/strings/check_pangram.py) + * [Detecting English Programmatically](https://github.com/TheAlgorithms/Python/blob/master/strings/detecting_english_programmatically.py) + * [Frequency Finder](https://github.com/TheAlgorithms/Python/blob/master/strings/frequency_finder.py) * [Is Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/is_palindrome.py) * [Jaro Winkler](https://github.com/TheAlgorithms/Python/blob/master/strings/jaro_winkler.py) * [Knuth Morris Pratt](https://github.com/TheAlgorithms/Python/blob/master/strings/knuth_morris_pratt.py) @@ -878,6 +897,7 @@ * [Manacher](https://github.com/TheAlgorithms/Python/blob/master/strings/manacher.py) * [Min Cost String Conversion](https://github.com/TheAlgorithms/Python/blob/master/strings/min_cost_string_conversion.py) * [Naive String Search](https://github.com/TheAlgorithms/Python/blob/master/strings/naive_string_search.py) + * [Palindrome](https://github.com/TheAlgorithms/Python/blob/master/strings/palindrome.py) * [Prefix Function](https://github.com/TheAlgorithms/Python/blob/master/strings/prefix_function.py) * [Rabin Karp](https://github.com/TheAlgorithms/Python/blob/master/strings/rabin_karp.py) * [Remove Duplicate](https://github.com/TheAlgorithms/Python/blob/master/strings/remove_duplicate.py) @@ -887,11 +907,9 @@ * [Swap Case](https://github.com/TheAlgorithms/Python/blob/master/strings/swap_case.py) * [Upper](https://github.com/TheAlgorithms/Python/blob/master/strings/upper.py) * [Word Occurrence](https://github.com/TheAlgorithms/Python/blob/master/strings/word_occurrence.py) + * [Word Patterns](https://github.com/TheAlgorithms/Python/blob/master/strings/word_patterns.py) * [Z Function](https://github.com/TheAlgorithms/Python/blob/master/strings/z_function.py) -## Traversals - * [Binary Tree Traversals](https://github.com/TheAlgorithms/Python/blob/master/traversals/binary_tree_traversals.py) - ## Web Programming * [Co2 Emission](https://github.com/TheAlgorithms/Python/blob/master/web_programming/co2_emission.py) * [Covid Stats Via Xpath](https://github.com/TheAlgorithms/Python/blob/master/web_programming/covid_stats_via_xpath.py) diff --git a/LICENSE.md b/LICENSE.md index 3b7951527ab3..c3c2857cd312 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 The Algorithms +Copyright (c) 2016-2021 The Algorithms Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bit_manipulation/binary_shifts.py b/bit_manipulation/binary_shifts.py new file mode 100644 index 000000000000..fe62880f941c --- /dev/null +++ b/bit_manipulation/binary_shifts.py @@ -0,0 +1,111 @@ +# Information on binary shifts: +# https://docs.python.org/3/library/stdtypes.html#bitwise-operations-on-integer-types +# https://www.interviewcake.com/concept/java/bit-shift + + +def logical_left_shift(number: int, shift_amount: int) -> str: + """ + Take in 2 positive integers. + 'number' is the integer to be logically left shifted 'shift_amount' times. + i.e. (number << shift_amount) + Return the shifted binary representation. + + >>> logical_left_shift(0, 1) + '0b00' + >>> logical_left_shift(1, 1) + '0b10' + >>> logical_left_shift(1, 5) + '0b100000' + >>> logical_left_shift(17, 2) + '0b1000100' + >>> logical_left_shift(1983, 4) + '0b111101111110000' + >>> logical_left_shift(1, -1) + Traceback (most recent call last): + ... + ValueError: both inputs must be positive integers + """ + if number < 0 or shift_amount < 0: + raise ValueError("both inputs must be positive integers") + + binary_number = str(bin(number)) + binary_number += "0" * shift_amount + return binary_number + + +def logical_right_shift(number: int, shift_amount: int) -> str: + """ + Take in positive 2 integers. + 'number' is the integer to be logically right shifted 'shift_amount' times. + i.e. (number >>> shift_amount) + Return the shifted binary representation. + + >>> logical_right_shift(0, 1) + '0b0' + >>> logical_right_shift(1, 1) + '0b0' + >>> logical_right_shift(1, 5) + '0b0' + >>> logical_right_shift(17, 2) + '0b100' + >>> logical_right_shift(1983, 4) + '0b1111011' + >>> logical_right_shift(1, -1) + Traceback (most recent call last): + ... + ValueError: both inputs must be positive integers + """ + if number < 0 or shift_amount < 0: + raise ValueError("both inputs must be positive integers") + + binary_number = str(bin(number))[2:] + if shift_amount >= len(binary_number): + return "0b0" + shifted_binary_number = binary_number[: len(binary_number) - shift_amount] + return "0b" + shifted_binary_number + + +def arithmetic_right_shift(number: int, shift_amount: int) -> str: + """ + Take in 2 integers. + 'number' is the integer to be arithmetically right shifted 'shift_amount' times. + i.e. (number >> shift_amount) + Return the shifted binary representation. + + >>> arithmetic_right_shift(0, 1) + '0b00' + >>> arithmetic_right_shift(1, 1) + '0b00' + >>> arithmetic_right_shift(-1, 1) + '0b11' + >>> arithmetic_right_shift(17, 2) + '0b000100' + >>> arithmetic_right_shift(-17, 2) + '0b111011' + >>> arithmetic_right_shift(-1983, 4) + '0b111110000100' + """ + if number >= 0: # Get binary representation of positive number + binary_number = "0" + str(bin(number)).strip("-")[2:] + else: # Get binary (2's complement) representation of negative number + binary_number_length = len(bin(number)[3:]) # Find 2's complement of number + binary_number = bin(abs(number) - (1 << binary_number_length))[3:] + binary_number = ( + ("1" + "0" * (binary_number_length - len(binary_number)) + binary_number) + if number < 0 + else "0" + ) + + if shift_amount >= len(binary_number): + return "0b" + binary_number[0] * len(binary_number) + return ( + "0b" + + binary_number[0] * shift_amount + + binary_number[: len(binary_number) - shift_amount] + ) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/binary_twos_complement.py b/bit_manipulation/binary_twos_complement.py new file mode 100644 index 000000000000..2c064ec142d7 --- /dev/null +++ b/bit_manipulation/binary_twos_complement.py @@ -0,0 +1,43 @@ +# Information on 2's complement: https://en.wikipedia.org/wiki/Two%27s_complement + + +def twos_complement(number: int) -> str: + """ + Take in a negative integer 'number'. + Return the two's complement representation of 'number'. + + >>> twos_complement(0) + '0b0' + >>> twos_complement(-1) + '0b11' + >>> twos_complement(-5) + '0b1011' + >>> twos_complement(-17) + '0b101111' + >>> twos_complement(-207) + '0b100110001' + >>> twos_complement(1) + Traceback (most recent call last): + ... + ValueError: input must be a negative integer + """ + if number > 0: + raise ValueError("input must be a negative integer") + binary_number_length = len(bin(number)[3:]) + twos_complement_number = bin(abs(number) - (1 << binary_number_length))[3:] + twos_complement_number = ( + ( + "1" + + "0" * (binary_number_length - len(twos_complement_number)) + + twos_complement_number + ) + if number < 0 + else "0" + ) + return "0b" + twos_complement_number + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/count_number_of_one_bits.py b/bit_manipulation/count_number_of_one_bits.py new file mode 100644 index 000000000000..51fd2b630483 --- /dev/null +++ b/bit_manipulation/count_number_of_one_bits.py @@ -0,0 +1,34 @@ +def get_set_bits_count(number: int) -> int: + """ + Count the number of set bits in a 32 bit integer + >>> get_set_bits_count(25) + 3 + >>> get_set_bits_count(37) + 3 + >>> get_set_bits_count(21) + 3 + >>> get_set_bits_count(58) + 4 + >>> get_set_bits_count(0) + 0 + >>> get_set_bits_count(256) + 1 + >>> get_set_bits_count(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + """ + if number < 0: + raise ValueError("the value of input must be positive") + result = 0 + while number: + if number % 2 == 1: + result += 1 + number = number >> 1 + return result + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/bit_manipulation/reverse_bits.py b/bit_manipulation/reverse_bits.py new file mode 100644 index 000000000000..55608ae12908 --- /dev/null +++ b/bit_manipulation/reverse_bits.py @@ -0,0 +1,85 @@ +def get_reverse_bit_string(number: int) -> str: + """ + return the bit string of an integer + + >>> get_reverse_bit_string(9) + '10010000000000000000000000000000' + >>> get_reverse_bit_string(43) + '11010100000000000000000000000000' + >>> get_reverse_bit_string(2873) + '10011100110100000000000000000000' + >>> get_reverse_bit_string("this is not a number") + Traceback (most recent call last): + ... + TypeError: operation can not be conducted on a object of type str + """ + if not isinstance(number, int): + raise TypeError( + "operation can not be conducted on a object of type " + f"{type(number).__name__}" + ) + bit_string = "" + for _ in range(0, 32): + bit_string += str(number % 2) + number = number >> 1 + return bit_string + + +def reverse_bit(number: int) -> str: + """ + Take in an 32 bit integer, reverse its bits, + return a string of reverse bits + + result of a reverse_bit and operation on the integer provided. + + >>> reverse_bit(25) + '00000000000000000000000000011001' + >>> reverse_bit(37) + '00000000000000000000000000100101' + >>> reverse_bit(21) + '00000000000000000000000000010101' + >>> reverse_bit(58) + '00000000000000000000000000111010' + >>> reverse_bit(0) + '00000000000000000000000000000000' + >>> reverse_bit(256) + '00000000000000000000000100000000' + >>> reverse_bit(-1) + Traceback (most recent call last): + ... + ValueError: the value of input must be positive + + >>> reverse_bit(1.1) + Traceback (most recent call last): + ... + TypeError: Input value must be a 'int' type + + >>> reverse_bit("0") + Traceback (most recent call last): + ... + TypeError: '<' not supported between instances of 'str' and 'int' + """ + if number < 0: + raise ValueError("the value of input must be positive") + elif isinstance(number, float): + raise TypeError("Input value must be a 'int' type") + elif isinstance(number, str): + raise TypeError("'<' not supported between instances of 'str' and 'int'") + result = 0 + # iterator over [1 to 32],since we are dealing with 32 bit integer + for _ in range(1, 33): + # left shift the bits by unity + result = result << 1 + # get the end bit + end_bit = number % 2 + # right shift the bits by unity + number = number >> 1 + # add that bit to our ans + result = result | end_bit + return get_reverse_bit_string(result) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/boolean_algebra/quine_mc_cluskey.py b/boolean_algebra/quine_mc_cluskey.py index 19bac336f6c5..70cdf25a701d 100644 --- a/boolean_algebra/quine_mc_cluskey.py +++ b/boolean_algebra/quine_mc_cluskey.py @@ -1,10 +1,13 @@ +from typing import List + + def compare_string(string1: str, string2: str) -> str: """ >>> compare_string('0010','0110') '0_10' >>> compare_string('0110','1101') - -1 + 'X' """ l1 = list(string1) l2 = list(string2) @@ -14,12 +17,12 @@ def compare_string(string1: str, string2: str) -> str: count += 1 l1[i] = "_" if count > 1: - return -1 + return "X" else: return "".join(l1) -def check(binary: [str]) -> [str]: +def check(binary: List[str]) -> List[str]: """ >>> check(['0.00.01.5']) ['0.00.01.5'] @@ -31,7 +34,7 @@ def check(binary: [str]) -> [str]: for i in range(len(binary)): for j in range(i + 1, len(binary)): k = compare_string(binary[i], binary[j]) - if k != -1: + if k != "X": check1[i] = "*" check1[j] = "*" temp.append(k) @@ -43,7 +46,7 @@ def check(binary: [str]) -> [str]: binary = list(set(temp)) -def decimal_to_binary(no_of_variable: int, minterms: [float]) -> [str]: +def decimal_to_binary(no_of_variable: int, minterms: List[float]) -> List[str]: """ >>> decimal_to_binary(3,[1.5]) ['0.00.01.5'] @@ -79,7 +82,7 @@ def is_for_table(string1: str, string2: str, count: int) -> bool: return False -def selection(chart: [[int]], prime_implicants: [str]) -> [str]: +def selection(chart: List[List[int]], prime_implicants: List[str]) -> List[str]: """ >>> selection([[1]],['0.00.01.5']) ['0.00.01.5'] @@ -126,7 +129,9 @@ def selection(chart: [[int]], prime_implicants: [str]) -> [str]: chart[j][i] = 0 -def prime_implicant_chart(prime_implicants: [str], binary: [str]) -> [[int]]: +def prime_implicant_chart( + prime_implicants: List[str], binary: List[str] +) -> List[List[int]]: """ >>> prime_implicant_chart(['0.00.01.5'],['0.00.01.5']) [[1]] diff --git a/other/game_of_life.py b/cellular_automata/game_of_life.py similarity index 100% rename from other/game_of_life.py rename to cellular_automata/game_of_life.py diff --git a/ciphers/a1z26.py b/ciphers/a1z26.py index 92710ec44b0e..e6684fb1e6fc 100644 --- a/ciphers/a1z26.py +++ b/ciphers/a1z26.py @@ -7,7 +7,7 @@ """ -def encode(plain: str) -> list: +def encode(plain: str) -> list[int]: """ >>> encode("myname") [13, 25, 14, 1, 13, 5] @@ -15,7 +15,7 @@ def encode(plain: str) -> list: return [ord(elem) - 96 for elem in plain] -def decode(encoded: list) -> str: +def decode(encoded: list[int]) -> str: """ >>> decode([13, 25, 14, 1, 13, 5]) 'myname' @@ -23,8 +23,8 @@ def decode(encoded: list) -> str: return "".join(chr(elem + 96) for elem in encoded) -def main(): - encoded = encode(input("->").strip().lower()) +def main() -> None: + encoded = encode(input("-> ").strip().lower()) print("Encoded: ", encoded) print("Decoded:", decode(encoded)) diff --git a/ciphers/affine_cipher.py b/ciphers/affine_cipher.py index cf8c0d5f4c1d..d3b806ba1eeb 100644 --- a/ciphers/affine_cipher.py +++ b/ciphers/affine_cipher.py @@ -9,26 +9,6 @@ ) -def main(): - """ - >>> key = get_random_key() - >>> msg = "This is a test!" - >>> decrypt_message(key, encrypt_message(key, msg)) == msg - True - """ - message = input("Enter message: ").strip() - key = int(input("Enter key [2000 - 9000]: ").strip()) - mode = input("Encrypt/Decrypt [E/D]: ").strip().lower() - - if mode.startswith("e"): - mode = "encrypt" - translated = encrypt_message(key, message) - elif mode.startswith("d"): - mode = "decrypt" - translated = decrypt_message(key, message) - print(f"\n{mode.title()}ed text: \n{translated}") - - def check_keys(keyA: int, keyB: int, mode: str) -> None: if mode == "encrypt": if keyA == 1: @@ -80,7 +60,7 @@ def decrypt_message(key: int, message: str) -> str: keyA, keyB = divmod(key, len(SYMBOLS)) check_keys(keyA, keyB, "decrypt") plainText = "" - modInverseOfkeyA = cryptomath.findModInverse(keyA, len(SYMBOLS)) + modInverseOfkeyA = cryptomath.find_mod_inverse(keyA, len(SYMBOLS)) for symbol in message: if symbol in SYMBOLS: symIndex = SYMBOLS.find(symbol) @@ -98,6 +78,26 @@ def get_random_key() -> int: return keyA * len(SYMBOLS) + keyB +def main() -> None: + """ + >>> key = get_random_key() + >>> msg = "This is a test!" + >>> decrypt_message(key, encrypt_message(key, msg)) == msg + True + """ + message = input("Enter message: ").strip() + key = int(input("Enter key [2000 - 9000]: ").strip()) + mode = input("Encrypt/Decrypt [E/D]: ").strip().lower() + + if mode.startswith("e"): + mode = "encrypt" + translated = encrypt_message(key, message) + elif mode.startswith("d"): + mode = "decrypt" + translated = decrypt_message(key, message) + print(f"\n{mode.title()}ed text: \n{translated}") + + if __name__ == "__main__": import doctest diff --git a/ciphers/atbash.py b/ciphers/atbash.py index c17d1e34f37a..5c2aea610bff 100644 --- a/ciphers/atbash.py +++ b/ciphers/atbash.py @@ -61,6 +61,6 @@ def benchmark() -> None: if __name__ == "__main__": - for sequence in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): - print(f"{sequence} encrypted in atbash: {atbash(sequence)}") + for example in ("ABCDEFGH", "123GGjj", "testStringtest", "with space"): + print(f"{example} encrypted in atbash: {atbash(example)}") benchmark() diff --git a/ciphers/base32.py b/ciphers/base32.py index 5bba8c4dd685..da289a7210e8 100644 --- a/ciphers/base32.py +++ b/ciphers/base32.py @@ -1,7 +1,7 @@ import base64 -def main(): +def main() -> None: inp = input("->") encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) b32encoded = base64.b32encode(encoded) # b32encoded the encoded string diff --git a/ciphers/base85.py b/ciphers/base85.py index ebfd0480f794..9740299b9771 100644 --- a/ciphers/base85.py +++ b/ciphers/base85.py @@ -1,7 +1,7 @@ import base64 -def main(): +def main() -> None: inp = input("->") encoded = inp.encode("utf-8") # encoded the input (we need a bytes like object) a85encoded = base64.a85encode(encoded) # a85encoded the encoded string diff --git a/ciphers/beaufort_cipher.py b/ciphers/beaufort_cipher.py index c885dec74001..8eae847a7ff7 100644 --- a/ciphers/beaufort_cipher.py +++ b/ciphers/beaufort_cipher.py @@ -66,7 +66,7 @@ def original_text(cipher_text: str, key_new: str) -> str: return or_txt -def main(): +def main() -> None: message = "THE GERMAN ATTACK" key = "SECRET" key_new = generate_key(message, key) diff --git a/ciphers/brute_force_caesar_cipher.py b/ciphers/brute_force_caesar_cipher.py index 13a165245403..8ab6e77307b4 100644 --- a/ciphers/brute_force_caesar_cipher.py +++ b/ciphers/brute_force_caesar_cipher.py @@ -43,7 +43,7 @@ def decrypt(message: str) -> None: print(f"Decryption using Key #{key}: {translated}") -def main(): +def main() -> None: message = input("Encrypted message: ") message = message.upper() decrypt(message) diff --git a/ciphers/cryptomath_module.py b/ciphers/cryptomath_module.py index ffeac1617f64..be8764ff38c3 100644 --- a/ciphers/cryptomath_module.py +++ b/ciphers/cryptomath_module.py @@ -4,9 +4,9 @@ def gcd(a: int, b: int) -> int: return b -def findModInverse(a: int, m: int) -> int: +def find_mod_inverse(a: int, m: int) -> int: if gcd(a, m) != 1: - return None + raise ValueError(f"mod inverse of {a!r} and {m!r} does not exist") u1, u2, u3 = 1, 0, a v1, v2, v3 = 0, 1, m while v3 != 0: diff --git a/ciphers/decrypt_caesar_with_chi_squared.py b/ciphers/decrypt_caesar_with_chi_squared.py index 41b4a12ba453..e7faeae73773 100644 --- a/ciphers/decrypt_caesar_with_chi_squared.py +++ b/ciphers/decrypt_caesar_with_chi_squared.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -from typing import Tuple +from typing import Optional def decrypt_caesar_with_chi_squared( ciphertext: str, - cipher_alphabet: str = None, - frequencies_dict: str = None, + cipher_alphabet: Optional[list[str]] = None, + frequencies_dict: Optional[dict[str, float]] = None, case_sensetive: bool = False, -) -> Tuple[int, float, str]: +) -> tuple[int, float, str]: """ Basic Usage =========== @@ -123,9 +123,9 @@ def decrypt_caesar_with_chi_squared( AttributeError: 'int' object has no attribute 'lower' """ alphabet_letters = cipher_alphabet or [chr(i) for i in range(97, 123)] - frequencies_dict = frequencies_dict or {} - if frequencies_dict == {}: + # If the argument is None or the user provided an empty dictionary + if not frequencies_dict: # Frequencies of letters in the english language (how much they show up) frequencies = { "a": 0.08497, @@ -163,7 +163,7 @@ def decrypt_caesar_with_chi_squared( ciphertext = ciphertext.lower() # Chi squared statistic values - chi_squared_statistic_values = {} + chi_squared_statistic_values: dict[int, tuple[float, str]] = {} # cycle through all of the shifts for shift in range(len(alphabet_letters)): @@ -215,22 +215,22 @@ def decrypt_caesar_with_chi_squared( chi_squared_statistic += chi_letter_value # Add the data to the chi_squared_statistic_values dictionary - chi_squared_statistic_values[shift] = [ + chi_squared_statistic_values[shift] = ( chi_squared_statistic, decrypted_with_shift, - ] + ) # Get the most likely cipher by finding the cipher with the smallest chi squared # statistic - most_likely_cipher = min( + most_likely_cipher: int = min( chi_squared_statistic_values, key=chi_squared_statistic_values.get - ) + ) # type: ignore # First argument to `min` is not optional # Get all the data from the most likely cipher (key, decoded message) - most_likely_cipher_chi_squared_value = chi_squared_statistic_values[ - most_likely_cipher - ][0] - decoded_most_likely_cipher = chi_squared_statistic_values[most_likely_cipher][1] + ( + most_likely_cipher_chi_squared_value, + decoded_most_likely_cipher, + ) = chi_squared_statistic_values[most_likely_cipher] # Return the data on the most likely shift return ( diff --git a/ciphers/diffie.py b/ciphers/diffie.py index 44b12bf9d103..a23a8104afe2 100644 --- a/ciphers/diffie.py +++ b/ciphers/diffie.py @@ -1,4 +1,7 @@ -def find_primitive(n: int) -> int: +from typing import Optional + + +def find_primitive(n: int) -> Optional[int]: for r in range(1, n): li = [] for x in range(n - 1): @@ -8,18 +11,22 @@ def find_primitive(n: int) -> int: li.append(val) else: return r + return None if __name__ == "__main__": q = int(input("Enter a prime number q: ")) a = find_primitive(q) - a_private = int(input("Enter private key of A: ")) - a_public = pow(a, a_private, q) - b_private = int(input("Enter private key of B: ")) - b_public = pow(a, b_private, q) + if a is None: + print(f"Cannot find the primitive for the value: {a!r}") + else: + a_private = int(input("Enter private key of A: ")) + a_public = pow(a, a_private, q) + b_private = int(input("Enter private key of B: ")) + b_public = pow(a, b_private, q) - a_secret = pow(b_public, a_private, q) - b_secret = pow(a_public, b_private, q) + a_secret = pow(b_public, a_private, q) + b_secret = pow(a_public, b_private, q) - print("The key value generated by A is: ", a_secret) - print("The key value generated by B is: ", b_secret) + print("The key value generated by A is: ", a_secret) + print("The key value generated by B is: ", b_secret) diff --git a/ciphers/diffie_hellman.py b/ciphers/diffie_hellman.py new file mode 100644 index 000000000000..ea35b67b483e --- /dev/null +++ b/ciphers/diffie_hellman.py @@ -0,0 +1,271 @@ +from binascii import hexlify +from hashlib import sha256 +from os import urandom + +# RFC 3526 - More Modular Exponential (MODP) Diffie-Hellman groups for +# Internet Key Exchange (IKE) https://tools.ietf.org/html/rfc3526 + +primes = { + # 1536-bit + 5: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 2048-bit + 14: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 3072-bit + 15: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 4096-bit + 16: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + + "FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 6144-bit + 17: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08" + + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B" + + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9" + + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6" + + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8" + + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C" + + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718" + + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D" + + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D" + + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226" + + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26" + + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB" + + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2" + + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127" + + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406" + + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918" + + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151" + + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03" + + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F" + + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B" + + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632" + + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + + "6DCC4024FFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, + # 8192-bit + 18: { + "prime": int( + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64" + + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B" + + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31" + + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA" + + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED" + + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492" + + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831" + + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF" + + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3" + + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328" + + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE" + + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300" + + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9" + + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A" + + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1" + + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47" + + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF", + base=16, + ), + "generator": 2, + }, +} + + +class DiffieHellman: + """ + Class to represent the Diffie-Hellman key exchange protocol + + + >>> alice = DiffieHellman() + >>> bob = DiffieHellman() + + >>> alice_private = alice.get_private_key() + >>> alice_public = alice.generate_public_key() + + >>> bob_private = bob.get_private_key() + >>> bob_public = bob.generate_public_key() + + >>> # generating shared key using the DH object + >>> alice_shared = alice.generate_shared_key(bob_public) + >>> bob_shared = bob.generate_shared_key(alice_public) + + >>> assert alice_shared == bob_shared + + >>> # generating shared key using static methods + >>> alice_shared = DiffieHellman.generate_shared_key_static( + ... alice_private, bob_public + ... ) + >>> bob_shared = DiffieHellman.generate_shared_key_static( + ... bob_private, alice_public + ... ) + + >>> assert alice_shared == bob_shared + """ + + # Current minimum recommendation is 2048 bit (group 14) + def __init__(self, group: int = 14) -> None: + if group not in primes: + raise ValueError("Unsupported Group") + self.prime = primes[group]["prime"] + self.generator = primes[group]["generator"] + + self.__private_key = int(hexlify(urandom(32)), base=16) + + def get_private_key(self) -> str: + return hex(self.__private_key)[2:] + + def generate_public_key(self) -> str: + public_key = pow(self.generator, self.__private_key, self.prime) + return hex(public_key)[2:] + + def is_valid_public_key(self, key: int) -> bool: + # check if the other public key is valid based on NIST SP800-56 + if 2 <= key and key <= self.prime - 2: + if pow(key, (self.prime - 1) // 2, self.prime) == 1: + return True + return False + + def generate_shared_key(self, other_key_str: str) -> str: + other_key = int(other_key_str, base=16) + if not self.is_valid_public_key(other_key): + raise ValueError("Invalid public key") + shared_key = pow(other_key, self.__private_key, self.prime) + return sha256(str(shared_key).encode()).hexdigest() + + @staticmethod + def is_valid_public_key_static( + local_private_key_str: str, remote_public_key_str: str, prime: int + ) -> bool: + # check if the other public key is valid based on NIST SP800-56 + if 2 <= remote_public_key_str and remote_public_key_str <= prime - 2: + if pow(remote_public_key_str, (prime - 1) // 2, prime) == 1: + return True + return False + + @staticmethod + def generate_shared_key_static( + local_private_key_str: str, remote_public_key_str: str, group: int = 14 + ) -> str: + local_private_key = int(local_private_key_str, base=16) + remote_public_key = int(remote_public_key_str, base=16) + prime = primes[group]["prime"] + if not DiffieHellman.is_valid_public_key_static( + local_private_key, remote_public_key, prime + ): + raise ValueError("Invalid public key") + shared_key = pow(remote_public_key, local_private_key, prime) + return sha256(str(shared_key).encode()).hexdigest() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/ciphers/elgamal_key_generator.py b/ciphers/elgamal_key_generator.py index 52cf69074187..f557b0e0dc91 100644 --- a/ciphers/elgamal_key_generator.py +++ b/ciphers/elgamal_key_generator.py @@ -2,24 +2,18 @@ import random import sys -from . import cryptomath_module as cryptoMath -from . import rabin_miller as rabinMiller +from . import cryptomath_module as cryptomath +from . import rabin_miller min_primitive_root = 3 -def main(): - print("Making key files...") - makeKeyFiles("elgamal", 2048) - print("Key files generation successful") - - # I have written my code naively same as definition of primitive root # however every time I run this program, memory exceeded... # so I used 4.80 Algorithm in # Handbook of Applied Cryptography(CRC Press, ISBN : 0-8493-8523-7, October 1996) # and it seems to run nicely! -def primitiveRoot(p_val: int) -> int: +def primitive_root(p_val: int) -> int: print("Generating primitive root of p") while True: g = random.randrange(3, p_val) @@ -30,20 +24,20 @@ def primitiveRoot(p_val: int) -> int: return g -def generateKey(keySize: int) -> ((int, int, int, int), (int, int)): +def generate_key(key_size: int) -> tuple[tuple[int, int, int, int], tuple[int, int]]: print("Generating prime p...") - p = rabinMiller.generateLargePrime(keySize) # select large prime number. - e_1 = primitiveRoot(p) # one primitive root on modulo p. + p = rabin_miller.generateLargePrime(key_size) # select large prime number. + e_1 = primitive_root(p) # one primitive root on modulo p. d = random.randrange(3, p) # private_key -> have to be greater than 2 for safety. - e_2 = cryptoMath.findModInverse(pow(e_1, d, p), p) + e_2 = cryptomath.find_mod_inverse(pow(e_1, d, p), p) - publicKey = (keySize, e_1, e_2, p) - privateKey = (keySize, d) + public_key = (key_size, e_1, e_2, p) + private_key = (key_size, d) - return publicKey, privateKey + return public_key, private_key -def makeKeyFiles(name: str, keySize: int): +def make_key_files(name: str, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % name) or os.path.exists( "%s_privkey.txt" % name ): @@ -55,7 +49,7 @@ def makeKeyFiles(name: str, keySize: int): ) sys.exit() - publicKey, privateKey = generateKey(keySize) + publicKey, privateKey = generate_key(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as fo: fo.write( @@ -67,5 +61,11 @@ def makeKeyFiles(name: str, keySize: int): fo.write("%d,%d" % (privateKey[0], privateKey[1])) +def main() -> None: + print("Making key files...") + make_key_files("elgamal", 2048) + print("Key files generation successful") + + if __name__ == "__main__": main() diff --git a/ciphers/enigma_machine2.py b/ciphers/enigma_machine2.py index 4344db0056fd..f4ce5a075f46 100644 --- a/ciphers/enigma_machine2.py +++ b/ciphers/enigma_machine2.py @@ -15,6 +15,10 @@ Created by TrapinchO """ +RotorPositionT = tuple[int, int, int] +RotorSelectionT = tuple[str, str, str] + + # used alphabet -------------------------- # from string.ascii_uppercase abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" @@ -63,7 +67,9 @@ rotor9 = "KOAEGVDHXPQZMLFTYWJNBRCIUS" -def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: +def _validator( + rotpos: RotorPositionT, rotsel: RotorSelectionT, pb: str +) -> tuple[RotorPositionT, RotorSelectionT, dict[str, str]]: """ Checks if the values can be used for the 'enigma' function @@ -99,12 +105,12 @@ def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: ) # Validates string and returns dict - pb = _plugboard(pb) + pbdict = _plugboard(pb) - return rotpos, rotsel, pb + return rotpos, rotsel, pbdict -def _plugboard(pbstring: str) -> dict: +def _plugboard(pbstring: str) -> dict[str, str]: """ https://en.wikipedia.org/wiki/Enigma_machine#Plugboard @@ -145,17 +151,17 @@ def _plugboard(pbstring: str) -> dict: # Created the dictionary pb = {} - for i in range(0, len(pbstring) - 1, 2): - pb[pbstring[i]] = pbstring[i + 1] - pb[pbstring[i + 1]] = pbstring[i] + for j in range(0, len(pbstring) - 1, 2): + pb[pbstring[j]] = pbstring[j + 1] + pb[pbstring[j + 1]] = pbstring[j] return pb def enigma( text: str, - rotor_position: tuple, - rotor_selection: tuple = (rotor1, rotor2, rotor3), + rotor_position: RotorPositionT, + rotor_selection: RotorSelectionT = (rotor1, rotor2, rotor3), plugb: str = "", ) -> str: """ diff --git a/ciphers/rsa_cipher.py b/ciphers/rsa_cipher.py index 57c916a44d4b..0df37d6ea3ff 100644 --- a/ciphers/rsa_cipher.py +++ b/ciphers/rsa_cipher.py @@ -118,7 +118,7 @@ def encryptAndWriteToFile( for i in range(len(encryptedBlocks)): encryptedBlocks[i] = str(encryptedBlocks[i]) encryptedContent = ",".join(encryptedBlocks) - encryptedContent = "{}_{}_{}".format(len(message), blockSize, encryptedContent) + encryptedContent = f"{len(message)}_{blockSize}_{encryptedContent}" with open(messageFilename, "w") as fo: fo.write(encryptedContent) return encryptedContent diff --git a/ciphers/rsa_key_generator.py b/ciphers/rsa_key_generator.py index 5693aa637ee9..584066d8970f 100644 --- a/ciphers/rsa_key_generator.py +++ b/ciphers/rsa_key_generator.py @@ -1,19 +1,18 @@ import os import random import sys -from typing import Tuple from . import cryptomath_module as cryptoMath from . import rabin_miller as rabinMiller -def main(): +def main() -> None: print("Making key files...") makeKeyFiles("rsa", 1024) print("Key files generation successful.") -def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: +def generateKey(keySize: int) -> tuple[tuple[int, int], tuple[int, int]]: print("Generating prime p...") p = rabinMiller.generateLargePrime(keySize) print("Generating prime q...") @@ -27,14 +26,14 @@ def generateKey(keySize: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: break print("Calculating d that is mod inverse of e...") - d = cryptoMath.findModInverse(e, (p - 1) * (q - 1)) + d = cryptoMath.find_mod_inverse(e, (p - 1) * (q - 1)) publicKey = (n, e) privateKey = (n, d) return (publicKey, privateKey) -def makeKeyFiles(name: int, keySize: int) -> None: +def makeKeyFiles(name: str, keySize: int) -> None: if os.path.exists("%s_pubkey.txt" % (name)) or os.path.exists( "%s_privkey.txt" % (name) ): @@ -49,11 +48,11 @@ def makeKeyFiles(name: int, keySize: int) -> None: publicKey, privateKey = generateKey(keySize) print("\nWriting public key to file %s_pubkey.txt..." % name) with open("%s_pubkey.txt" % name, "w") as out_file: - out_file.write("{},{},{}".format(keySize, publicKey[0], publicKey[1])) + out_file.write(f"{keySize},{publicKey[0]},{publicKey[1]}") print("Writing private key to file %s_privkey.txt..." % name) with open("%s_privkey.txt" % name, "w") as out_file: - out_file.write("{},{},{}".format(keySize, privateKey[0], privateKey[1])) + out_file.write(f"{keySize},{privateKey[0]},{privateKey[1]}") if __name__ == "__main__": diff --git a/compression/burrows_wheeler.py b/compression/burrows_wheeler.py index 1a6610915e65..7d705af7428e 100644 --- a/compression/burrows_wheeler.py +++ b/compression/burrows_wheeler.py @@ -157,11 +157,12 @@ def reverse_bwt(bwt_string: str, idx_original_string: int) -> str: entry_msg = "Provide a string that I will generate its BWT transform: " s = input(entry_msg).strip() result = bwt_transform(s) - bwt_output_msg = "Burrows Wheeler transform for string '{}' results in '{}'" - print(bwt_output_msg.format(s, result["bwt_string"])) + print( + f"Burrows Wheeler transform for string '{s}' results " + f"in '{result['bwt_string']}'" + ) original_string = reverse_bwt(result["bwt_string"], result["idx_original_string"]) - fmt = ( - "Reversing Burrows Wheeler transform for entry '{}' we get original" - " string '{}'" + print( + f"Reversing Burrows Wheeler transform for entry '{result['bwt_string']}' " + f"we get original string '{original_string}'" ) - print(fmt.format(result["bwt_string"], original_string)) diff --git a/compression/lempel_ziv.py b/compression/lempel_ziv.py index 2d0601b27b34..6743dc42d56e 100644 --- a/compression/lempel_ziv.py +++ b/compression/lempel_ziv.py @@ -26,7 +26,7 @@ def read_file_binary(file_path: str) -> str: def add_key_to_lexicon( - lexicon: dict, curr_string: str, index: int, last_match_id: int + lexicon: dict, curr_string: str, index: int, last_match_id: str ) -> None: """ Adds new strings (curr_string + "0", curr_string + "1") to the lexicon diff --git a/data_structures/binary_tree/avl_tree.py b/data_structures/binary_tree/avl_tree.py index 3362610b9303..e0d3e4d438a8 100644 --- a/data_structures/binary_tree/avl_tree.py +++ b/data_structures/binary_tree/avl_tree.py @@ -8,84 +8,85 @@ import math import random +from typing import Any, List, Optional class my_queue: - def __init__(self): - self.data = [] - self.head = 0 - self.tail = 0 + def __init__(self) -> None: + self.data: List[Any] = [] + self.head: int = 0 + self.tail: int = 0 - def is_empty(self): + def is_empty(self) -> bool: return self.head == self.tail - def push(self, data): + def push(self, data: Any) -> None: self.data.append(data) self.tail = self.tail + 1 - def pop(self): + def pop(self) -> Any: ret = self.data[self.head] self.head = self.head + 1 return ret - def count(self): + def count(self) -> int: return self.tail - self.head - def print(self): + def print(self) -> None: print(self.data) print("**************") print(self.data[self.head : self.tail]) class my_node: - def __init__(self, data): + def __init__(self, data: Any) -> None: self.data = data - self.left = None - self.right = None - self.height = 1 + self.left: Optional[my_node] = None + self.right: Optional[my_node] = None + self.height: int = 1 - def get_data(self): + def get_data(self) -> Any: return self.data - def get_left(self): + def get_left(self) -> Optional["my_node"]: return self.left - def get_right(self): + def get_right(self) -> Optional["my_node"]: return self.right - def get_height(self): + def get_height(self) -> int: return self.height - def set_data(self, data): + def set_data(self, data: Any) -> None: self.data = data return - def set_left(self, node): + def set_left(self, node: Optional["my_node"]) -> None: self.left = node return - def set_right(self, node): + def set_right(self, node: Optional["my_node"]) -> None: self.right = node return - def set_height(self, height): + def set_height(self, height: int) -> None: self.height = height return -def get_height(node): +def get_height(node: Optional["my_node"]) -> int: if node is None: return 0 return node.get_height() -def my_max(a, b): +def my_max(a: int, b: int) -> int: if a > b: return a return b -def right_rotation(node): +def right_rotation(node: my_node) -> my_node: r""" A B / \ / \ @@ -98,6 +99,7 @@ def right_rotation(node): """ print("left rotation node:", node.get_data()) ret = node.get_left() + assert ret is not None node.set_left(ret.get_right()) ret.set_right(node) h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 @@ -107,12 +109,13 @@ def right_rotation(node): return ret -def left_rotation(node): +def left_rotation(node: my_node) -> my_node: """ a mirror symmetry rotation of the left_rotation """ print("right rotation node:", node.get_data()) ret = node.get_right() + assert ret is not None node.set_right(ret.get_left()) ret.set_left(node) h1 = my_max(get_height(node.get_right()), get_height(node.get_left())) + 1 @@ -122,7 +125,7 @@ def left_rotation(node): return ret -def lr_rotation(node): +def lr_rotation(node: my_node) -> my_node: r""" A A Br / \ / \ / \ @@ -133,16 +136,20 @@ def lr_rotation(node): UB Bl RR = right_rotation LR = left_rotation """ - node.set_left(left_rotation(node.get_left())) + left_child = node.get_left() + assert left_child is not None + node.set_left(left_rotation(left_child)) return right_rotation(node) -def rl_rotation(node): - node.set_right(right_rotation(node.get_right())) +def rl_rotation(node: my_node) -> my_node: + right_child = node.get_right() + assert right_child is not None + node.set_right(right_rotation(right_child)) return left_rotation(node) -def insert_node(node, data): +def insert_node(node: Optional["my_node"], data: Any) -> Optional["my_node"]: if node is None: return my_node(data) if data < node.get_data(): @@ -150,8 +157,10 @@ def insert_node(node, data): if ( get_height(node.get_left()) - get_height(node.get_right()) == 2 ): # an unbalance detected + left_child = node.get_left() + assert left_child is not None if ( - data < node.get_left().get_data() + data < left_child.get_data() ): # new node is the left child of the left child node = right_rotation(node) else: @@ -159,7 +168,9 @@ def insert_node(node, data): else: node.set_right(insert_node(node.get_right(), data)) if get_height(node.get_right()) - get_height(node.get_left()) == 2: - if data < node.get_right().get_data(): + right_child = node.get_right() + assert right_child is not None + if data < right_child.get_data(): node = rl_rotation(node) else: node = left_rotation(node) @@ -168,52 +179,59 @@ def insert_node(node, data): return node -def get_rightMost(root): - while root.get_right() is not None: - root = root.get_right() +def get_rightMost(root: my_node) -> Any: + while True: + right_child = root.get_right() + if right_child is None: + break + root = right_child return root.get_data() -def get_leftMost(root): - while root.get_left() is not None: - root = root.get_left() +def get_leftMost(root: my_node) -> Any: + while True: + left_child = root.get_left() + if left_child is None: + break + root = left_child return root.get_data() -def del_node(root, data): +def del_node(root: my_node, data: Any) -> Optional["my_node"]: + left_child = root.get_left() + right_child = root.get_right() if root.get_data() == data: - if root.get_left() is not None and root.get_right() is not None: - temp_data = get_leftMost(root.get_right()) + if left_child is not None and right_child is not None: + temp_data = get_leftMost(right_child) root.set_data(temp_data) - root.set_right(del_node(root.get_right(), temp_data)) - elif root.get_left() is not None: - root = root.get_left() + root.set_right(del_node(right_child, temp_data)) + elif left_child is not None: + root = left_child + elif right_child is not None: + root = right_child else: - root = root.get_right() + return None elif root.get_data() > data: - if root.get_left() is None: + if left_child is None: print("No such data") return root else: - root.set_left(del_node(root.get_left(), data)) - elif root.get_data() < data: - if root.get_right() is None: + root.set_left(del_node(left_child, data)) + else: # root.get_data() < data + if right_child is None: return root else: - root.set_right(del_node(root.get_right(), data)) - if root is None: - return root - if get_height(root.get_right()) - get_height(root.get_left()) == 2: - if get_height(root.get_right().get_right()) > get_height( - root.get_right().get_left() - ): + root.set_right(del_node(right_child, data)) + + if get_height(right_child) - get_height(left_child) == 2: + assert right_child is not None + if get_height(right_child.get_right()) > get_height(right_child.get_left()): root = left_rotation(root) else: root = rl_rotation(root) - elif get_height(root.get_right()) - get_height(root.get_left()) == -2: - if get_height(root.get_left().get_left()) > get_height( - root.get_left().get_right() - ): + elif get_height(right_child) - get_height(left_child) == -2: + assert left_child is not None + if get_height(left_child.get_left()) > get_height(left_child.get_right()): root = right_rotation(root) else: root = lr_rotation(root) @@ -256,25 +274,26 @@ class AVLtree: ************************************* """ - def __init__(self): - self.root = None + def __init__(self) -> None: + self.root: Optional[my_node] = None - def get_height(self): - # print("yyy") + def get_height(self) -> int: return get_height(self.root) - def insert(self, data): + def insert(self, data: Any) -> None: print("insert:" + str(data)) self.root = insert_node(self.root, data) - def del_node(self, data): + def del_node(self, data: Any) -> None: print("delete:" + str(data)) if self.root is None: print("Tree is empty!") return self.root = del_node(self.root, data) - def __str__(self): # a level traversale, gives a more intuitive look on the tree + def __str__( + self, + ) -> str: # a level traversale, gives a more intuitive look on the tree output = "" q = my_queue() q.push(self.root) @@ -308,7 +327,7 @@ def __str__(self): # a level traversale, gives a more intuitive look on the tre return output -def _test(): +def _test() -> None: import doctest doctest.testmod() diff --git a/data_structures/binary_tree/binary_search_tree_recursive.py b/data_structures/binary_tree/binary_search_tree_recursive.py index f1e46e33cd24..a05e28a7bd54 100644 --- a/data_structures/binary_tree/binary_search_tree_recursive.py +++ b/data_structures/binary_tree/binary_search_tree_recursive.py @@ -8,21 +8,22 @@ python binary_search_tree_recursive.py """ import unittest +from typing import Iterator, Optional class Node: - def __init__(self, label: int, parent): + def __init__(self, label: int, parent: Optional["Node"]) -> None: self.label = label self.parent = parent - self.left = None - self.right = None + self.left: Optional[Node] = None + self.right: Optional[Node] = None class BinarySearchTree: - def __init__(self): - self.root = None + def __init__(self) -> None: + self.root: Optional[Node] = None - def empty(self): + def empty(self) -> None: """ Empties the tree @@ -46,7 +47,7 @@ def is_empty(self) -> bool: """ return self.root is None - def put(self, label: int): + def put(self, label: int) -> None: """ Put a new node in the tree @@ -65,7 +66,9 @@ def put(self, label: int): """ self.root = self._put(self.root, label) - def _put(self, node: Node, label: int, parent: Node = None) -> Node: + def _put( + self, node: Optional[Node], label: int, parent: Optional[Node] = None + ) -> Node: if node is None: node = Node(label, parent) else: @@ -95,7 +98,7 @@ def search(self, label: int) -> Node: """ return self._search(self.root, label) - def _search(self, node: Node, label: int) -> Node: + def _search(self, node: Optional[Node], label: int) -> Node: if node is None: raise Exception(f"Node with label {label} does not exist") else: @@ -106,7 +109,7 @@ def _search(self, node: Node, label: int) -> Node: return node - def remove(self, label: int): + def remove(self, label: int) -> None: """ Removes a node in the tree @@ -122,13 +125,7 @@ def remove(self, label: int): Exception: Node with label 3 does not exist """ node = self.search(label) - if not node.right and not node.left: - self._reassign_nodes(node, None) - elif not node.right and node.left: - self._reassign_nodes(node, node.left) - elif node.right and not node.left: - self._reassign_nodes(node, node.right) - else: + if node.right and node.left: lowest_node = self._get_lowest_node(node.right) lowest_node.left = node.left lowest_node.right = node.right @@ -136,8 +133,14 @@ def remove(self, label: int): if node.right: node.right.parent = lowest_node self._reassign_nodes(node, lowest_node) + elif not node.right and node.left: + self._reassign_nodes(node, node.left) + elif node.right and not node.left: + self._reassign_nodes(node, node.right) + else: + self._reassign_nodes(node, None) - def _reassign_nodes(self, node: Node, new_children: Node): + def _reassign_nodes(self, node: Node, new_children: Optional[Node]) -> None: if new_children: new_children.parent = node.parent @@ -192,7 +195,7 @@ def get_max_label(self) -> int: >>> t.get_max_label() 10 """ - if self.is_empty(): + if self.root is None: raise Exception("Binary search tree is empty") node = self.root @@ -216,7 +219,7 @@ def get_min_label(self) -> int: >>> t.get_min_label() 8 """ - if self.is_empty(): + if self.root is None: raise Exception("Binary search tree is empty") node = self.root @@ -225,7 +228,7 @@ def get_min_label(self) -> int: return node.label - def inorder_traversal(self) -> list: + def inorder_traversal(self) -> Iterator[Node]: """ Return the inorder traversal of the tree @@ -241,13 +244,13 @@ def inorder_traversal(self) -> list: """ return self._inorder_traversal(self.root) - def _inorder_traversal(self, node: Node) -> list: + def _inorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: if node is not None: yield from self._inorder_traversal(node.left) yield node yield from self._inorder_traversal(node.right) - def preorder_traversal(self) -> list: + def preorder_traversal(self) -> Iterator[Node]: """ Return the preorder traversal of the tree @@ -263,7 +266,7 @@ def preorder_traversal(self) -> list: """ return self._preorder_traversal(self.root) - def _preorder_traversal(self, node: Node) -> list: + def _preorder_traversal(self, node: Optional[Node]) -> Iterator[Node]: if node is not None: yield node yield from self._preorder_traversal(node.left) @@ -272,7 +275,7 @@ def _preorder_traversal(self, node: Node) -> list: class BinarySearchTreeTest(unittest.TestCase): @staticmethod - def _get_binary_search_tree(): + def _get_binary_search_tree() -> BinarySearchTree: r""" 8 / \ @@ -298,7 +301,7 @@ def _get_binary_search_tree(): return t - def test_put(self): + def test_put(self) -> None: t = BinarySearchTree() assert t.is_empty() @@ -306,6 +309,7 @@ def test_put(self): r""" 8 """ + assert t.root is not None assert t.root.parent is None assert t.root.label == 8 @@ -315,6 +319,7 @@ def test_put(self): \ 10 """ + assert t.root.right is not None assert t.root.right.parent == t.root assert t.root.right.label == 10 @@ -324,6 +329,7 @@ def test_put(self): / \ 3 10 """ + assert t.root.left is not None assert t.root.left.parent == t.root assert t.root.left.label == 3 @@ -335,6 +341,7 @@ def test_put(self): \ 6 """ + assert t.root.left.right is not None assert t.root.left.right.parent == t.root.left assert t.root.left.right.label == 6 @@ -346,13 +353,14 @@ def test_put(self): / \ 1 6 """ + assert t.root.left.left is not None assert t.root.left.left.parent == t.root.left assert t.root.left.left.label == 1 with self.assertRaises(Exception): t.put(1) - def test_search(self): + def test_search(self) -> None: t = self._get_binary_search_tree() node = t.search(6) @@ -364,7 +372,7 @@ def test_search(self): with self.assertRaises(Exception): t.search(2) - def test_remove(self): + def test_remove(self) -> None: t = self._get_binary_search_tree() t.remove(13) @@ -379,6 +387,9 @@ def test_remove(self): \ 5 """ + assert t.root is not None + assert t.root.right is not None + assert t.root.right.right is not None assert t.root.right.right.right is None assert t.root.right.right.left is None @@ -394,6 +405,9 @@ def test_remove(self): \ 5 """ + assert t.root.left is not None + assert t.root.left.right is not None + assert t.root.left.right.left is not None assert t.root.left.right.right is None assert t.root.left.right.left.label == 4 @@ -407,6 +421,8 @@ def test_remove(self): \ 5 """ + assert t.root.left.left is not None + assert t.root.left.right.right is not None assert t.root.left.left.label == 1 assert t.root.left.right.label == 4 assert t.root.left.right.right.label == 5 @@ -422,6 +438,7 @@ def test_remove(self): / \ \ 1 5 14 """ + assert t.root is not None assert t.root.left.label == 4 assert t.root.left.right.label == 5 assert t.root.left.left.label == 1 @@ -437,13 +454,15 @@ def test_remove(self): / \ 1 14 """ + assert t.root.left is not None + assert t.root.left.left is not None assert t.root.left.label == 5 assert t.root.left.right is None assert t.root.left.left.label == 1 assert t.root.left.parent == t.root assert t.root.left.left.parent == t.root.left - def test_remove_2(self): + def test_remove_2(self) -> None: t = self._get_binary_search_tree() t.remove(3) @@ -456,6 +475,12 @@ def test_remove_2(self): / \ / 5 7 13 """ + assert t.root is not None + assert t.root.left is not None + assert t.root.left.left is not None + assert t.root.left.right is not None + assert t.root.left.right.left is not None + assert t.root.left.right.right is not None assert t.root.left.label == 4 assert t.root.left.right.label == 6 assert t.root.left.left.label == 1 @@ -466,25 +491,25 @@ def test_remove_2(self): assert t.root.left.left.parent == t.root.left assert t.root.left.right.left.parent == t.root.left.right - def test_empty(self): + def test_empty(self) -> None: t = self._get_binary_search_tree() t.empty() assert t.root is None - def test_is_empty(self): + def test_is_empty(self) -> None: t = self._get_binary_search_tree() assert not t.is_empty() t.empty() assert t.is_empty() - def test_exists(self): + def test_exists(self) -> None: t = self._get_binary_search_tree() assert t.exists(6) assert not t.exists(-1) - def test_get_max_label(self): + def test_get_max_label(self) -> None: t = self._get_binary_search_tree() assert t.get_max_label() == 14 @@ -493,7 +518,7 @@ def test_get_max_label(self): with self.assertRaises(Exception): t.get_max_label() - def test_get_min_label(self): + def test_get_min_label(self) -> None: t = self._get_binary_search_tree() assert t.get_min_label() == 1 @@ -502,20 +527,20 @@ def test_get_min_label(self): with self.assertRaises(Exception): t.get_min_label() - def test_inorder_traversal(self): + def test_inorder_traversal(self) -> None: t = self._get_binary_search_tree() inorder_traversal_nodes = [i.label for i in t.inorder_traversal()] assert inorder_traversal_nodes == [1, 3, 4, 5, 6, 7, 8, 10, 13, 14] - def test_preorder_traversal(self): + def test_preorder_traversal(self) -> None: t = self._get_binary_search_tree() preorder_traversal_nodes = [i.label for i in t.preorder_traversal()] assert preorder_traversal_nodes == [8, 3, 1, 6, 4, 5, 7, 10, 14, 13] -def binary_search_tree_example(): +def binary_search_tree_example() -> None: r""" Example 8 diff --git a/data_structures/binary_tree/lazy_segment_tree.py b/data_structures/binary_tree/lazy_segment_tree.py index 5bc79e74efcd..9066db294613 100644 --- a/data_structures/binary_tree/lazy_segment_tree.py +++ b/data_structures/binary_tree/lazy_segment_tree.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +from typing import List, Union class SegmentTree: @@ -37,7 +38,7 @@ def right(self, idx: int) -> int: return idx * 2 + 1 def build( - self, idx: int, left_element: int, right_element: int, A: list[int] + self, idx: int, left_element: int, right_element: int, A: List[int] ) -> None: if left_element == right_element: self.segment_tree[idx] = A[left_element - 1] @@ -88,7 +89,7 @@ def update( # query with O(lg n) def query( self, idx: int, left_element: int, right_element: int, a: int, b: int - ) -> int: + ) -> Union[int, float]: """ query(1, 1, size, a, b) for query max of [a,b] >>> A = [1, 2, -4, 7, 3, -5, 6, 11, -20, 9, 14, 15, 5, 2, -8] @@ -118,8 +119,8 @@ def query( q2 = self.query(self.right(idx), mid + 1, right_element, a, b) return max(q1, q2) - def __str__(self) -> None: - return [self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)] + def __str__(self) -> str: + return str([self.query(1, 1, self.size, i, i) for i in range(1, self.size + 1)]) if __name__ == "__main__": diff --git a/data_structures/binary_tree/merge_two_binary_trees.py b/data_structures/binary_tree/merge_two_binary_trees.py new file mode 100644 index 000000000000..6b202adb3cf5 --- /dev/null +++ b/data_structures/binary_tree/merge_two_binary_trees.py @@ -0,0 +1,93 @@ +#!/usr/local/bin/python3 +""" +Problem Description: Given two binary tree, return the merged tree. +The rule for merging is that if two nodes overlap, then put the value sum of +both nodes to the new value of the merged node. Otherwise, the NOT null node +will be used as the node of new tree. +""" +from typing import Optional + + +class Node: + """ + A binary node has value variable and pointers to its left and right node. + """ + + def __init__(self, value: int = 0) -> None: + self.value = value + self.left: Optional[Node] = None + self.right: Optional[Node] = None + + +def merge_two_binary_trees(tree1: Optional[Node], tree2: Optional[Node]) -> Node: + """ + Returns root node of the merged tree. + + >>> tree1 = Node(5) + >>> tree1.left = Node(6) + >>> tree1.right = Node(7) + >>> tree1.left.left = Node(2) + >>> tree2 = Node(4) + >>> tree2.left = Node(5) + >>> tree2.right = Node(8) + >>> tree2.left.right = Node(1) + >>> tree2.right.right = Node(4) + >>> merged_tree = merge_two_binary_trees(tree1, tree2) + >>> print_preorder(merged_tree) + 9 + 11 + 2 + 1 + 15 + 4 + """ + if tree1 is None: + return tree2 + if tree2 is None: + return tree1 + + tree1.value = tree1.value + tree2.value + tree1.left = merge_two_binary_trees(tree1.left, tree2.left) + tree1.right = merge_two_binary_trees(tree1.right, tree2.right) + return tree1 + + +def print_preorder(root: Optional[Node]) -> None: + """ + Print pre-order traversal of the tree. + + >>> root = Node(1) + >>> root.left = Node(2) + >>> root.right = Node(3) + >>> print_preorder(root) + 1 + 2 + 3 + >>> print_preorder(root.right) + 3 + """ + if root: + print(root.value) + print_preorder(root.left) + print_preorder(root.right) + + +if __name__ == "__main__": + tree1 = Node(1) + tree1.left = Node(2) + tree1.right = Node(3) + tree1.left.left = Node(4) + + tree2 = Node(2) + tree2.left = Node(4) + tree2.right = Node(6) + tree2.left.right = Node(9) + tree2.right.right = Node(5) + + print("Tree1 is: ") + print_preorder(tree1) + print("Tree2 is: ") + print_preorder(tree2) + merged_tree = merge_two_binary_trees(tree1, tree2) + print("Merged Tree is: ") + print_preorder(merged_tree) diff --git a/data_structures/binary_tree/non_recursive_segment_tree.py b/data_structures/binary_tree/non_recursive_segment_tree.py index 064e5aded7b4..c914079e0a8d 100644 --- a/data_structures/binary_tree/non_recursive_segment_tree.py +++ b/data_structures/binary_tree/non_recursive_segment_tree.py @@ -49,7 +49,7 @@ def __init__(self, arr: list[T], fnc: Callable[[T, T], T]) -> None: :param arr: list of elements for the segment tree :param fnc: commutative function for combine two elements - >>> SegmentTree(['a', 'b', 'c'], lambda a, b: '{}{}'.format(a, b)).query(0, 2) + >>> SegmentTree(['a', 'b', 'c'], lambda a, b: f'{a}{b}').query(0, 2) 'abc' >>> SegmentTree([(1, 2), (2, 3), (3, 4)], ... lambda a, b: (a[0] + b[0], a[1] + b[1])).query(0, 2) diff --git a/data_structures/binary_tree/red_black_tree.py b/data_structures/binary_tree/red_black_tree.py index 5d721edfa45b..de971a712fc1 100644 --- a/data_structures/binary_tree/red_black_tree.py +++ b/data_structures/binary_tree/red_black_tree.py @@ -475,11 +475,13 @@ def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: - return "'{} {}'".format(self.label, (self.color and "red") or "blk") + return f"'{self.label} {(self.color and 'red') or 'blk'}'" return pformat( { - "%s %s" - % (self.label, (self.color and "red") or "blk"): (self.left, self.right) + f"{self.label} {(self.color and 'red') or 'blk'}": ( + self.left, + self.right, + ) }, indent=1, ) diff --git a/data_structures/binary_tree/treap.py b/data_structures/binary_tree/treap.py index 26648f7aba61..a09dcc928143 100644 --- a/data_structures/binary_tree/treap.py +++ b/data_structures/binary_tree/treap.py @@ -3,6 +3,7 @@ from __future__ import annotations from random import random +from typing import Optional, Tuple class Node: @@ -11,13 +12,13 @@ class Node: Treap is a binary tree by value and heap by priority """ - def __init__(self, value: int = None): + def __init__(self, value: Optional[int] = None): self.value = value self.prior = random() - self.left = None - self.right = None + self.left: Optional[Node] = None + self.right: Optional[Node] = None - def __repr__(self): + def __repr__(self) -> str: from pprint import pformat if self.left is None and self.right is None: @@ -27,14 +28,14 @@ def __repr__(self): {f"{self.value}: {self.prior:.5}": (self.left, self.right)}, indent=1 ) - def __str__(self): + def __str__(self) -> str: value = str(self.value) + " " left = str(self.left or "") right = str(self.right or "") return value + left + right -def split(root: Node, value: int) -> tuple[Node, Node]: +def split(root: Optional[Node], value: int) -> Tuple[Optional[Node], Optional[Node]]: """ We split current tree into 2 trees with value: @@ -42,9 +43,9 @@ def split(root: Node, value: int) -> tuple[Node, Node]: Right tree contains all values greater or equal, than split value """ if root is None: # None tree is split into 2 Nones - return (None, None) + return None, None elif root.value is None: - return (None, None) + return None, None else: if value < root.value: """ @@ -54,16 +55,16 @@ def split(root: Node, value: int) -> tuple[Node, Node]: Right tree's left son: right part of that split """ left, root.left = split(root.left, value) - return (left, root) + return left, root else: """ Just symmetric to previous case """ root.right, right = split(root.right, value) - return (root, right) + return root, right -def merge(left: Node, right: Node) -> Node: +def merge(left: Optional[Node], right: Optional[Node]) -> Optional[Node]: """ We merge 2 trees into one. Note: all left tree's values must be less than all right tree's @@ -85,7 +86,7 @@ def merge(left: Node, right: Node) -> Node: return right -def insert(root: Node, value: int) -> Node: +def insert(root: Optional[Node], value: int) -> Optional[Node]: """ Insert element @@ -98,7 +99,7 @@ def insert(root: Node, value: int) -> Node: return merge(merge(left, node), right) -def erase(root: Node, value: int) -> Node: +def erase(root: Optional[Node], value: int) -> Optional[Node]: """ Erase element @@ -111,7 +112,7 @@ def erase(root: Node, value: int) -> Node: return merge(left, right) -def inorder(root: Node): +def inorder(root: Optional[Node]) -> None: """ Just recursive print of a tree """ @@ -123,7 +124,7 @@ def inorder(root: Node): inorder(root.right) -def interactTreap(root, args): +def interactTreap(root: Optional[Node], args: str) -> Optional[Node]: """ Commands: + value to add value into treap @@ -160,7 +161,7 @@ def interactTreap(root, args): return root -def main(): +def main() -> None: """After each command, program prints treap""" root = None print( diff --git a/data_structures/linked_list/deque_doubly.py b/data_structures/linked_list/deque_doubly.py index 894f91d561cc..c9ae8b3d1ba2 100644 --- a/data_structures/linked_list/deque_doubly.py +++ b/data_structures/linked_list/deque_doubly.py @@ -20,8 +20,8 @@ def __init__(self, link_p, element, link_n): self._next = link_n def has_next_and_prev(self): - return " Prev -> {}, Next -> {}".format( - self._prev is not None, self._next is not None + return ( + f" Prev -> {self._prev is not None}, Next -> {self._next is not None}" ) def __init__(self): diff --git a/divide_and_conquer/max_difference_pair.py b/divide_and_conquer/max_difference_pair.py index b976aca43137..ffc4b76a7154 100644 --- a/divide_and_conquer/max_difference_pair.py +++ b/divide_and_conquer/max_difference_pair.py @@ -1,7 +1,4 @@ -from typing import List - - -def max_difference(a: List[int]) -> (int, int): +def max_difference(a: list[int]) -> tuple[int, int]: """ We are given an array A[1..n] of integers, n >= 1. We want to find a pair of indices (i, j) such that diff --git a/divide_and_conquer/strassen_matrix_multiplication.py b/divide_and_conquer/strassen_matrix_multiplication.py index 29a174daebf9..ca10e04abcbc 100644 --- a/divide_and_conquer/strassen_matrix_multiplication.py +++ b/divide_and_conquer/strassen_matrix_multiplication.py @@ -121,7 +121,7 @@ def strassen(matrix1: list, matrix2: list) -> list: dimension2 = matrix_dimensions(matrix2) if dimension1[0] == dimension1[1] and dimension2[0] == dimension2[1]: - return matrix1, matrix2 + return [matrix1, matrix2] maximum = max(max(dimension1), max(dimension2)) maxim = int(math.pow(2, math.ceil(math.log2(maximum)))) diff --git a/dynamic_programming/climbing_stairs.py b/dynamic_programming/climbing_stairs.py index 79605261f981..048d57aed1be 100644 --- a/dynamic_programming/climbing_stairs.py +++ b/dynamic_programming/climbing_stairs.py @@ -25,8 +25,9 @@ def climb_stairs(n: int) -> int: ... AssertionError: n needs to be positive integer, your input -7 """ - fmt = "n needs to be positive integer, your input {}" - assert isinstance(n, int) and n > 0, fmt.format(n) + assert ( + isinstance(n, int) and n > 0 + ), f"n needs to be positive integer, your input {n}" if n == 1: return 1 dp = [0] * (n + 1) diff --git a/dynamic_programming/iterating_through_submasks.py b/dynamic_programming/iterating_through_submasks.py index 855af61d6707..21c64dba4ecc 100644 --- a/dynamic_programming/iterating_through_submasks.py +++ b/dynamic_programming/iterating_through_submasks.py @@ -37,8 +37,9 @@ def list_of_submasks(mask: int) -> list[int]: """ - fmt = "mask needs to be positive integer, your input {}" - assert isinstance(mask, int) and mask > 0, fmt.format(mask) + assert ( + isinstance(mask, int) and mask > 0 + ), f"mask needs to be positive integer, your input {mask}" """ first submask iterated will be mask itself then operation will be performed diff --git a/dynamic_programming/coin_change.py b/dynamic_programming/minimum_coin_change.py similarity index 100% rename from dynamic_programming/coin_change.py rename to dynamic_programming/minimum_coin_change.py diff --git a/electronics/electric_power.py b/electronics/electric_power.py index 768c3d5c7232..e4e685bbd0f0 100644 --- a/electronics/electric_power.py +++ b/electronics/electric_power.py @@ -1,8 +1,9 @@ # https://en.m.wikipedia.org/wiki/Electric_power from collections import namedtuple +from typing import Tuple -def electric_power(voltage: float, current: float, power: float) -> float: +def electric_power(voltage: float, current: float, power: float) -> Tuple: """ This function can calculate any one of the three (voltage, current, power), fundamental value of electrical system. @@ -41,6 +42,8 @@ def electric_power(voltage: float, current: float, power: float) -> float: return result("current", power / voltage) elif power == 0: return result("power", float(round(abs(voltage * current), 2))) + else: + raise ValueError("Exactly one argument must be 0") if __name__ == "__main__": diff --git a/electronics/ohms_law.py b/electronics/ohms_law.py index a7b37b635397..41bffa9f87c8 100644 --- a/electronics/ohms_law.py +++ b/electronics/ohms_law.py @@ -1,7 +1,8 @@ # https://en.wikipedia.org/wiki/Ohm%27s_law +from typing import Dict -def ohms_law(voltage: float, current: float, resistance: float) -> float: +def ohms_law(voltage: float, current: float, resistance: float) -> Dict[str, float]: """ Apply Ohm's Law, on any two given electrical values, which can be voltage, current, and resistance, and then in a Python dict return name/value pair of the zero value. @@ -31,6 +32,8 @@ def ohms_law(voltage: float, current: float, resistance: float) -> float: return {"current": voltage / resistance} elif resistance == 0: return {"resistance": voltage / current} + else: + raise ValueError("Exactly one argument must be 0") if __name__ == "__main__": diff --git a/file_transfer/receive_file.py b/file_transfer/receive_file.py index cfba6ed88484..37a503036dc2 100644 --- a/file_transfer/receive_file.py +++ b/file_transfer/receive_file.py @@ -13,7 +13,7 @@ print("Receiving data...") while True: data = sock.recv(1024) - print(f"data={data}") + print(f"{data = }") if not data: break out_file.write(data) # Write data to a file diff --git a/file_transfer/send_file.py b/file_transfer/send_file.py index 5b53471dfb50..1c56e48f47a1 100644 --- a/file_transfer/send_file.py +++ b/file_transfer/send_file.py @@ -13,7 +13,7 @@ def send_file(filename: str = "mytext.txt", testing: bool = False) -> None: conn, addr = sock.accept() # Establish connection with client. print(f"Got connection from {addr}") data = conn.recv(1024) - print(f"Server received {data}") + print(f"Server received: {data = }") with open(filename, "rb") as in_file: data = in_file.read(1024) diff --git a/fractals/koch_snowflake.py b/fractals/koch_snowflake.py new file mode 100644 index 000000000000..07c1835b41ed --- /dev/null +++ b/fractals/koch_snowflake.py @@ -0,0 +1,116 @@ +""" +Description + The Koch snowflake is a fractal curve and one of the earliest fractals to + have been described. The Koch snowflake can be built up iteratively, in a + sequence of stages. The first stage is an equilateral triangle, and each + successive stage is formed by adding outward bends to each side of the + previous stage, making smaller equilateral triangles. + This can be achieved through the following steps for each line: + 1. divide the line segment into three segments of equal length. + 2. draw an equilateral triangle that has the middle segment from step 1 + as its base and points outward. + 3. remove the line segment that is the base of the triangle from step 2. + (description adapted from https://en.wikipedia.org/wiki/Koch_snowflake ) + (for a more detailed explanation and an implementation in the + Processing language, see https://natureofcode.com/book/chapter-8-fractals/ + #84-the-koch-curve-and-the-arraylist-technique ) + +Requirements (pip): + - matplotlib + - numpy +""" + + +from __future__ import annotations + +import matplotlib.pyplot as plt # type: ignore +import numpy + +# initial triangle of Koch snowflake +VECTOR_1 = numpy.array([0, 0]) +VECTOR_2 = numpy.array([0.5, 0.8660254]) +VECTOR_3 = numpy.array([1, 0]) +INITIAL_VECTORS = [VECTOR_1, VECTOR_2, VECTOR_3, VECTOR_1] + +# uncomment for simple Koch curve instead of Koch snowflake +# INITIAL_VECTORS = [VECTOR_1, VECTOR_3] + + +def iterate(initial_vectors: list[numpy.ndarray], steps: int) -> list[numpy.ndarray]: + """ + Go through the number of iterations determined by the argument "steps". + Be careful with high values (above 5) since the time to calculate increases + exponentially. + >>> iterate([numpy.array([0, 0]), numpy.array([1, 0])], 1) + [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ +0.28867513]), array([0.66666667, 0. ]), array([1, 0])] + """ + vectors = initial_vectors + for i in range(steps): + vectors = iteration_step(vectors) + return vectors + + +def iteration_step(vectors: list[numpy.ndarray]) -> list[numpy.ndarray]: + """ + Loops through each pair of adjacent vectors. Each line between two adjacent + vectors is divided into 4 segments by adding 3 additional vectors in-between + the original two vectors. The vector in the middle is constructed through a + 60 degree rotation so it is bent outwards. + >>> iteration_step([numpy.array([0, 0]), numpy.array([1, 0])]) + [array([0, 0]), array([0.33333333, 0. ]), array([0.5 , \ +0.28867513]), array([0.66666667, 0. ]), array([1, 0])] + """ + new_vectors = [] + for i, start_vector in enumerate(vectors[:-1]): + end_vector = vectors[i + 1] + new_vectors.append(start_vector) + difference_vector = end_vector - start_vector + new_vectors.append(start_vector + difference_vector / 3) + new_vectors.append( + start_vector + difference_vector / 3 + rotate(difference_vector / 3, 60) + ) + new_vectors.append(start_vector + difference_vector * 2 / 3) + new_vectors.append(vectors[-1]) + return new_vectors + + +def rotate(vector: numpy.ndarray, angle_in_degrees: float) -> numpy.ndarray: + """ + Standard rotation of a 2D vector with a rotation matrix + (see https://en.wikipedia.org/wiki/Rotation_matrix ) + >>> rotate(numpy.array([1, 0]), 60) + array([0.5 , 0.8660254]) + >>> rotate(numpy.array([1, 0]), 90) + array([6.123234e-17, 1.000000e+00]) + """ + theta = numpy.radians(angle_in_degrees) + c, s = numpy.cos(theta), numpy.sin(theta) + rotation_matrix = numpy.array(((c, -s), (s, c))) + return numpy.dot(rotation_matrix, vector) + + +def plot(vectors: list[numpy.ndarray]) -> None: + """ + Utility function to plot the vectors using matplotlib.pyplot + No doctest was implemented since this function does not have a return value + """ + # avoid stretched display of graph + axes = plt.gca() + axes.set_aspect("equal") + + # matplotlib.pyplot.plot takes a list of all x-coordinates and a list of all + # y-coordinates as inputs, which are constructed from the vector-list using + # zip() + x_coordinates, y_coordinates = zip(*vectors) + plt.plot(x_coordinates, y_coordinates) + plt.show() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + processed_vectors = iterate(INITIAL_VECTORS, 5) + plot(processed_vectors) diff --git a/fractals/mandelbrot.py b/fractals/mandelbrot.py new file mode 100644 index 000000000000..de795bb3fc6f --- /dev/null +++ b/fractals/mandelbrot.py @@ -0,0 +1,150 @@ +""" +The Mandelbrot set is the set of complex numbers "c" for which the series +"z_(n+1) = z_n * z_n + c" does not diverge, i.e. remains bounded. Thus, a +complex number "c" is a member of the Mandelbrot set if, when starting with +"z_0 = 0" and applying the iteration repeatedly, the absolute value of +"z_n" remains bounded for all "n > 0". Complex numbers can be written as +"a + b*i": "a" is the real component, usually drawn on the x-axis, and "b*i" +is the imaginary component, usually drawn on the y-axis. Most visualizations +of the Mandelbrot set use a color-coding to indicate after how many steps in +the series the numbers outside the set diverge. Images of the Mandelbrot set +exhibit an elaborate and infinitely complicated boundary that reveals +progressively ever-finer recursive detail at increasing magnifications, making +the boundary of the Mandelbrot set a fractal curve. +(description adapted from https://en.wikipedia.org/wiki/Mandelbrot_set ) +(see also https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set ) +""" + + +import colorsys + +from PIL import Image # type: ignore + + +def get_distance(x: float, y: float, max_step: int) -> float: + """ + Return the relative distance (= step/max_step) after which the complex number + constituted by this x-y-pair diverges. Members of the Mandelbrot set do not + diverge so their distance is 1. + + >>> get_distance(0, 0, 50) + 1.0 + >>> get_distance(0.5, 0.5, 50) + 0.061224489795918366 + >>> get_distance(2, 0, 50) + 0.0 + """ + a = x + b = y + for step in range(max_step): + a_new = a * a - b * b + x + b = 2 * a * b + y + a = a_new + + # divergence happens for all complex number with an absolute value + # greater than 4 + if a * a + b * b > 4: + break + return step / (max_step - 1) + + +def get_black_and_white_rgb(distance: float) -> tuple: + """ + Black&white color-coding that ignores the relative distance. The Mandelbrot + set is black, everything else is white. + + >>> get_black_and_white_rgb(0) + (255, 255, 255) + >>> get_black_and_white_rgb(0.5) + (255, 255, 255) + >>> get_black_and_white_rgb(1) + (0, 0, 0) + """ + if distance == 1: + return (0, 0, 0) + else: + return (255, 255, 255) + + +def get_color_coded_rgb(distance: float) -> tuple: + """ + Color-coding taking the relative distance into account. The Mandelbrot set + is black. + + >>> get_color_coded_rgb(0) + (255, 0, 0) + >>> get_color_coded_rgb(0.5) + (0, 255, 255) + >>> get_color_coded_rgb(1) + (0, 0, 0) + """ + if distance == 1: + return (0, 0, 0) + else: + return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(distance, 1, 1)) + + +def get_image( + image_width: int = 800, + image_height: int = 600, + figure_center_x: float = -0.6, + figure_center_y: float = 0, + figure_width: float = 3.2, + max_step: int = 50, + use_distance_color_coding: bool = True, +) -> Image.Image: + """ + Function to generate the image of the Mandelbrot set. Two types of coordinates + are used: image-coordinates that refer to the pixels and figure-coordinates + that refer to the complex numbers inside and outside the Mandelbrot set. The + figure-coordinates in the arguments of this function determine which section + of the Mandelbrot set is viewed. The main area of the Mandelbrot set is + roughly between "-1.5 < x < 0.5" and "-1 < y < 1" in the figure-coordinates. + + >>> get_image().load()[0,0] + (255, 0, 0) + >>> get_image(use_distance_color_coding = False).load()[0,0] + (255, 255, 255) + """ + img = Image.new("RGB", (image_width, image_height)) + pixels = img.load() + + # loop through the image-coordinates + for image_x in range(image_width): + for image_y in range(image_height): + + # determine the figure-coordinates based on the image-coordinates + figure_height = figure_width / image_width * image_height + figure_x = figure_center_x + (image_x / image_width - 0.5) * figure_width + figure_y = figure_center_y + (image_y / image_height - 0.5) * figure_height + + distance = get_distance(figure_x, figure_y, max_step) + + # color the corresponding pixel based on the selected coloring-function + if use_distance_color_coding: + pixels[image_x, image_y] = get_color_coded_rgb(distance) + else: + pixels[image_x, image_y] = get_black_and_white_rgb(distance) + + return img + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + # colored version, full figure + img = get_image() + + # uncomment for colored version, different section, zoomed in + # img = get_image(figure_center_x = -0.6, figure_center_y = -0.4, + # figure_width = 0.8) + + # uncomment for black and white version, full figure + # img = get_image(use_distance_color_coding = False) + + # uncomment to save the image + # img.save("mandelbrot.png") + + img.show() diff --git a/other/sierpinski_triangle.py b/fractals/sierpinski_triangle.py similarity index 100% rename from other/sierpinski_triangle.py rename to fractals/sierpinski_triangle.py diff --git a/graphics/bezier_curve.py b/graphics/bezier_curve.py index 295ff47e8cdc..2bb764fdc916 100644 --- a/graphics/bezier_curve.py +++ b/graphics/bezier_curve.py @@ -2,7 +2,7 @@ # https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm from __future__ import annotations -from scipy.special import comb +from scipy.special import comb # type: ignore class BezierCurve: @@ -78,7 +78,7 @@ def plot_curve(self, step_size: float = 0.01): step_size: defines the step(s) at which to evaluate the Bezier curve. The smaller the step size, the finer the curve produced. """ - from matplotlib import pyplot as plt + from matplotlib import pyplot as plt # type: ignore to_plot_x: list[float] = [] # x coordinates of points to plot to_plot_y: list[float] = [] # y coordinates of points to plot diff --git a/graphs/graph_list.py b/graphs/graph_list.py index a812fecd961e..bab6d6893a89 100644 --- a/graphs/graph_list.py +++ b/graphs/graph_list.py @@ -1,44 +1,143 @@ -#!/usr/bin/python +#!/usr/bin/env python3 -# Author: OMKAR PATHAK +# Author: OMKAR PATHAK, Nwachukwu Chidiebere -# We can use Python's dictionary for constructing the graph. +# Use a Python dictionary to construct the graph. +from pprint import pformat -class AdjacencyList: - def __init__(self): - self.adj_list = {} - def add_edge(self, from_vertex: int, to_vertex: int) -> None: - # check if vertex is already present - if from_vertex in self.adj_list: - self.adj_list[from_vertex].append(to_vertex) - else: - self.adj_list[from_vertex] = [to_vertex] +class GraphAdjacencyList: + """ + Adjacency List type Graph Data Structure that accounts for directed and undirected + Graphs. Initialize graph object indicating whether it's directed or undirected. - def print_list(self) -> None: - for i in self.adj_list: - print((i, "->", " -> ".join([str(j) for j in self.adj_list[i]]))) + Directed graph example: + >>> d_graph = GraphAdjacencyList() + >>> d_graph + {} + >>> d_graph.add_edge(0, 1) + {0: [1], 1: []} + >>> d_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [2, 4, 5], 2: [], 4: [], 5: []} + >>> d_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(d_graph) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + >>> print(repr(d_graph)) + {0: [1], 1: [2, 4, 5], 2: [0, 6, 7], 4: [], 5: [], 6: [], 7: []} + + Undirected graph example: + >>> u_graph = GraphAdjacencyList(directed=False) + >>> u_graph.add_edge(0, 1) + {0: [1], 1: [0]} + >>> u_graph.add_edge(1, 2).add_edge(1, 4).add_edge(1, 5) + {0: [1], 1: [0, 2, 4, 5], 2: [1], 4: [1], 5: [1]} + >>> u_graph.add_edge(2, 0).add_edge(2, 6).add_edge(2, 7) + {0: [1, 2], 1: [0, 2, 4, 5], 2: [1, 0, 6, 7], 4: [1], 5: [1], 6: [2], 7: [2]} + >>> u_graph.add_edge(4, 5) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(u_graph) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + >>> print(repr(u_graph)) + {0: [1, 2], + 1: [0, 2, 4, 5], + 2: [1, 0, 6, 7], + 4: [1, 5], + 5: [1, 4], + 6: [2], + 7: [2]} + """ + + def __init__(self, directed: bool = True): + """ + Parameters: + directed: (bool) Indicates if graph is directed or undirected. Default is True. + """ + + self.adj_list = {} # dictionary of lists + self.directed = directed + + def add_edge(self, source_vertex: int, destination_vertex: int) -> object: + """ + Connects vertices together. Creates and Edge from source vertex to destination + vertex. + Vertices will be created if not found in graph + """ + + if not self.directed: # For undirected graphs + # if both source vertex and destination vertex are both present in the + # adjacency list, add destination vertex to source vertex list of adjacent + # vertices and add source vertex to destination vertex list of adjacent + # vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex].append(source_vertex) + # if only source vertex is present in adjacency list, add destination vertex + # to source vertex list of adjacent vertices, then create a new vertex with + # destination vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [source_vertex] + # if only destination vertex is present in adjacency list, add source vertex + # to destination vertex list of adjacent vertices, then create a new vertex + # with source vertex as key and assign a list containing the source vertex + # as it's first adjacent vertex. + elif destination_vertex in self.adj_list: + self.adj_list[destination_vertex].append(source_vertex) + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and assign a list + # containing the destination vertex as it's first adjacent vertex also + # create a new vertex with destination vertex as key and assign a list + # containing the source vertex as it's first adjacent vertex. + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [source_vertex] + else: # For directed graphs + # if both source vertex and destination vertex are present in adjacency + # list, add destination vertex to source vertex list of adjacent vertices. + if source_vertex in self.adj_list and destination_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + # if only source vertex is present in adjacency list, add destination + # vertex to source vertex list of adjacent vertices and create a new vertex + # with destination vertex as key, which has no adjacent vertex + elif source_vertex in self.adj_list: + self.adj_list[source_vertex].append(destination_vertex) + self.adj_list[destination_vertex] = [] + # if only destination vertex is present in adjacency list, create a new + # vertex with source vertex as key and assign a list containing destination + # vertex as first adjacent vertex + elif destination_vertex in self.adj_list: + self.adj_list[source_vertex] = [destination_vertex] + # if both source vertex and destination vertex are not present in adjacency + # list, create a new vertex with source vertex as key and a list containing + # destination vertex as it's first adjacent vertex. Then create a new vertex + # with destination vertex as key, which has no adjacent vertex + else: + self.adj_list[source_vertex] = [destination_vertex] + self.adj_list[destination_vertex] = [] + + return self + + def __repr__(self) -> str: + return pformat(self.adj_list) if __name__ == "__main__": - al = AdjacencyList() - al.add_edge(0, 1) - al.add_edge(0, 4) - al.add_edge(4, 1) - al.add_edge(4, 3) - al.add_edge(1, 0) - al.add_edge(1, 4) - al.add_edge(1, 3) - al.add_edge(1, 2) - al.add_edge(2, 3) - al.add_edge(3, 4) - - al.print_list() - - # OUTPUT: - # 0 -> 1 -> 4 - # 1 -> 0 -> 4 -> 3 -> 2 - # 2 -> 3 - # 3 -> 4 - # 4 -> 1 -> 3 + import doctest + + doctest.testmod() diff --git a/other/markov_chain.py b/graphs/markov_chain.py similarity index 100% rename from other/markov_chain.py rename to graphs/markov_chain.py diff --git a/machine_learning/forecasting/run.py b/machine_learning/forecasting/run.py index 0e11f958825f..b11a230129eb 100644 --- a/machine_learning/forecasting/run.py +++ b/machine_learning/forecasting/run.py @@ -29,8 +29,7 @@ def linear_regression_prediction( >>> abs(n - 5.0) < 1e-6 # Checking precision because of floating point errors True """ - x = [[1, item, train_mtch[i]] for i, item in enumerate(train_dt)] - x = np.array(x) + x = np.array([[1, item, train_mtch[i]] for i, item in enumerate(train_dt)]) y = np.array(train_usr) beta = np.dot(np.dot(np.linalg.inv(np.dot(x.transpose(), x)), x.transpose()), y) return abs(beta[0] + test_dt[0] * beta[1] + test_mtch[0] + beta[2]) diff --git a/machine_learning/k_means_clust.py b/machine_learning/k_means_clust.py index f155d4845f41..c45be8a4c064 100644 --- a/machine_learning/k_means_clust.py +++ b/machine_learning/k_means_clust.py @@ -200,7 +200,7 @@ def kmeans( def ReportGenerator( - df: pd.DataFrame, ClusteringVariables: np.array, FillMissingReport=None + df: pd.DataFrame, ClusteringVariables: np.ndarray, FillMissingReport=None ) -> pd.DataFrame: """ Function generates easy-erading clustering report. It takes 2 arguments as an input: diff --git a/machine_learning/k_nearest_neighbours.py b/machine_learning/k_nearest_neighbours.py index e90ea09a58c1..2a90cfe5987a 100644 --- a/machine_learning/k_nearest_neighbours.py +++ b/machine_learning/k_nearest_neighbours.py @@ -32,7 +32,7 @@ def classifier(train_data, train_target, classes, point, k=5): :train_data: Set of points that are classified into two or more classes :train_target: List of classes in the order of train_data points :classes: Labels of the classes - :point: The data point that needs to be classifed + :point: The data point that needs to be classified >>> X_train = [[0, 0], [1, 0], [0, 1], [0.5, 0.5], [3, 3], [2, 3], [3, 2]] >>> y_train = [0, 0, 0, 0, 1, 1, 1] diff --git a/machine_learning/knn_sklearn.py b/machine_learning/knn_sklearn.py index 9a9114102ff3..4a621a4244b6 100644 --- a/machine_learning/knn_sklearn.py +++ b/machine_learning/knn_sklearn.py @@ -26,6 +26,6 @@ prediction = knn.predict(X_new) print( - "\nNew array: \n {}" - "\n\nTarget Names Prediction: \n {}".format(X_new, iris["target_names"][prediction]) + f"\nNew array: \n {X_new}\n\nTarget Names Prediction: \n" + f" {iris['target_names'][prediction]}" ) diff --git a/machine_learning/similarity_search.py b/machine_learning/similarity_search.py index 6bfb12ed88cb..af845c9109b1 100644 --- a/machine_learning/similarity_search.py +++ b/machine_learning/similarity_search.py @@ -8,6 +8,7 @@ 2. distance between the vector and the nearest vector (float) """ import math +from typing import List, Union import numpy as np @@ -30,7 +31,9 @@ def euclidean(input_a: np.ndarray, input_b: np.ndarray) -> float: return math.sqrt(sum(pow(a - b, 2) for a, b in zip(input_a, input_b))) -def similarity_search(dataset: np.ndarray, value_array: np.ndarray) -> list: +def similarity_search( + dataset: np.ndarray, value_array: np.ndarray +) -> List[List[Union[List[float], float]]]: """ :param dataset: Set containing the vectors. Should be ndarray. :param value_array: vector/vectors we want to know the nearest vector from dataset. diff --git a/machine_learning/word_frequency_functions.py b/machine_learning/word_frequency_functions.py index 9cf7b694c6be..3e8faf39cf07 100644 --- a/machine_learning/word_frequency_functions.py +++ b/machine_learning/word_frequency_functions.py @@ -61,7 +61,7 @@ def term_frequency(term: str, document: str) -> int: return len([word for word in tokenize_document if word.lower() == term.lower()]) -def document_frequency(term: str, corpus: str) -> int: +def document_frequency(term: str, corpus: str) -> tuple[int, int]: """ Calculate the number of documents in a corpus that contain a given term diff --git a/maths/3n_plus_1.py b/maths/3n_plus_1.py index 28c9fd7b426f..e455a158e619 100644 --- a/maths/3n_plus_1.py +++ b/maths/3n_plus_1.py @@ -9,7 +9,7 @@ def n31(a: int) -> tuple[list[int], int]: """ if not isinstance(a, int): - raise TypeError("Must be int, not {}".format(type(a).__name__)) + raise TypeError(f"Must be int, not {type(a).__name__}") if a < 1: raise ValueError(f"Given integer must be greater than 1, not {a}") diff --git a/other/binary_exponentiation_2.py b/maths/binary_exponentiation_2.py similarity index 100% rename from other/binary_exponentiation_2.py rename to maths/binary_exponentiation_2.py diff --git a/other/binary_exponentiation.py b/maths/binary_exponentiation_3.py similarity index 100% rename from other/binary_exponentiation.py rename to maths/binary_exponentiation_3.py diff --git a/other/euclidean_gcd.py b/maths/euclidean_gcd.py similarity index 100% rename from other/euclidean_gcd.py rename to maths/euclidean_gcd.py diff --git a/maths/explicit_euler.py b/maths/euler_method.py similarity index 100% rename from maths/explicit_euler.py rename to maths/euler_method.py diff --git a/maths/greedy_coin_change.py b/maths/greedy_coin_change.py new file mode 100644 index 000000000000..5a7d9e8d84ae --- /dev/null +++ b/maths/greedy_coin_change.py @@ -0,0 +1,102 @@ +""" +Test cases: +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make in Indian Currency: 987 +Following is minimal change for 987 : +500 100 100 100 100 50 20 10 5 2 + +Do you want to enter your denominations ? (Y/N) :Y +Enter number of denomination:10 +1 +5 +10 +20 +50 +100 +200 +500 +1000 +2000 +Enter the change you want to make: 18745 +Following is minimal change for 18745 : +2000 2000 2000 2000 2000 2000 2000 2000 2000 500 200 20 20 5 + +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make: 0 +The total value cannot be zero or negative. +Do you want to enter your denominations ? (Y/N) :N +Enter the change you want to make: -98 +The total value cannot be zero or negative. + +Do you want to enter your denominations ? (Y/N) :Y +Enter number of denomination:5 +1 +5 +100 +500 +1000 +Enter the change you want to make: 456 +Following is minimal change for 456 : +100 100 100 100 5 5 5 5 5 5 5 5 5 5 5 1 +""" + + +def find_minimum_change(denominations: list[int], value: int) -> list[int]: + """ + Find the minimum change from the given denominations and value + >>> find_minimum_change([1, 5, 10, 20, 50, 100, 200, 500, 1000,2000], 18745) + [2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 500, 200, 20, 20, 5] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], 987) + [500, 100, 100, 100, 100, 50, 20, 10, 5, 2] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], 0) + [] + >>> find_minimum_change([1, 2, 5, 10, 20, 50, 100, 500, 2000], -98) + [] + >>> find_minimum_change([1, 5, 100, 500, 1000], 456) + [100, 100, 100, 100, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1] + """ + total_value = int(value) + + # Initialize Result + answer = [] + + # Traverse through all denomination + for denomination in reversed(denominations): + + # Find denominations + while int(total_value) >= int(denomination): + total_value -= int(denomination) + answer.append(denomination) # Append the "answers" array + + return answer + + +# Driver Code +if __name__ == "__main__": + + denominations = list() + value = 0 + + if ( + input("Do you want to enter your denominations ? (yY/n): ").strip().lower() + == "y" + ): + n = int(input("Enter the number of denominations you want to add: ").strip()) + + for i in range(0, n): + denominations.append(int(input(f"Denomination {i}: ").strip())) + value = input("Enter the change you want to make in Indian Currency: ").strip() + else: + # All denominations of Indian Currency if user does not enter + denominations = [1, 2, 5, 10, 20, 50, 100, 500, 2000] + value = input("Enter the change you want to make: ").strip() + + if int(value) == 0 or int(value) < 0: + print("The total value cannot be zero or negative.") + + else: + print(f"Following is minimal change for {value}: ") + answer = find_minimum_change(denominations, value) + # Print result + for i in range(len(answer)): + print(answer[i], end=" ") diff --git a/other/integeration_by_simpson_approx.py b/maths/integration_by_simpson_approx.py similarity index 100% rename from other/integeration_by_simpson_approx.py rename to maths/integration_by_simpson_approx.py diff --git a/other/largest_subarray_sum.py b/maths/largest_subarray_sum.py similarity index 100% rename from other/largest_subarray_sum.py rename to maths/largest_subarray_sum.py diff --git a/other/max_sum_sliding_window.py b/maths/max_sum_sliding_window.py similarity index 96% rename from other/max_sum_sliding_window.py rename to maths/max_sum_sliding_window.py index 4be7d786f215..593cb5c8bd67 100644 --- a/other/max_sum_sliding_window.py +++ b/maths/max_sum_sliding_window.py @@ -1,45 +1,45 @@ -""" -Given an array of integer elements and an integer 'k', we are required to find the -maximum sum of 'k' consecutive elements in the array. - -Instead of using a nested for loop, in a Brute force approach we will use a technique -called 'Window sliding technique' where the nested loops can be converted to a single -loop to reduce time complexity. -""" -from typing import List - - -def max_sum_in_array(array: List[int], k: int) -> int: - """ - Returns the maximum sum of k consecutive elements - >>> arr = [1, 4, 2, 10, 2, 3, 1, 0, 20] - >>> k = 4 - >>> max_sum_in_array(arr, k) - 24 - >>> k = 10 - >>> max_sum_in_array(arr,k) - Traceback (most recent call last): - ... - ValueError: Invalid Input - >>> arr = [1, 4, 2, 10, 2, 13, 1, 0, 2] - >>> k = 4 - >>> max_sum_in_array(arr, k) - 27 - """ - if len(array) < k or k < 0: - raise ValueError("Invalid Input") - max_sum = current_sum = sum(array[:k]) - for i in range(len(array) - k): - current_sum = current_sum - array[i] + array[i + k] - max_sum = max(max_sum, current_sum) - return max_sum - - -if __name__ == "__main__": - from doctest import testmod - from random import randint - - testmod() - array = [randint(-1000, 1000) for i in range(100)] - k = randint(0, 110) - print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}") +""" +Given an array of integer elements and an integer 'k', we are required to find the +maximum sum of 'k' consecutive elements in the array. + +Instead of using a nested for loop, in a Brute force approach we will use a technique +called 'Window sliding technique' where the nested loops can be converted to a single +loop to reduce time complexity. +""" +from typing import List + + +def max_sum_in_array(array: List[int], k: int) -> int: + """ + Returns the maximum sum of k consecutive elements + >>> arr = [1, 4, 2, 10, 2, 3, 1, 0, 20] + >>> k = 4 + >>> max_sum_in_array(arr, k) + 24 + >>> k = 10 + >>> max_sum_in_array(arr,k) + Traceback (most recent call last): + ... + ValueError: Invalid Input + >>> arr = [1, 4, 2, 10, 2, 13, 1, 0, 2] + >>> k = 4 + >>> max_sum_in_array(arr, k) + 27 + """ + if len(array) < k or k < 0: + raise ValueError("Invalid Input") + max_sum = current_sum = sum(array[:k]) + for i in range(len(array) - k): + current_sum = current_sum - array[i] + array[i + k] + max_sum = max(max_sum, current_sum) + return max_sum + + +if __name__ == "__main__": + from doctest import testmod + from random import randint + + testmod() + array = [randint(-1000, 1000) for i in range(100)] + k = randint(0, 110) + print(f"The maximum sum of {k} consecutive elements is {max_sum_in_array(array,k)}") diff --git a/other/median_of_two_arrays.py b/maths/median_of_two_arrays.py similarity index 100% rename from other/median_of_two_arrays.py rename to maths/median_of_two_arrays.py diff --git a/maths/polynomial_evaluation.py b/maths/polynomial_evaluation.py index e929a2d02972..68ff97ddd25d 100644 --- a/maths/polynomial_evaluation.py +++ b/maths/polynomial_evaluation.py @@ -5,7 +5,7 @@ def evaluate_poly(poly: Sequence[float], x: float) -> float: """Evaluate a polynomial f(x) at specified point x and return the value. Arguments: - poly -- the coeffiecients of a polynomial as an iterable in order of + poly -- the coefficients of a polynomial as an iterable in order of ascending degree x -- the point at which to evaluate the polynomial @@ -26,7 +26,7 @@ def horner(poly: Sequence[float], x: float) -> float: https://en.wikipedia.org/wiki/Horner's_method Arguments: - poly -- the coeffiecients of a polynomial as an iterable in order of + poly -- the coefficients of a polynomial as an iterable in order of ascending degree x -- the point at which to evaluate the polynomial diff --git a/other/primelib.py b/maths/primelib.py similarity index 100% rename from other/primelib.py rename to maths/primelib.py diff --git a/maths/quadratic_equations_complex_numbers.py b/maths/quadratic_equations_complex_numbers.py index 01a411bc560d..1035171e4ec3 100644 --- a/maths/quadratic_equations_complex_numbers.py +++ b/maths/quadratic_equations_complex_numbers.py @@ -30,8 +30,8 @@ def quadratic_roots(a: int, b: int, c: int) -> tuple[complex, complex]: def main(): - solutions = quadratic_roots(a=5, b=6, c=1) - print("The solutions are: {} and {}".format(*solutions)) + solution1, solution2 = quadratic_roots(a=5, b=6, c=1) + print(f"The solutions are: {solution1} and {solution2}") if __name__ == "__main__": diff --git a/maths/series/arithmetic_mean.py b/maths/series/arithmetic_mean.py new file mode 100644 index 000000000000..b5d64b63ac3f --- /dev/null +++ b/maths/series/arithmetic_mean.py @@ -0,0 +1,66 @@ +""" +ARITHMETIC MEAN : https://en.wikipedia.org/wiki/Arithmetic_mean + +""" + + +def is_arithmetic_series(series: list) -> bool: + """ + checking whether the input series is arithmetic series or not + + >>> is_arithmetic_series([2, 4, 6]) + True + >>> is_arithmetic_series([3, 6, 12, 24]) + False + >>> is_arithmetic_series([1, 2, 3]) + True + """ + if len(series) == 1: + return True + common_diff = series[1] - series[0] + for index in range(len(series) - 1): + if series[index + 1] - series[index] != common_diff: + return False + return True + + +def arithmetic_mean(series: list) -> float: + """ + return the arithmetic mean of series + + >>> arithmetic_mean([2, 4, 6]) + 4.0 + >>> arithmetic_mean([3, 6, 9, 12]) + 7.5 + >>> arithmetic_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 6] + >>> arithmetic_mean([4, 8, 1]) + Traceback (most recent call last): + ... + ValueError: Input list is not an arithmetic series + >>> arithmetic_mean([1, 2, 3]) + 2.0 + >>> arithmetic_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 6]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if not is_arithmetic_series(series): + raise ValueError("Input list is not an arithmetic series") + answer = 0 + for val in series: + answer += val + return answer / len(series) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/maths/series/geometric_mean.py b/maths/series/geometric_mean.py new file mode 100644 index 000000000000..50ae54ad6574 --- /dev/null +++ b/maths/series/geometric_mean.py @@ -0,0 +1,75 @@ +""" +GEOMETRIC MEAN : https://en.wikipedia.org/wiki/Geometric_mean +""" + + +def is_geometric_series(series: list) -> bool: + """ + checking whether the input series is geometric series or not + + >>> is_geometric_series([2, 4, 8]) + True + >>> is_geometric_series([3, 6, 12, 24]) + True + >>> is_geometric_series([1, 2, 3]) + False + >>> is_geometric_series([0, 0, 3]) + False + + """ + if len(series) == 1: + return True + try: + common_ratio = series[1] / series[0] + for index in range(len(series) - 1): + if series[index + 1] / series[index] != common_ratio: + return False + except ZeroDivisionError: + return False + return True + + +def geometric_mean(series: list) -> float: + """ + return the geometric mean of series + + >>> geometric_mean([2, 4, 8]) + 3.9999999999999996 + >>> geometric_mean([3, 6, 12, 24]) + 8.48528137423857 + >>> geometric_mean([4, 8, 16]) + 7.999999999999999 + >>> geometric_mean(4) + Traceback (most recent call last): + ... + ValueError: Input series is not valid, valid series - [2, 4, 8] + >>> geometric_mean([1, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Input list is not a geometric series + >>> geometric_mean([0, 2, 3]) + Traceback (most recent call last): + ... + ValueError: Input list is not a geometric series + >>> geometric_mean([]) + Traceback (most recent call last): + ... + ValueError: Input list must be a non empty list + + """ + if not isinstance(series, list): + raise ValueError("Input series is not valid, valid series - [2, 4, 8]") + if len(series) == 0: + raise ValueError("Input list must be a non empty list") + if not is_geometric_series(series): + raise ValueError("Input list is not a geometric series") + answer = 1 + for value in series: + answer *= value + return pow(answer, 1 / len(series)) + + +if __name__ == "__main__": + import doctest + + doctest.testmod() diff --git a/other/triplet_sum.py b/maths/triplet_sum.py similarity index 96% rename from other/triplet_sum.py rename to maths/triplet_sum.py index 0e78bb52bb72..22fab17d30c2 100644 --- a/other/triplet_sum.py +++ b/maths/triplet_sum.py @@ -1,89 +1,89 @@ -""" -Given an array of integers and another integer target, -we are required to find a triplet from the array such that it's sum is equal to -the target. -""" -from __future__ import annotations - -from itertools import permutations -from random import randint -from timeit import repeat - - -def make_dataset() -> tuple[list[int], int]: - arr = [randint(-1000, 1000) for i in range(10)] - r = randint(-5000, 5000) - return (arr, r) - - -dataset = make_dataset() - - -def triplet_sum1(arr: list[int], target: int) -> tuple[int, int, int]: - """ - Returns a triplet in the array with sum equal to target, - else (0, 0, 0). - >>> triplet_sum1([13, 29, 7, 23, 5], 35) - (5, 7, 23) - >>> triplet_sum1([37, 9, 19, 50, 44], 65) - (9, 19, 37) - >>> arr = [6, 47, 27, 1, 15] - >>> target = 11 - >>> triplet_sum1(arr, target) - (0, 0, 0) - """ - for triplet in permutations(arr, 3): - if sum(triplet) == target: - return tuple(sorted(triplet)) - return (0, 0, 0) - - -def triplet_sum2(arr: list[int], target: int) -> tuple[int, int, int]: - """ - Returns a triplet in the array with sum equal to target, - else (0, 0, 0). - >>> triplet_sum2([13, 29, 7, 23, 5], 35) - (5, 7, 23) - >>> triplet_sum2([37, 9, 19, 50, 44], 65) - (9, 19, 37) - >>> arr = [6, 47, 27, 1, 15] - >>> target = 11 - >>> triplet_sum2(arr, target) - (0, 0, 0) - """ - arr.sort() - n = len(arr) - for i in range(n - 1): - left, right = i + 1, n - 1 - while left < right: - if arr[i] + arr[left] + arr[right] == target: - return (arr[i], arr[left], arr[right]) - elif arr[i] + arr[left] + arr[right] < target: - left += 1 - elif arr[i] + arr[left] + arr[right] > target: - right -= 1 - return (0, 0, 0) - - -def solution_times() -> tuple[float, float]: - setup_code = """ -from __main__ import dataset, triplet_sum1, triplet_sum2 -""" - test_code1 = """ -triplet_sum1(*dataset) -""" - test_code2 = """ -triplet_sum2(*dataset) -""" - times1 = repeat(setup=setup_code, stmt=test_code1, repeat=5, number=10000) - times2 = repeat(setup=setup_code, stmt=test_code2, repeat=5, number=10000) - return (min(times1), min(times2)) - - -if __name__ == "__main__": - from doctest import testmod - - testmod() - times = solution_times() - print(f"The time for naive implementation is {times[0]}.") - print(f"The time for optimized implementation is {times[1]}.") +""" +Given an array of integers and another integer target, +we are required to find a triplet from the array such that it's sum is equal to +the target. +""" +from __future__ import annotations + +from itertools import permutations +from random import randint +from timeit import repeat + + +def make_dataset() -> tuple[list[int], int]: + arr = [randint(-1000, 1000) for i in range(10)] + r = randint(-5000, 5000) + return (arr, r) + + +dataset = make_dataset() + + +def triplet_sum1(arr: list[int], target: int) -> tuple[int, int, int]: + """ + Returns a triplet in the array with sum equal to target, + else (0, 0, 0). + >>> triplet_sum1([13, 29, 7, 23, 5], 35) + (5, 7, 23) + >>> triplet_sum1([37, 9, 19, 50, 44], 65) + (9, 19, 37) + >>> arr = [6, 47, 27, 1, 15] + >>> target = 11 + >>> triplet_sum1(arr, target) + (0, 0, 0) + """ + for triplet in permutations(arr, 3): + if sum(triplet) == target: + return tuple(sorted(triplet)) + return (0, 0, 0) + + +def triplet_sum2(arr: list[int], target: int) -> tuple[int, int, int]: + """ + Returns a triplet in the array with sum equal to target, + else (0, 0, 0). + >>> triplet_sum2([13, 29, 7, 23, 5], 35) + (5, 7, 23) + >>> triplet_sum2([37, 9, 19, 50, 44], 65) + (9, 19, 37) + >>> arr = [6, 47, 27, 1, 15] + >>> target = 11 + >>> triplet_sum2(arr, target) + (0, 0, 0) + """ + arr.sort() + n = len(arr) + for i in range(n - 1): + left, right = i + 1, n - 1 + while left < right: + if arr[i] + arr[left] + arr[right] == target: + return (arr[i], arr[left], arr[right]) + elif arr[i] + arr[left] + arr[right] < target: + left += 1 + elif arr[i] + arr[left] + arr[right] > target: + right -= 1 + return (0, 0, 0) + + +def solution_times() -> tuple[float, float]: + setup_code = """ +from __main__ import dataset, triplet_sum1, triplet_sum2 +""" + test_code1 = """ +triplet_sum1(*dataset) +""" + test_code2 = """ +triplet_sum2(*dataset) +""" + times1 = repeat(setup=setup_code, stmt=test_code1, repeat=5, number=10000) + times2 = repeat(setup=setup_code, stmt=test_code2, repeat=5, number=10000) + return (min(times1), min(times2)) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() + times = solution_times() + print(f"The time for naive implementation is {times[0]}.") + print(f"The time for optimized implementation is {times[1]}.") diff --git a/other/two_pointer.py b/maths/two_pointer.py similarity index 100% rename from other/two_pointer.py rename to maths/two_pointer.py diff --git a/other/two_sum.py b/maths/two_sum.py similarity index 100% rename from other/two_sum.py rename to maths/two_sum.py diff --git a/matrix/nth_fibonacci_using_matrix_exponentiation.py b/matrix/nth_fibonacci_using_matrix_exponentiation.py index 296c36e88691..8c39de0f23b6 100644 --- a/matrix/nth_fibonacci_using_matrix_exponentiation.py +++ b/matrix/nth_fibonacci_using_matrix_exponentiation.py @@ -71,13 +71,13 @@ def nth_fibonacci_bruteforce(n): def main(): - fmt = ( - "{} fibonacci number using matrix exponentiation is {} and using bruteforce " - "is {}\n" - ) for ordinal in "0th 1st 2nd 3rd 10th 100th 1000th".split(): n = int("".join(c for c in ordinal if c in "0123456789")) # 1000th --> 1000 - print(fmt.format(ordinal, nth_fibonacci_matrix(n), nth_fibonacci_bruteforce(n))) + print( + f"{ordinal} fibonacci number using matrix exponentiation is " + f"{nth_fibonacci_matrix(n)} and using bruteforce is " + f"{nth_fibonacci_bruteforce(n)}\n" + ) # from timeit import timeit # print(timeit("nth_fibonacci_matrix(1000000)", # "from main import nth_fibonacci_matrix", number=5)) diff --git a/matrix/rotate_matrix.py b/matrix/rotate_matrix.py index 6daf7e0cf2c5..f638597ae35d 100644 --- a/matrix/rotate_matrix.py +++ b/matrix/rotate_matrix.py @@ -5,8 +5,10 @@ https://stackoverflow.com/questions/42519/how-do-you-rotate-a-two-dimensional-array """ +from __future__ import annotations -def make_matrix(row_size: int = 4) -> [[int]]: + +def make_matrix(row_size: int = 4) -> list[list]: """ >>> make_matrix() [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] @@ -23,7 +25,7 @@ def make_matrix(row_size: int = 4) -> [[int]]: return [[1 + x + y * row_size for x in range(row_size)] for y in range(row_size)] -def rotate_90(matrix: [[]]) -> [[]]: +def rotate_90(matrix: list[list]) -> list[list]: """ >>> rotate_90(make_matrix()) [[4, 8, 12, 16], [3, 7, 11, 15], [2, 6, 10, 14], [1, 5, 9, 13]] @@ -35,7 +37,7 @@ def rotate_90(matrix: [[]]) -> [[]]: # OR.. transpose(reverse_column(matrix)) -def rotate_180(matrix: [[]]) -> [[]]: +def rotate_180(matrix: list[list]) -> list[list]: """ >>> rotate_180(make_matrix()) [[16, 15, 14, 13], [12, 11, 10, 9], [8, 7, 6, 5], [4, 3, 2, 1]] @@ -47,7 +49,7 @@ def rotate_180(matrix: [[]]) -> [[]]: # OR.. reverse_column(reverse_row(matrix)) -def rotate_270(matrix: [[]]) -> [[]]: +def rotate_270(matrix: list[list]) -> list[list]: """ >>> rotate_270(make_matrix()) [[13, 9, 5, 1], [14, 10, 6, 2], [15, 11, 7, 3], [16, 12, 8, 4]] @@ -59,22 +61,22 @@ def rotate_270(matrix: [[]]) -> [[]]: # OR.. transpose(reverse_row(matrix)) -def transpose(matrix: [[]]) -> [[]]: +def transpose(matrix: list[list]) -> list[list]: matrix[:] = [list(x) for x in zip(*matrix)] return matrix -def reverse_row(matrix: [[]]) -> [[]]: +def reverse_row(matrix: list[list]) -> list[list]: matrix[:] = matrix[::-1] return matrix -def reverse_column(matrix: [[]]) -> [[]]: +def reverse_column(matrix: list[list]) -> list[list]: matrix[:] = [x[::-1] for x in matrix] return matrix -def print_matrix(matrix: [[]]) -> [[]]: +def print_matrix(matrix: list[list]) -> None: for i in matrix: print(*i) diff --git a/matrix/sherman_morrison.py b/matrix/sherman_morrison.py index 4920ec6c13db..3466b3d4a01f 100644 --- a/matrix/sherman_morrison.py +++ b/matrix/sherman_morrison.py @@ -175,9 +175,7 @@ def __mul__(self, another): result[r, c] += self[r, i] * another[i, c] return result else: - raise TypeError( - "Unsupported type given for another ({})".format(type(another)) - ) + raise TypeError(f"Unsupported type given for another ({type(another)})") def transpose(self): """ @@ -260,7 +258,7 @@ def test1(): print(f"v is {v}") print("uv^T is %s" % (u * v.transpose())) # Sherman Morrison - print("(a + uv^T)^(-1) is {}".format(ainv.ShermanMorrison(u, v))) + print(f"(a + uv^T)^(-1) is {ainv.ShermanMorrison(u, v)}") def test2(): import doctest diff --git a/matrix/tests/test_matrix_operation.py b/matrix/tests/test_matrix_operation.py index 3500dfeb0641..65b35fd7e78b 100644 --- a/matrix/tests/test_matrix_operation.py +++ b/matrix/tests/test_matrix_operation.py @@ -12,7 +12,7 @@ import sys import numpy as np -import pytest +import pytest # type: ignore # Custom/local libraries from matrix import matrix_operation as matop diff --git a/neural_network/2_hidden_layers_neural_network.py b/neural_network/2_hidden_layers_neural_network.py index baa4316200d9..1cf78ec4c7c0 100644 --- a/neural_network/2_hidden_layers_neural_network.py +++ b/neural_network/2_hidden_layers_neural_network.py @@ -196,8 +196,8 @@ def predict(self, input: numpy.ndarray) -> int: >>> output_val = numpy.array(([0], [1], [1]), dtype=float) >>> nn = TwoHiddenLayerNeuralNetwork(input_val, output_val) >>> nn.train(output_val, 1000, False) - >>> nn.predict([0,1,0]) - 1 + >>> nn.predict([0,1,0]) in (0, 1) + True """ # Input values for which the predictions are to be made. @@ -260,8 +260,8 @@ def example() -> int: In this example the output is divided into 2 classes i.e. binary classification, the two classes are represented by '0' and '1'. - >>> example() - 1 + >>> example() in (0, 1) + True """ # Input values. input = numpy.array( diff --git a/neural_network/perceptron.py b/neural_network/perceptron.py index 23b409b227c4..063be5ea554c 100644 --- a/neural_network/perceptron.py +++ b/neural_network/perceptron.py @@ -11,7 +11,14 @@ class Perceptron: - def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=-1): + def __init__( + self, + sample: list[list[float]], + target: list[int], + learning_rate: float = 0.01, + epoch_number: int = 1000, + bias: float = -1, + ) -> None: """ Initializes a Perceptron network for oil analysis :param sample: sample dataset of 3 parameters with shape [30,3] @@ -46,7 +53,7 @@ def __init__(self, sample, target, learning_rate=0.01, epoch_number=1000, bias=- self.bias = bias self.number_sample = len(sample) self.col_sample = len(sample[0]) # number of columns in dataset - self.weight = [] + self.weight: list = [] def training(self) -> None: """ @@ -94,7 +101,7 @@ def training(self) -> None: # if epoch_count > self.epoch_number or not error: break - def sort(self, sample) -> None: + def sort(self, sample: list[float]) -> None: """ :param sample: example row to classify as P1 or P2 :return: None @@ -221,11 +228,11 @@ def sign(self, u: float) -> int: print("Finished training perceptron") print("Enter values to predict or q to exit") while True: - sample = [] + sample: list = [] for i in range(len(samples[0])): - observation = input("value: ").strip() - if observation == "q": + user_input = input("value: ").strip() + if user_input == "q": break - observation = float(observation) + observation = float(user_input) sample.insert(i, observation) network.sort(sample) diff --git a/other/graham_scan.py b/other/graham_scan.py new file mode 100644 index 000000000000..67c5cd8ab9d8 --- /dev/null +++ b/other/graham_scan.py @@ -0,0 +1,171 @@ +""" +This is a pure Python implementation of the merge-insertion sort algorithm +Source: https://en.wikipedia.org/wiki/Graham_scan + +For doctests run following command: +python3 -m doctest -v graham_scan.py +""" + +from __future__ import annotations + +from collections import deque +from enum import Enum +from math import atan2, degrees +from sys import maxsize + + +def graham_scan(points: list[list[int, int]]) -> list[list[int, int]]: + """Pure implementation of graham scan algorithm in Python + + :param points: The unique points on coordinates. + :return: The points on convex hell. + + Examples: + >>> graham_scan([(9, 6), (3, 1), (0, 0), (5, 5), (5, 2), (7, 0), (3, 3), (1, 4)]) + [(0, 0), (7, 0), (9, 6), (5, 5), (1, 4)] + + >>> graham_scan([(0, 0), (1, 0), (1, 1), (0, 1)]) + [(0, 0), (1, 0), (1, 1), (0, 1)] + + >>> graham_scan([(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)]) + [(0, 0), (1, 1), (2, 2), (3, 3), (-1, 2)] + + >>> graham_scan([(-100, 20), (99, 3), (1, 10000001), (5133186, -25), (-66, -4)]) + [(5133186, -25), (1, 10000001), (-100, 20), (-66, -4)] + """ + + if len(points) <= 2: + # There is no convex hull + raise ValueError("graham_scan: argument must contain more than 3 points.") + if len(points) == 3: + return points + # find the lowest and the most left point + minidx = 0 + miny, minx = maxsize, maxsize + for i, point in enumerate(points): + x = point[0] + y = point[1] + if y < miny: + miny = y + minx = x + minidx = i + if y == miny: + if x < minx: + minx = x + minidx = i + + # remove the lowest and the most left point from points for preparing for sort + points.pop(minidx) + + def angle_comparer(point: list[int, int], minx: int, miny: int) -> float: + """Return the angle toward to point from (minx, miny) + + :param point: The target point + minx: The starting point's x + miny: The starting point's y + :return: the angle + + Examples: + >>> angle_comparer([1,1], 0, 0) + 45.0 + + >>> angle_comparer([100,1], 10, 10) + -5.710593137499642 + + >>> angle_comparer([5,5], 2, 3) + 33.690067525979785 + """ + # sort the points accorgind to the angle from the lowest and the most left point + x = point[0] + y = point[1] + angle = degrees(atan2(y - miny, x - minx)) + return angle + + sorted_points = sorted(points, key=lambda point: angle_comparer(point, minx, miny)) + # This insert actually costs complexity, + # and you should insteadly add (minx, miny) into stack later. + # I'm using insert just for easy understanding. + sorted_points.insert(0, (minx, miny)) + + # traversal from the lowest and the most left point in anti-clockwise direction + # if direction gets right, the previous point is not the convex hull. + class Direction(Enum): + left = 1 + straight = 2 + right = 3 + + def check_direction( + starting: list[int, int], via: list[int, int], target: list[int, int] + ) -> Direction: + """Return the direction toward to the line from via to target from starting + + :param starting: The starting point + via: The via point + target: The target point + :return: the Direction + + Examples: + >>> check_direction([1,1], [2,2], [3,3]) + Direction.straight + + >>> check_direction([60,1], [-50,199], [30,2]) + Direction.left + + >>> check_direction([0,0], [5,5], [10,0]) + Direction.right + """ + x0, y0 = starting + x1, y1 = via + x2, y2 = target + via_angle = degrees(atan2(y1 - y0, x1 - x0)) + if via_angle < 0: + via_angle += 360 + target_angle = degrees(atan2(y2 - y0, x2 - x0)) + if target_angle < 0: + target_angle += 360 + # t- + # \ \ + # \ v + # \| + # s + # via_angle is always lower than target_angle, if direction is left. + # If they are same, it means they are on a same line of convex hull. + if target_angle > via_angle: + return Direction.left + if target_angle == via_angle: + return Direction.straight + if target_angle < via_angle: + return Direction.right + + stack = deque() + stack.append(sorted_points[0]) + stack.append(sorted_points[1]) + stack.append(sorted_points[2]) + # In any ways, the first 3 points line are towards left. + # Because we sort them the angle from minx, miny. + current_direction = Direction.left + + for i in range(3, len(sorted_points)): + while True: + starting = stack[-2] + via = stack[-1] + target = sorted_points[i] + next_direction = check_direction(starting, via, target) + + if next_direction == Direction.left: + current_direction = Direction.left + break + if next_direction == Direction.straight: + if current_direction == Direction.left: + # We keep current_direction as left. + # Because if the straight line keeps as straight, + # we want to know if this straight line is towards left. + break + elif current_direction == Direction.right: + # If the straight line is towards right, + # every previous points on those straigh line is not convex hull. + stack.pop() + if next_direction == Direction.right: + stack.pop() + stack.append(sorted_points[i]) + return list(stack) diff --git a/traversals/__init__.py b/project_euler/problem_109/__init__.py similarity index 100% rename from traversals/__init__.py rename to project_euler/problem_109/__init__.py diff --git a/project_euler/problem_109/sol1.py b/project_euler/problem_109/sol1.py new file mode 100644 index 000000000000..91c71eb9f4cb --- /dev/null +++ b/project_euler/problem_109/sol1.py @@ -0,0 +1,89 @@ +""" +In the game of darts a player throws three darts at a target board which is +split into twenty equal sized sections numbered one to twenty. + +The score of a dart is determined by the number of the region that the dart +lands in. A dart landing outside the red/green outer ring scores zero. The black +and cream regions inside this ring represent single scores. However, the red/green +outer ring and middle ring score double and treble scores respectively. + +At the centre of the board are two concentric circles called the bull region, or +bulls-eye. The outer bull is worth 25 points and the inner bull is a double, +worth 50 points. + +There are many variations of rules but in the most popular game the players will +begin with a score 301 or 501 and the first player to reduce their running total +to zero is a winner. However, it is normal to play a "doubles out" system, which +means that the player must land a double (including the double bulls-eye at the +centre of the board) on their final dart to win; any other dart that would reduce +their running total to one or lower means the score for that set of three darts +is "bust". + +When a player is able to finish on their current score it is called a "checkout" +and the highest checkout is 170: T20 T20 D25 (two treble 20s and double bull). + +There are exactly eleven distinct ways to checkout on a score of 6: + +D3 +D1 D2 +S2 D2 +D2 D1 +S4 D1 +S1 S1 D2 +S1 T1 D1 +S1 S3 D1 +D1 D1 D1 +D1 S2 D1 +S2 S2 D1 + +Note that D1 D2 is considered different to D2 D1 as they finish on different +doubles. However, the combination S1 T1 D1 is considered the same as T1 S1 D1. + +In addition we shall not include misses in considering combinations; for example, +D3 is the same as 0 D3 and 0 0 D3. + +Incredibly there are 42336 distinct ways of checking out in total. + +How many distinct ways can a player checkout with a score less than 100? + +Solution: + We first construct a list of the possible dart values, separated by type. + We then iterate through the doubles, followed by the possible 2 following throws. + If the total of these three darts is less than the given limit, we increment + the counter. +""" + +from itertools import combinations_with_replacement + + +def solution(limit: int = 100) -> int: + """ + Count the number of distinct ways a player can checkout with a score + less than limit. + >>> solution(171) + 42336 + >>> solution(50) + 12577 + """ + singles: list[int] = [x for x in range(1, 21)] + [25] + doubles: list[int] = [2 * x for x in range(1, 21)] + [50] + triples: list[int] = [3 * x for x in range(1, 21)] + all_values: list[int] = singles + doubles + triples + [0] + + num_checkouts: int = 0 + double: int + throw1: int + throw2: int + checkout_total: int + + for double in doubles: + for throw1, throw2 in combinations_with_replacement(all_values, 2): + checkout_total = double + throw1 + throw2 + if checkout_total < limit: + num_checkouts += 1 + + return num_checkouts + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/project_euler/problem_121/__init__.py b/project_euler/problem_121/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_121/sol1.py b/project_euler/problem_121/sol1.py new file mode 100644 index 000000000000..93679cf4a897 --- /dev/null +++ b/project_euler/problem_121/sol1.py @@ -0,0 +1,64 @@ +""" +A bag contains one red disc and one blue disc. In a game of chance a player takes a +disc at random and its colour is noted. After each turn the disc is returned to the +bag, an extra red disc is added, and another disc is taken at random. + +The player pays £1 to play and wins if they have taken more blue discs than red +discs at the end of the game. + +If the game is played for four turns, the probability of a player winning is exactly +11/120, and so the maximum prize fund the banker should allocate for winning in this +game would be £10 before they would expect to incur a loss. Note that any payout will +be a whole number of pounds and also includes the original £1 paid to play the game, +so in the example given the player actually wins £9. + +Find the maximum prize fund that should be allocated to a single game in which +fifteen turns are played. + + +Solution: + For each 15-disc sequence of red and blue for which there are more red than blue, + we calculate the probability of that sequence and add it to the total probability + of the player winning. The inverse of this probability gives an upper bound for + the prize if the banker wants to avoid an expected loss. +""" + +from itertools import product + + +def solution(num_turns: int = 15) -> int: + """ + Find the maximum prize fund that should be allocated to a single game in which + fifteen turns are played. + >>> solution(4) + 10 + >>> solution(10) + 225 + """ + total_prob: float = 0.0 + prob: float + num_blue: int + num_red: int + ind: int + col: int + series: tuple[int, ...] + + for series in product(range(2), repeat=num_turns): + num_blue = series.count(1) + num_red = num_turns - num_blue + if num_red >= num_blue: + continue + prob = 1.0 + for ind, col in enumerate(series, 2): + if col == 0: + prob *= (ind - 1) / ind + else: + prob *= 1 / ind + + total_prob += prob + + return int(1 / total_prob) + + +if __name__ == "__main__": + print(f"{solution() = }") diff --git a/quantum/ripple_adder_classic.py b/quantum/ripple_adder_classic.py index f5b0a980c8e2..dc0c2103b2e5 100644 --- a/quantum/ripple_adder_classic.py +++ b/quantum/ripple_adder_classic.py @@ -6,7 +6,7 @@ from qiskit.providers import BaseBackend -def store_two_classics(val1: int, val2: int) -> (QuantumCircuit, str, str): +def store_two_classics(val1: int, val2: int) -> tuple[QuantumCircuit, str, str]: """ Generates a Quantum Circuit which stores two classical integers Returns the circuit and binary representation of the integers diff --git a/traversals/binary_tree_traversals.py b/searches/binary_tree_traversal.py similarity index 99% rename from traversals/binary_tree_traversals.py rename to searches/binary_tree_traversal.py index cb471ba55bac..f919a2962354 100644 --- a/traversals/binary_tree_traversals.py +++ b/searches/binary_tree_traversal.py @@ -188,7 +188,7 @@ def pre_order_iter(node: TreeNode) -> None: """ if not isinstance(node, TreeNode) or not node: return - stack: List[TreeNode] = [] + stack: list[TreeNode] = [] n = node while n or stack: while n: # start from root node, find its left child @@ -218,7 +218,7 @@ def in_order_iter(node: TreeNode) -> None: """ if not isinstance(node, TreeNode) or not node: return - stack: List[TreeNode] = [] + stack: list[TreeNode] = [] n = node while n or stack: while n: diff --git a/searches/hill_climbing.py b/searches/hill_climbing.py index 70622ebefb4e..bb24e781a6c1 100644 --- a/searches/hill_climbing.py +++ b/searches/hill_climbing.py @@ -60,7 +60,7 @@ def get_neighbors(self): def __hash__(self): """ - hash the string represetation of the current search state. + hash the string representation of the current search state. """ return hash(str(self)) diff --git a/sorts/bucket_sort.py b/sorts/bucket_sort.py index a0566be662e3..1ac76774f4ba 100644 --- a/sorts/bucket_sort.py +++ b/sorts/bucket_sort.py @@ -27,6 +27,7 @@ Source: https://en.wikipedia.org/wiki/Bucket_sort """ +from typing import List def bucket_sort(my_list: list) -> list: @@ -51,7 +52,7 @@ def bucket_sort(my_list: list) -> list: return [] min_value, max_value = min(my_list), max(my_list) bucket_count = int(max_value - min_value) + 1 - buckets = [[] for _ in range(bucket_count)] + buckets: List[list] = [[] for _ in range(bucket_count)] for i in range(len(my_list)): buckets[(int(my_list[i] - min_value) // bucket_count)].append(my_list[i]) diff --git a/sorts/cocktail_shaker_sort.py b/sorts/cocktail_shaker_sort.py index 42015abc5f97..b738ff31d768 100644 --- a/sorts/cocktail_shaker_sort.py +++ b/sorts/cocktail_shaker_sort.py @@ -33,7 +33,8 @@ def cocktail_shaker_sort(unsorted: list) -> list: swapped = True if not swapped: - return unsorted + break + return unsorted if __name__ == "__main__": diff --git a/sorts/odd_even_sort.py b/sorts/odd_even_sort.py new file mode 100644 index 000000000000..557337ee77bc --- /dev/null +++ b/sorts/odd_even_sort.py @@ -0,0 +1,47 @@ +"""For reference +https://en.wikipedia.org/wiki/Odd%E2%80%93even_sort +""" + + +def odd_even_sort(input_list: list) -> list: + """this algorithm uses the same idea of bubblesort, + but by first dividing in two phase (odd and even). + Originally developed for use on parallel processors + with local interconnections. + :param collection: mutable ordered sequence of elements + :return: same collection in ascending order + Examples: + >>> odd_even_sort([5 , 4 ,3 ,2 ,1]) + [1, 2, 3, 4, 5] + >>> odd_even_sort([]) + [] + >>> odd_even_sort([-10 ,-1 ,10 ,2]) + [-10, -1, 2, 10] + >>> odd_even_sort([1 ,2 ,3 ,4]) + [1, 2, 3, 4] + """ + sorted = False + while sorted is False: # Until all the indices are traversed keep looping + sorted = True + for i in range(0, len(input_list) - 1, 2): # iterating over all even indices + if input_list[i] > input_list[i + 1]: + + input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] + # swapping if elements not in order + sorted = False + + for i in range(1, len(input_list) - 1, 2): # iterating over all odd indices + if input_list[i] > input_list[i + 1]: + input_list[i], input_list[i + 1] = input_list[i + 1], input_list[i] + # swapping if elements not in order + sorted = False + return input_list + + +if __name__ == "__main__": + print("Enter list to be sorted") + input_list = [int(x) for x in input().split()] + # inputing elements of the list in one line + sorted_list = odd_even_sort(input_list) + print("The sorted list is") + print(sorted_list) diff --git a/sorts/patience_sort.py b/sorts/patience_sort.py index f4e35d9a0ac6..87f5a4078612 100644 --- a/sorts/patience_sort.py +++ b/sorts/patience_sort.py @@ -1,6 +1,7 @@ from bisect import bisect_left from functools import total_ordering from heapq import merge +from typing import List """ A pure Python implementation of the patience sort algorithm @@ -43,7 +44,7 @@ def patience_sort(collection: list) -> list: >>> patience_sort([-3, -17, -48]) [-48, -17, -3] """ - stacks = [] + stacks: List[Stack] = [] # sort into stacks for element in collection: new_stacks = Stack([element]) diff --git a/sorts/quick_sort.py b/sorts/quick_sort.py index c6687a7fa8d5..6f51f6eca7db 100644 --- a/sorts/quick_sort.py +++ b/sorts/quick_sort.py @@ -7,6 +7,7 @@ For manual testing run: python3 quick_sort.py """ +from typing import List def quick_sort(collection: list) -> list: @@ -26,8 +27,8 @@ def quick_sort(collection: list) -> list: if len(collection) < 2: return collection pivot = collection.pop() # Use the last element as the first pivot - greater = [] # All elements greater than pivot - lesser = [] # All elements less than or equal to pivot + greater: List[int] = [] # All elements greater than pivot + lesser: List[int] = [] # All elements less than or equal to pivot for element in collection: (greater if element > pivot else lesser).append(element) return quick_sort(lesser) + [pivot] + quick_sort(greater) diff --git a/sorts/quick_sort_3_partition.py b/sorts/quick_sort_3_partition.py index 18c6e0f876d2..1a6db6a364f0 100644 --- a/sorts/quick_sort_3_partition.py +++ b/sorts/quick_sort_3_partition.py @@ -18,6 +18,53 @@ def quick_sort_3partition(sorting: list, left: int, right: int) -> None: quick_sort_3partition(sorting, b + 1, right) +def quick_sort_lomuto_partition(sorting: list, left: int, right: int) -> None: + """ + A pure Python implementation of quick sort algorithm(in-place) + with Lomuto partition scheme: + https://en.wikipedia.org/wiki/Quicksort#Lomuto_partition_scheme + + :param sorting: sort list + :param left: left endpoint of sorting + :param right: right endpoint of sorting + :return: None + + Examples: + >>> nums1 = [0, 5, 3, 1, 2] + >>> quick_sort_lomuto_partition(nums1, 0, 4) + >>> nums1 + [0, 1, 2, 3, 5] + >>> nums2 = [] + >>> quick_sort_lomuto_partition(nums2, 0, 0) + >>> nums2 + [] + >>> nums3 = [-2, 5, 0, -4] + >>> quick_sort_lomuto_partition(nums3, 0, 3) + >>> nums3 + [-4, -2, 0, 5] + """ + if left < right: + pivot_index = lomuto_partition(sorting, left, right) + quick_sort_lomuto_partition(sorting, left, pivot_index - 1) + quick_sort_lomuto_partition(sorting, pivot_index + 1, right) + + +def lomuto_partition(sorting: list, left: int, right: int) -> int: + """ + Example: + >>> lomuto_partition([1,5,7,6], 0, 3) + 2 + """ + pivot = sorting[right] + store_index = left + for i in range(left, right): + if sorting[i] < pivot: + sorting[store_index], sorting[i] = sorting[i], sorting[store_index] + store_index += 1 + sorting[right], sorting[store_index] = sorting[store_index], sorting[right] + return store_index + + def three_way_radix_quicksort(sorting: list) -> list: """ Three-way radix quicksort: diff --git a/sorts/radix_sort.py b/sorts/radix_sort.py index 57dbbaa79076..b802b5278119 100644 --- a/sorts/radix_sort.py +++ b/sorts/radix_sort.py @@ -30,7 +30,7 @@ def radix_sort(list_of_ints: List[int]) -> List[int]: max_digit = max(list_of_ints) while placement <= max_digit: # declare and initialize empty buckets - buckets = [list() for _ in range(RADIX)] + buckets: List[list] = [list() for _ in range(RADIX)] # split list_of_ints between the buckets for i in list_of_ints: tmp = int((i / placement) % RADIX) diff --git a/sorts/recursive_insertion_sort.py b/sorts/recursive_insertion_sort.py index 66dd08157df1..89f88b4a961b 100644 --- a/sorts/recursive_insertion_sort.py +++ b/sorts/recursive_insertion_sort.py @@ -4,6 +4,8 @@ from __future__ import annotations +from typing import List + def rec_insertion_sort(collection: list, n: int): """ @@ -70,6 +72,6 @@ def insert_next(collection: list, index: int): if __name__ == "__main__": numbers = input("Enter integers separated by spaces: ") - numbers = [int(num) for num in numbers.split()] - rec_insertion_sort(numbers, len(numbers)) - print(numbers) + number_list: List[int] = [int(num) for num in numbers.split()] + rec_insertion_sort(number_list, len(number_list)) + print(number_list) diff --git a/sorts/shell_sort.py b/sorts/shell_sort.py index 2e749e43d056..10ae9ba407ec 100644 --- a/sorts/shell_sort.py +++ b/sorts/shell_sort.py @@ -26,7 +26,8 @@ def shell_sort(collection): while j >= gap and collection[j - gap] > insert_value: collection[j] = collection[j - gap] j -= gap - collection[j] = insert_value + if j != i: + collection[j] = insert_value return collection diff --git a/sorts/slowsort.py b/sorts/slowsort.py new file mode 100644 index 000000000000..53bb14554ee2 --- /dev/null +++ b/sorts/slowsort.py @@ -0,0 +1,63 @@ +""" +Slowsort is a sorting algorithm. It is of humorous nature and not useful. +It's based on the principle of multiply and surrender, +a tongue-in-cheek joke of divide and conquer. +It was published in 1986 by Andrei Broder and Jorge Stolfi +in their paper Pessimal Algorithms and Simplexity Analysis +(a parody of optimal algorithms and complexity analysis). + +Source: https://en.wikipedia.org/wiki/Slowsort +""" + +from typing import Optional + + +def slowsort( + sequence: list, start: Optional[int] = None, end: Optional[int] = None +) -> None: + """ + Sorts sequence[start..end] (both inclusive) in-place. + start defaults to 0 if not given. + end defaults to len(sequence) - 1 if not given. + It returns None. + >>> seq = [1, 6, 2, 5, 3, 4, 4, 5]; slowsort(seq); seq + [1, 2, 3, 4, 4, 5, 5, 6] + >>> seq = []; slowsort(seq); seq + [] + >>> seq = [2]; slowsort(seq); seq + [2] + >>> seq = [1, 2, 3, 4]; slowsort(seq); seq + [1, 2, 3, 4] + >>> seq = [4, 3, 2, 1]; slowsort(seq); seq + [1, 2, 3, 4] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, 2, 7); seq + [9, 8, 2, 3, 4, 5, 6, 7, 1, 0] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, end = 4); seq + [5, 6, 7, 8, 9, 4, 3, 2, 1, 0] + >>> seq = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; slowsort(seq, start = 5); seq + [9, 8, 7, 6, 5, 0, 1, 2, 3, 4] + """ + if start is None: + start = 0 + + if end is None: + end = len(sequence) - 1 + + if start >= end: + return + + mid = (start + end) // 2 + + slowsort(sequence, start, mid) + slowsort(sequence, mid + 1, end) + + if sequence[end] < sequence[mid]: + sequence[end], sequence[mid] = sequence[mid], sequence[end] + + slowsort(sequence, start, end - 1) + + +if __name__ == "__main__": + from doctest import testmod + + testmod() diff --git a/other/anagrams.py b/strings/anagrams.py similarity index 95% rename from other/anagrams.py rename to strings/anagrams.py index 0be013d5bc47..1a7c675d6719 100644 --- a/other/anagrams.py +++ b/strings/anagrams.py @@ -6,7 +6,7 @@ start_time = time.time() print("creating word list...") path = os.path.split(os.path.realpath(__file__)) -with open(path[0] + "/words") as f: +with open(path[0] + "/words.txt") as f: word_list = sorted(list({word.strip().lower() for word in f})) diff --git a/other/autocomplete_using_trie.py b/strings/autocomplete_using_trie.py similarity index 100% rename from other/autocomplete_using_trie.py rename to strings/autocomplete_using_trie.py diff --git a/other/detecting_english_programmatically.py b/strings/detecting_english_programmatically.py similarity index 100% rename from other/detecting_english_programmatically.py rename to strings/detecting_english_programmatically.py diff --git a/other/dictionary.txt b/strings/dictionary.txt similarity index 100% rename from other/dictionary.txt rename to strings/dictionary.txt diff --git a/other/frequency_finder.py b/strings/frequency_finder.py similarity index 100% rename from other/frequency_finder.py rename to strings/frequency_finder.py diff --git a/strings/levenshtein_distance.py b/strings/levenshtein_distance.py index 54948a96670b..540a21c93da3 100644 --- a/strings/levenshtein_distance.py +++ b/strings/levenshtein_distance.py @@ -69,8 +69,4 @@ def levenshtein_distance(first_word: str, second_word: str) -> int: second_word = input("Enter the second word:\n").strip() result = levenshtein_distance(first_word, second_word) - print( - "Levenshtein distance between {} and {} is {}".format( - first_word, second_word, result - ) - ) + print(f"Levenshtein distance between {first_word} and {second_word} is {result}") diff --git a/other/palindrome.py b/strings/palindrome.py similarity index 100% rename from other/palindrome.py rename to strings/palindrome.py diff --git a/other/word_patterns.py b/strings/word_patterns.py similarity index 100% rename from other/word_patterns.py rename to strings/word_patterns.py diff --git a/other/words b/strings/words.txt similarity index 100% rename from other/words rename to strings/words.txt diff --git a/web_programming/covid_stats_via_xpath.py b/web_programming/covid_stats_via_xpath.py index d22ed017878c..85ea5d940d85 100644 --- a/web_programming/covid_stats_via_xpath.py +++ b/web_programming/covid_stats_via_xpath.py @@ -7,7 +7,7 @@ from collections import namedtuple import requests -from lxml import html +from lxml import html # type: ignore covid_data = namedtuple("covid_data", "cases deaths recovered")